迁移剩下的 UI 到 Serialization 方式

This commit is contained in:
2026-09-14 17:19:31 +08:00
parent 091a29db63
commit b628a8537c
107 changed files with 4147 additions and 3680 deletions
@@ -0,0 +1,57 @@
using SepCore.CustomUtility;
using SepCore.Definition;
using UnityEngine;
namespace SepCore.UI
{
public class ItemTooltipContext : UIContext
{
public int Index;
public string IconAssetName;
public string Title;
public ItemType ItemType;
public Rarity Rarity;
public string Description;
public int Price;
public Vector3 TargetPos;
public bool ShowRecycleButton;
public ItemTooltipContext()
{
Index = -1;
IconAssetName = string.Empty;
Title = string.Empty;
ItemType = ItemType.None;
Rarity = Rarity.White;
Description = string.Empty;
Price = 0;
TargetPos = Vector3.zero;
ShowRecycleButton = false;
}
public ItemTooltipContext(ItemTooltipRawData rawData) : this()
{
Index = rawData.Index;
ItemType = rawData.ItemType;
Rarity = rawData.Rarity;
Price = rawData.Price;
TargetPos = rawData.TargetPos;
ShowRecycleButton = rawData.OnRecycle != null;
if (ItemType == ItemType.Weapon)
{
var data = rawData.WeaponData;
IconAssetName = data.IconAssetName;
Title = data.Title;
Description = ItemDescUtility.CreateWeaponDescription(data);
}
else if (ItemType == ItemType.Prop)
{
var data = rawData.PropData;
IconAssetName = data.IconAssetName;
Title = data.Title;
Description = ItemDescUtility.CreatePropDescription(data);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cdcef9502b7fa944ca95cb839fdc60bc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,116 @@
using System;
using Cysharp.Threading.Tasks;
using SepCore.Event;
using SepCore.Definition;
using GameFramework.Event;
using UnityGameFramework.Runtime;
namespace SepCore.UI
{
public class ItemTooltipController : UIControllerBase<ItemTooltipContext, ItemTooltipForm>
{
protected override UIFormType UIFormType => UIFormType.ItemTooltipForm;
private Action<int, int> _onRecycle;
private bool _subscribed = false;
protected override void SubscribeCustomEvents()
{
if (_subscribed) return;
GameEntry.Event.Subscribe(ItemTooltipRecycleEventArgs.EventId, ItemTooltipRecycle);
GameEntry.Event.Subscribe(ItemTooltipHideEventArgs.EventId, ItemTooltipHide);
_subscribed = true;
}
protected override void UnsubscribeCustomEvents()
{
if (!_subscribed) return;
GameEntry.Event.Unsubscribe(ItemTooltipRecycleEventArgs.EventId, ItemTooltipRecycle);
GameEntry.Event.Unsubscribe(ItemTooltipHideEventArgs.EventId, ItemTooltipHide);
_subscribed = false;
}
protected override void RefreshUI(ItemTooltipForm form, ItemTooltipContext context)
{
form.RefreshUI(context).Forget();
}
protected override void CloseLoadedFormDirect(ItemTooltipForm form)
{
GameEntry.UI.CloseUIForm(form);
}
private static ItemTooltipContext BuildContext(ItemTooltipRawData rawData)
{
if (rawData == null)
{
Log.Error("ItemTooltipController.BuildContext() rawData is null.");
return null;
}
return new ItemTooltipContext(rawData);
}
public override async UniTask OpenUIAsync(object userData = null, float timeout = 30f)
{
if (userData is not ItemTooltipRawData rawData)
{
if (userData != null)
{
Log.Warning("ItemTooltipController.OpenUIAsync() userData type is invalid.");
}
else
{
Log.Warning("ItemTooltipController.OpenUIAsync() rawData is required.");
}
return;
}
ItemTooltipContext context = BuildContext(rawData);
if (context == null)
{
return;
}
_onRecycle = rawData.OnRecycle;
await OpenFormAsync(context, timeout);
}
public override async UniTask CloseUIAsync(object userData = null, float timeout = 30f)
{
_onRecycle = null;
await base.CloseUIAsync(userData, timeout);
}
public override void BindUseCase(IUIUseCase useCase)
{
if (useCase != null)
{
Log.Error("ItemTooltipController does not use a use case.");
}
}
#region Event Handlers
private void ItemTooltipRecycle(object sender, GameEventArgs e)
{
if (e is not ItemTooltipRecycleEventArgs args)
{
return;
}
_onRecycle?.Invoke(args.Index, args.Price);
}
private void ItemTooltipHide(object sender, GameEventArgs e)
{
if (e is not ItemTooltipHideEventArgs args) return;
CloseUIAsync().Forget();
}
#endregion
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8a9b5acbc476ae44a8259a5aea1c0cf2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,188 @@
using System;
using Cysharp.Threading.Tasks;
using SepCore.AsyncTask;
using SepCore.Event;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace SepCore.UI
{
public partial class ItemTooltipForm : UGuiForm
{
[SerializeField] private bool _autoResizeHeight = true;
[SerializeField] private float _fixedTopHeight = 150f;
[SerializeField] private float _fixedBottomHeight = 150f;
[SerializeField] private float _fixedWidth = 300f;
[SerializeField] private float _screenEdgePadding = 0f;
private ItemTooltipContext _context;
private Vector3 _targetPos;
private bool _listenersBound;
public async UniTaskVoid RefreshUI(ItemTooltipContext context)
{
EnsureListeners();
_context = context;
_targetPos = ConvertWorldToAnchored(_context.TargetPos);
View.contentRectTransform.anchoredPosition3D = _targetPos;
View.titleText.text = _context.Title;
View.typeText.text = BuildTypeText(_context.ItemType);
View.infoText.text = _context.Description;
View.textFormatText.Set(_context.Price);
View.recycleButton.gameObject.SetActive(_context.ShowRecycleButton);
if (!string.IsNullOrEmpty(_context.IconAssetName))
{
string iconAssetName = _context.IconAssetName;
var sprite = await SpriteLoader.LoadSpriteByKeyAsync(iconAssetName);
if (_context != null && _context.IconAssetName == iconAssetName)
{
View.iconArea.OnInit(sprite, context.Rarity);
ResizeToFitContent();
ApplyClampedTargetPos();
}
}
ResizeToFitContent();
ApplyClampedTargetPos();
}
protected override void OnOpen(object userData)
{
base.OnOpen(userData);
if (userData is not ItemTooltipContext context)
{
Log.Error("ItemTooltipForm context is invalid.");
return;
}
RefreshUI(context).Forget();
}
protected override void OnClose(bool isShutdown, object userData)
{
_context = null;
base.OnClose(isShutdown, userData);
}
public void OnRecycleButtonClick()
{
GameEntry.Event.Fire(this, ItemTooltipRecycleEventArgs.Create(_context.Index, _context.Price));
}
public void OnCancelButtonClick()
{
GameEntry.Event.Fire(this, ItemTooltipHideEventArgs.Create());
}
private void EnsureListeners()
{
if (_listenersBound)
{
return;
}
_listenersBound = true;
View.recycleButton.onClick.AddListener(OnRecycleButtonClick);
View.cancelButton.onClick.AddListener(OnCancelButtonClick);
}
private void ResizeToFitContent()
{
if (!_autoResizeHeight)
{
return;
}
View.infoText.ForceMeshUpdate();
var descriptionSize = View.infoText.rectTransform.sizeDelta;
descriptionSize.y = View.infoText.preferredHeight;
View.infoText.rectTransform.sizeDelta = descriptionSize;
float descriptionHeight = Mathf.Max(0f, View.infoText.preferredHeight);
float targetHeight = _fixedTopHeight + descriptionHeight + _fixedBottomHeight;
Vector2 size = new Vector2(_fixedWidth, targetHeight);
View.contentRectTransform.sizeDelta = size;
}
private void ApplyClampedTargetPos()
{
RectTransform parent = View.contentRectTransform.parent as RectTransform;
if (parent == null)
{
return;
}
Vector2 size = View.contentRectTransform.sizeDelta;
if (size.x <= 0f || size.y <= 0f)
{
size = View.contentRectTransform.rect.size;
}
Vector2 pivot = View.contentRectTransform.pivot;
float halfWidth = parent.rect.width * 0.5f;
float halfHeight = parent.rect.height * 0.5f;
float minX = -halfWidth + size.x * pivot.x + _screenEdgePadding;
float maxX = halfWidth - size.x * (1f - pivot.x) - _screenEdgePadding;
float minY = -halfHeight + size.y * pivot.y + _screenEdgePadding;
float maxY = halfHeight - size.y * (1f - pivot.y) - _screenEdgePadding;
float clampedX = minX <= maxX ? Mathf.Clamp(_targetPos.x, minX, maxX) : 0f;
float clampedY = minY <= maxY ? Mathf.Clamp(_targetPos.y, minY, maxY) : 0f;
View.contentRectTransform.anchoredPosition3D = new Vector3(clampedX, clampedY, _targetPos.z);
}
private Vector3 ConvertWorldToAnchored(Vector3 worldPos)
{
RectTransform parent = View.contentRectTransform.parent as RectTransform;
if (parent == null)
{
return worldPos;
}
Canvas canvas = View.contentRectTransform.GetComponentInParent<Canvas>();
Canvas rootCanvas = canvas != null ? canvas.rootCanvas : null;
Camera uiCamera = null;
if (rootCanvas != null && rootCanvas.renderMode != RenderMode.ScreenSpaceOverlay)
{
uiCamera = rootCanvas.worldCamera != null ? rootCanvas.worldCamera : Camera.main;
}
Vector2 screenPoint = RectTransformUtility.WorldToScreenPoint(uiCamera, worldPos);
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(parent, screenPoint, uiCamera,
out Vector2 localPoint))
{
return worldPos;
}
float z = View.contentRectTransform.anchoredPosition3D.z;
return new Vector3(localPoint.x, localPoint.y, z);
}
private string BuildTypeText(ItemType type)
{
return type switch
{
ItemType.None => "error",
ItemType.Weapon => "武器",
ItemType.Prop => "道具",
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 32469245f20597240a3c51d2cd1fb0c5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,13 @@
// <auto-generated />
using UnityEngine;
namespace SepCore.UI
{
[DisallowMultipleComponent]
public partial class ItemTooltipForm : UGuiForm
{
[SerializeField] private ItemTooltipView _view = null;
public ItemTooltipView View => _view;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7489a7d3442947647bee8612880a3f83
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,18 @@
// <auto-generated />
using UnityEngine;
namespace SepCore.UI
{
[DisallowMultipleComponent]
public partial class ItemTooltipView : MonoBehaviour
{
[SerializeField] public global::UnityEngine.RectTransform contentRectTransform;
[SerializeField] public global::SepCore.UI.IconArea iconArea;
[SerializeField] public global::TMPro.TextMeshProUGUI titleText;
[SerializeField] public global::TMPro.TextMeshProUGUI typeText;
[SerializeField] public global::TMPro.TextMeshProUGUI infoText;
[SerializeField] public global::SepCore.UI.CommonButton recycleButton;
[SerializeField] public global::SepCore.UI.FormatTextUI textFormatText;
[SerializeField] public global::SepCore.UI.CommonButton cancelButton;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4d9524b70efb9c840a84baed8b0c12d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: