迁移剩下的 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,41 @@
using SepCore.Definition;
using TMPro;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace SepCore.UI
{
/// <summary>
/// 使用 Luban 格式化文本表(TbFormatText)驱动 TextMeshProUGUI 的组件。
/// 在 Inspector 上填写 Key,调用 Set 传入格式化参数即可更新文本。
/// </summary>
[RequireComponent(typeof(TextMeshProUGUI))]
public class FormatTextUI : MonoBehaviour
{
[SerializeField] private string key;
private TextMeshProUGUI _text;
private void Awake()
{
_text = GetComponent<TextMeshProUGUI>();
}
public void Set(params object[] args)
{
FormatText format = GameEntry.Luban.Tables.TbFormatText.GetOrDefault(key);
if (format == null)
{
Log.Warning("Can not find format text '{0}'.", key);
return;
}
_text.SetText(string.Format(format.Format, args));
}
public void Clear()
{
_text.SetText(string.Empty);
}
}
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: f21ab051c635de04998b80804d9bafe5
guid: 2efdcd06cdaa830448f76b22ea7c98fb
MonoImporter:
externalObjects: {}
serializedVersion: 2
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 16815ca7d82e7ea4c91dd017189eeacb
guid: 0a0edeeb11ca93846b60f6fbb75452a9
folderAsset: yes
DefaultImporter:
externalObjects: {}
@@ -11,18 +11,19 @@ namespace SepCore.UI
[SerializeField] private IconArea _iconArea;
[SerializeField] private RectTransform _itemRect;
[SerializeField] private CommonButton _button;
private DisplayItemContext _context;
private int _index = -1;
public void SetIndex(int index)
{
_index = index;
}
private bool _listenersBound;
public async UniTaskVoid OnInit(DisplayItemContext context, int index)
{
EnsureListeners();
_context = context;
_index = index;
@@ -38,7 +39,22 @@ namespace SepCore.UI
_iconArea.OnReset();
}
public void OnItemInfoShow()
private void EnsureListeners()
{
if (_listenersBound)
{
return;
}
_listenersBound = true;
CommonButton button = GetComponent<CommonButton>();
button.onClick.AddListener(OnItemInfoLock);
button.RegisterPointerEnterAction(OnItemInfoShow);
button.RegisterPointerExitAction(OnItemInfoHide);
}
private void OnItemInfoShow()
{
if (_index < 0) return;
@@ -51,14 +67,14 @@ namespace SepCore.UI
GameEntry.Event.Fire(this, DisplayItemHoverEventArgs.Create(_index, _context.IsWeapon, targetPos));
}
public void OnItemInfoLock()
private void OnItemInfoLock()
{
GameEntry.Event.Fire(this, DisplayItemLockEventArgs.Create());
}
public void OnItemInfoHide()
private void OnItemInfoHide()
{
GameEntry.Event.Fire(this, DisplayItemHideEventArgs.Create());
}
}
}
}
@@ -0,0 +1,9 @@
namespace SepCore.UI
{
public class DisplayListContext : UIContext
{
public int CurrentCount;
public int MaxCount = -1;
public DisplayItemContext[] ItemContexts;
}
}
@@ -0,0 +1,51 @@
using System.Collections.Generic;
using UnityEngine;
namespace SepCore.UI
{
public partial class DisplayListForm
{
private readonly List<DisplayItem> _displayItems = new List<DisplayItem>();
public void OnInit(DisplayListContext context)
{
View.titleFormatText.Set(context.CurrentCount, context.MaxCount);
int targetCount = Mathf.Min(context.CurrentCount, context.ItemContexts.Length);
EnsureItemCount(targetCount);
for (int i = 0; i < _displayItems.Count; i++)
{
DisplayItem item = _displayItems[i];
if (i < targetCount)
{
item.OnInit(context.ItemContexts[i], i).Forget();
item.gameObject.SetActive(true);
}
else
{
item.OnReset();
item.gameObject.SetActive(false);
}
}
}
public void OnReset()
{
for (int i = 0; i < _displayItems.Count; i++)
{
_displayItems[i].OnReset();
_displayItems[i].gameObject.SetActive(false);
}
}
private void EnsureItemCount(int count)
{
while (_displayItems.Count < count)
{
DisplayItem item = Instantiate(View.displayItemTemplate, View.listRoot.transform);
_displayItems.Add(item);
}
}
}
}
@@ -0,0 +1,13 @@
// <auto-generated />
using UnityEngine;
namespace SepCore.UI
{
[DisallowMultipleComponent]
public partial class DisplayListForm : UGuiForm
{
[SerializeField] private DisplayListView _view = null;
public DisplayListView View => _view;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 220e0137e2c60eb4084e50234225dd50
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 DisplayListView : MonoBehaviour
{
[SerializeField] public global::SepCore.UI.FormatTextUI titleFormatText;
[SerializeField] public global::UnityEngine.UI.GridLayoutGroup listRoot;
[SerializeField] public global::SepCore.UI.DisplayItem displayItemTemplate;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d9eb9bf8c92339346b767aa476f14f94
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 5d21ab27094da2548a00119313150780
guid: ec716511ca2cd8043bff922575851a1b
folderAsset: yes
DefaultImporter:
externalObjects: {}
@@ -2,47 +2,29 @@ using System.Collections;
using SepCore.Event;
using GameFramework.Event;
using SepCore.EnemyManager;
using TMPro;
using UnityEngine;
using Unity.Profiling;
using UnityEngine.UI;
namespace SepCore.UI
{
public class HudForm : UGuiForm
public partial class HudForm : UGuiForm
{
[SerializeField] private float _sliderFadeDuration = 0.5f;
[SerializeField] private Slider _hpSlider;
[SerializeField] private TMP_Text _hpText;
private float _currentHpPercent = 0;
private Coroutine _hpSliderFadeCoroutine;
[SerializeField] private Slider _expSlider;
private float _currentExpPercent = 0;
private Coroutine _expSliderFadeCoroutine;
[SerializeField] private TMP_Text _expText;
private int _playerCurrentLevel = 1;
[SerializeField] private TMP_Text _coinText;
private int _currentCoin = 0;
[SerializeField] private TMP_Text _levelTimeLeftText;
private int _currentTimeLeft = 0;
[SerializeField] private TMP_Text _enemyCountText;
private EnemyManagerComponent _enemy;
[SerializeField] private GameObject _movePromptRoot;
[SerializeField] private TMP_Text _movePromptText;
[SerializeField] private TMP_Text _cpuTimeCostText;
[SerializeField] private float _performanceStatsRefreshInterval = 0.2f;
private ProfilerRecorder _cpuTimeRecorder;
@@ -57,8 +39,8 @@ namespace SepCore.UI
public void RefreshMovePrompt(string prompt)
{
bool visible = !string.IsNullOrEmpty(prompt);
_movePromptRoot.SetActive(visible);
_movePromptText.text = prompt;
View.inputPromptObject.gameObject.SetActive(visible);
View.inputPromptText.text = prompt;
}
#region FSM
@@ -145,15 +127,7 @@ namespace SepCore.UI
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
base.OnUpdate(elapseSeconds, realElapseSeconds);
if (_enemy == null)
{
_enemy = GameEntry.EnemyManager;
}
if (_enemy != null)
{
_enemyCountText.text = $"Enemy: {_enemy.CurrentEnemyCount}";
}
View.enemyCountFormatText.Set(_enemy.CurrentEnemyCount);
}
#endregion
@@ -179,14 +153,13 @@ namespace SepCore.UI
if (!hasCpuFrame)
{
_cpuTimeCostText.text =
"CPU Frame: -- ms\nSimulation: -- ms\nJob Wait: -- ms";
View.cpuTimeCostFormatText.Set(-1);
return;
}
float cpuFrameMilliseconds = cpuFrameNanoseconds / 1_000_000f;
_cpuTimeCostText.SetText("CPU Frame: {0:2} ms",cpuFrameMilliseconds);
View.cpuTimeCostFormatText.Set(cpuFrameMilliseconds);
}
private static bool TryGetLastValue(ProfilerRecorder recorder, out long value)
@@ -207,7 +180,7 @@ namespace SepCore.UI
{
if (!(e is PlayerHealthChangeEventArgs args)) return;
_hpText.text = $"{args.CurrentHealth}/{args.MaxHealth}";
View.hpFormatText.Set(args.CurrentHealth, args.MaxHealth);
float percent = (float)args.CurrentHealth / args.MaxHealth;
if (Mathf.Approximately(_currentHpPercent, percent)) return;
@@ -215,7 +188,7 @@ namespace SepCore.UI
_currentHpPercent = percent;
if (_hpSliderFadeCoroutine != null) StopCoroutine(_hpSliderFadeCoroutine);
_hpSliderFadeCoroutine = StartCoroutine(_hpSlider.SmoothValue(percent, _sliderFadeDuration));
_hpSliderFadeCoroutine = StartCoroutine(View.hpSlider.SmoothValue(percent, _sliderFadeDuration));
}
private void PlayerExpChange(object sender, GameEventArgs e)
@@ -227,7 +200,7 @@ namespace SepCore.UI
_currentExpPercent = percent;
if (_expSliderFadeCoroutine != null) StopCoroutine(_expSliderFadeCoroutine);
_expSliderFadeCoroutine = StartCoroutine(_expSlider.SmoothValue(percent, _sliderFadeDuration));
_expSliderFadeCoroutine = StartCoroutine(View.expSlider.SmoothValue(percent, _sliderFadeDuration));
}
private void PlayerLevelUp(object sender, GameEventArgs e)
@@ -235,7 +208,7 @@ namespace SepCore.UI
if (!(e is PlayerLevelUpEventArgs)) return;
_playerCurrentLevel++;
_expText.text = $"LV.{_playerCurrentLevel}";
View.expFormatText.Set(_playerCurrentLevel);
}
private void PlayerCoinChange(object sender, GameEventArgs e)
@@ -243,7 +216,7 @@ namespace SepCore.UI
if (!(e is PlayerCoinChangeEventArgs args)) return;
if (_currentCoin == args.CoinCount) return;
_coinText.text = $"<sprite name=\"coin\" index=0> {args.CoinCount}";
View.coinFormatText.Set(args.CoinCount);
_currentCoin = args.CoinCount;
}
@@ -253,7 +226,7 @@ namespace SepCore.UI
if (_currentTimeLeft == args.LevelTimeLeft) return;
_currentTimeLeft = args.LevelTimeLeft;
_levelTimeLeftText.text = _currentTimeLeft.ToString();
View.countdownText.text = _currentTimeLeft.ToString();
}
#endregion
@@ -0,0 +1,13 @@
// <auto-generated />
using UnityEngine;
namespace SepCore.UI
{
[DisallowMultipleComponent]
public partial class HudForm : UGuiForm
{
[SerializeField] private HudView _view = null;
public HudView View => _view;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f50fc6aab9e9b4846b0a80dd670c94b7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,20 @@
// <auto-generated />
using UnityEngine;
namespace SepCore.UI
{
[DisallowMultipleComponent]
public partial class HudView : MonoBehaviour
{
[SerializeField] public global::UnityEngine.UI.Slider hpSlider;
[SerializeField] public global::SepCore.UI.FormatTextUI hpFormatText;
[SerializeField] public global::UnityEngine.UI.Slider expSlider;
[SerializeField] public global::SepCore.UI.FormatTextUI expFormatText;
[SerializeField] public global::SepCore.UI.FormatTextUI coinFormatText;
[SerializeField] public global::SepCore.UI.FormatTextUI enemyCountFormatText;
[SerializeField] public global::SepCore.UI.FormatTextUI cpuTimeCostFormatText;
[SerializeField] public global::TMPro.TextMeshProUGUI countdownText;
[SerializeField] public global::UnityEngine.RectTransform inputPromptObject;
[SerializeField] public global::TMPro.TextMeshProUGUI inputPromptText;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 128e0c828a462a242a89114f9c8b9d32
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -2,28 +2,13 @@ using System;
using Cysharp.Threading.Tasks;
using SepCore.AsyncTask;
using SepCore.Event;
using TMPro;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace SepCore.UI
{
public class ItemTooltipForm : UGuiForm
public partial class ItemTooltipForm : UGuiForm
{
[SerializeField] private RectTransform _content;
[SerializeField] private IconArea _iconArea;
[SerializeField] private TMP_Text _itemTitle;
[SerializeField] private TMP_Text _itemTypeText;
[SerializeField] private TMP_Text _itemDescription;
[SerializeField] private CommonButton _recycleButton;
[SerializeField] private TMP_Text _itemPrice;
[SerializeField] private bool _autoResizeHeight = true;
[SerializeField] private float _fixedTopHeight = 150f;
@@ -38,21 +23,24 @@ namespace SepCore.UI
private Vector3 _targetPos;
private bool _listenersBound;
public async UniTaskVoid RefreshUI(ItemTooltipContext context)
{
EnsureListeners();
_context = context;
_targetPos = ConvertWorldToAnchored(_context.TargetPos);
Log.Info($"{_context.TargetPos}->{_targetPos}");
_content.anchoredPosition3D = _targetPos;
View.contentRectTransform.anchoredPosition3D = _targetPos;
_itemTitle.text = _context.Title;
_itemTypeText.text = BuildTypeText(_context.ItemType);
_itemDescription.text = _context.Description;
_itemPrice.text = $"{_context.Price} <sprite name=\"coin\" index=0>";
View.titleText.text = _context.Title;
View.typeText.text = BuildTypeText(_context.ItemType);
View.infoText.text = _context.Description;
View.textFormatText.Set(_context.Price);
_recycleButton.gameObject.SetActive(_context.ShowRecycleButton);
View.recycleButton.gameObject.SetActive(_context.ShowRecycleButton);
if (!string.IsNullOrEmpty(_context.IconAssetName))
{
@@ -60,7 +48,7 @@ namespace SepCore.UI
var sprite = await SpriteLoader.LoadSpriteByKeyAsync(iconAssetName);
if (_context != null && _context.IconAssetName == iconAssetName)
{
_iconArea.OnInit(sprite, context.Rarity);
View.iconArea.OnInit(sprite, context.Rarity);
ResizeToFitContent();
ApplyClampedTargetPos();
}
@@ -74,7 +62,7 @@ namespace SepCore.UI
{
base.OnOpen(userData);
if (!(userData is ItemTooltipContext context))
if (userData is not ItemTooltipContext context)
{
Log.Error("ItemTooltipForm context is invalid.");
return;
@@ -89,6 +77,29 @@ namespace SepCore.UI
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)
@@ -96,32 +107,32 @@ namespace SepCore.UI
return;
}
_itemDescription.ForceMeshUpdate();
var descriptionSize = _itemDescription.rectTransform.sizeDelta;
descriptionSize.y = _itemDescription.preferredHeight;
_itemDescription.rectTransform.sizeDelta = descriptionSize;
float descriptionHeight = Mathf.Max(0f, _itemDescription.preferredHeight);
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);
_content.sizeDelta = size;
View.contentRectTransform.sizeDelta = size;
}
private void ApplyClampedTargetPos()
{
RectTransform parent = _content.parent as RectTransform;
RectTransform parent = View.contentRectTransform.parent as RectTransform;
if (parent == null)
{
return;
}
Vector2 size = _content.sizeDelta;
Vector2 size = View.contentRectTransform.sizeDelta;
if (size.x <= 0f || size.y <= 0f)
{
size = _content.rect.size;
size = View.contentRectTransform.rect.size;
}
Vector2 pivot = _content.pivot;
Vector2 pivot = View.contentRectTransform.pivot;
float halfWidth = parent.rect.width * 0.5f;
float halfHeight = parent.rect.height * 0.5f;
@@ -133,18 +144,18 @@ namespace SepCore.UI
float clampedX = minX <= maxX ? Mathf.Clamp(_targetPos.x, minX, maxX) : 0f;
float clampedY = minY <= maxY ? Mathf.Clamp(_targetPos.y, minY, maxY) : 0f;
_content.anchoredPosition3D = new Vector3(clampedX, clampedY, _targetPos.z);
View.contentRectTransform.anchoredPosition3D = new Vector3(clampedX, clampedY, _targetPos.z);
}
private Vector3 ConvertWorldToAnchored(Vector3 worldPos)
{
RectTransform parent = _content.parent as RectTransform;
RectTransform parent = View.contentRectTransform.parent as RectTransform;
if (parent == null)
{
return worldPos;
}
Canvas canvas = _content.GetComponentInParent<Canvas>();
Canvas canvas = View.contentRectTransform.GetComponentInParent<Canvas>();
Canvas rootCanvas = canvas != null ? canvas.rootCanvas : null;
Camera uiCamera = null;
if (rootCanvas != null && rootCanvas.renderMode != RenderMode.ScreenSpaceOverlay)
@@ -159,20 +170,10 @@ namespace SepCore.UI
return worldPos;
}
float z = _content.anchoredPosition3D.z;
float z = View.contentRectTransform.anchoredPosition3D.z;
return new Vector3(localPoint.x, localPoint.y, z);
}
public void OnRecycleButtonClick()
{
GameEntry.Event.Fire(this, ItemTooltipRecycleEventArgs.Create(_context.Index, _context.Price));
}
public void OnCancelButtonClick()
{
GameEntry.Event.Fire(this, ItemTooltipHideEventArgs.Create());
}
private string BuildTypeText(ItemType type)
{
return type switch
@@ -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:
@@ -0,0 +1,108 @@
using System.Collections.Generic;
using SepCore.Event;
using UnityGameFramework.Runtime;
namespace SepCore.UI
{
public partial class LevelUpForm : UGuiForm
{
private readonly List<LevelUpRewardItem> _rewardItems = new List<LevelUpRewardItem>();
private LevelUpContext _context;
private bool _listenersBound;
public void RefreshUI(LevelUpContext context)
{
EnsureButtonListeners();
_context = context;
if (View.levelUpRewardTemplate.gameObject.activeSelf)
{
View.levelUpRewardTemplate.gameObject.SetActive(false);
}
int count = _context.Props?.Count ?? 0;
EnsureRewardItemCount(count);
for (int i = 0; i < _rewardItems.Count; i++)
{
LevelUpRewardItem item = _rewardItems[i];
if (i < count)
{
item.Init(_context.Props[i]);
item.gameObject.SetActive(true);
}
else
{
item.gameObject.SetActive(false);
}
}
// 刷新 -20 <sprite name="coin" index=0>
View.refreshCostFormatText.Set(context.RefreshPrice);
}
#region Lifecycle
protected override void OnOpen(object userData)
{
base.OnOpen(userData);
if (userData is not LevelUpContext context)
{
Log.Error("LevelUpForm requires LevelUpContext as userData.");
return;
}
RefreshUI(context);
}
protected override void OnClose(bool isShutdown, object userData)
{
_context = null;
base.OnClose(isShutdown, userData);
}
#endregion
private void EnsureButtonListeners()
{
if (_listenersBound)
{
return;
}
_listenersBound = true;
View.refreshButton.onClick.AddListener(OnRefreshButtonClick);
}
private void EnsureRewardItemCount(int count)
{
while (_rewardItems.Count < count)
{
int index = _rewardItems.Count;
LevelUpRewardItem item = Instantiate(View.levelUpRewardTemplate, View.rewardAreaRoot);
CommonButton button = item.GetComponentInChildren<CommonButton>();
if (button != null)
{
button.onClick.AddListener(() => OnSelectButtonClick(index));
}
_rewardItems.Add(item);
}
}
public void OnSelectButtonClick(int index)
{
GameEntry.Event.Fire(this, LevelUpPropSelectedEventArgs.Create(index));
}
public void OnRefreshButtonClick()
{
GameEntry.Event.Fire(this, LevelUpRefreshEventArgs.Create(_context.RefreshPrice));
}
}
}
@@ -0,0 +1,13 @@
// <auto-generated />
using UnityEngine;
namespace SepCore.UI
{
[DisallowMultipleComponent]
public partial class LevelUpForm : UGuiForm
{
[SerializeField] private LevelUpView _view = null;
public LevelUpView View => _view;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3172a8000d5463f45b2f4c5f5a19433d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 32bf4a12bd0ac2847b19bbb76b7c3dd5
guid: 0f0b958b02844f940982990f3922c912
folderAsset: yes
DefaultImporter:
externalObjects: {}
@@ -0,0 +1,14 @@
// <auto-generated />
using UnityEngine;
namespace SepCore.UI
{
[DisallowMultipleComponent]
public partial class LevelUpView : MonoBehaviour
{
[SerializeField] public global::UnityEngine.RectTransform rewardAreaRoot;
[SerializeField] public global::SepCore.UI.LevelUpRewardItem levelUpRewardTemplate;
[SerializeField] public global::SepCore.UI.CommonButton refreshButton;
[SerializeField] public global::SepCore.UI.FormatTextUI refreshCostFormatText;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 39b4ea16f6b8d6a47b6ea7078982a7d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,69 +0,0 @@
using SepCore.Event;
using TMPro;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace SepCore.UI
{
public class LevelUpForm : UGuiForm
{
[SerializeField] private LevelUpRewardItem[] _propItems;
[SerializeField] private TMP_Text _refreshButtonText;
private LevelUpContext _context;
public void RefreshUI(LevelUpContext context)
{
_context = context;
foreach (var propItem in _propItems)
{
propItem.gameObject.SetActive(false);
}
int count = Mathf.Min(_propItems.Length, _context.Props.Count);
for (int i = 0; i < count; i++)
{
_propItems[i].gameObject.SetActive(true);
_propItems[i].Init(_context.Props[i]);
}
//刷新 -20 <sprite name="coin" index=0>
_refreshButtonText.text = $"刷新 -{context.RefreshPrice} <sprite name=\"coin\" index=0>";
}
#region FSM
protected override void OnOpen(object userData)
{
base.OnOpen(userData);
if (userData is not LevelUpContext context)
{
Log.Error("LevelUpForm requires LevelUpContext as userData.");
return;
}
RefreshUI(context);
}
protected override void OnClose(bool isShutdown, object userData)
{
_context = null;
base.OnClose(isShutdown, userData);
}
#endregion
public void OnSelectButtonClick(int index)
{
GameEntry.Event.Fire(this, LevelUpPropSelectedEventArgs.Create(index));
}
public void OnRefreshButtonClick()
{
GameEntry.Event.Fire(this, LevelUpRefreshEventArgs.Create(_context.RefreshPrice));
}
}
}
@@ -1,226 +0,0 @@
using System;
using System.Collections.Generic;
using GameFramework.ObjectPool;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityGameFramework.Runtime;
namespace SepCore.UI
{
public enum DisplayListAreaType : byte
{
None = 0,
Prop = 1,
Weapon = 2
}
public class DisplayListArea : MonoBehaviour
{
[SerializeField] private TMP_Text _titleText;
[SerializeField] private TMP_Text _countText;
[SerializeField] private DisplayItem _displayItemTemplate;
[SerializeField] private Transform _displayItemRoot;
[SerializeField] private Vector2 _startPosition = Vector2.zero;
[SerializeField] private float _itemSize = 150f;
[SerializeField] private float _itemSpacing = 10f;
[SerializeField] private int _fixedColumnCount = 0;
[SerializeField] private int _instancePoolCapacity = 32;
private DisplayListAreaContext _context;
private IObjectPool<DisplayItemObject> _displayItemObjectPool;
private const string DisplayItemPoolName = "DisplayItem";
private readonly List<DisplayItem> _activeItems = new List<DisplayItem>();
public void OnInit(DisplayListAreaContext context)
{
EnsurePool();
_context = context;
_titleText.text = context.Title;
UpdateCountText(context.CurrentCount, context.MaxCount);
int targetCount = Mathf.Min(context.CurrentCount, context.ItemContexts.Length);
EnsureItemCount(targetCount);
for (int i = 0; i < targetCount; i++)
{
_activeItems[i].OnInit(context.ItemContexts[i], i).Forget();
}
LayoutItems();
}
public void OnReset()
{
_context = null;
for (int i = _activeItems.Count - 1; i >= 0; i--)
{
HideItem(_activeItems[i]);
}
}
public void OnDestroy()
{
_displayItemObjectPool?.ReleaseAllUnused();
}
public DisplayItem AddItem(DisplayItemContext itemContext)
{
EnsurePool();
DisplayItem item = CreateItem();
int index = _activeItems.Count;
_activeItems.Add(item);
item.OnInit(itemContext, index).Forget();
item.SetIndex(index);
int columnCount = _fixedColumnCount > 0 ? _fixedColumnCount : CalculateColumnCount(GetRootWidth());
LayoutItem(item, index, columnCount);
int currentCount = _activeItems.Count;
_context.CurrentCount = currentCount;
UpdateCountText(currentCount, _context.MaxCount);
return item;
}
public bool RemoveItemAt(int index)
{
if (index < 0 || index >= _activeItems.Count)
{
return false;
}
HideItem(_activeItems[index]);
LayoutItems();
int currentCount = _activeItems.Count;
_context.CurrentCount = currentCount;
UpdateCountText(currentCount, _context.MaxCount);
return true;
}
private void EnsurePool()
{
if (_displayItemObjectPool != null) return;
GridLayoutGroup gridLayout = _displayItemRoot.GetComponent<GridLayoutGroup>();
if (gridLayout != null)
{
gridLayout.enabled = false;
}
if (GameEntry.ObjectPool.HasObjectPool<DisplayItemObject>(DisplayItemPoolName))
{
_displayItemObjectPool = GameEntry.ObjectPool.GetObjectPool<DisplayItemObject>(DisplayItemPoolName);
return;
}
_displayItemObjectPool =
GameEntry.ObjectPool.CreateSingleSpawnObjectPool<DisplayItemObject>(DisplayItemPoolName,
_instancePoolCapacity);
}
private void EnsureItemCount(int count)
{
for (int i = _activeItems.Count; i < count; i++)
{
_activeItems.Add(CreateItem());
}
for (int i = _activeItems.Count - 1; i >= count; i--)
{
HideItem(_activeItems[i]);
}
}
private DisplayItem CreateItem()
{
DisplayItem item;
DisplayItemObject itemObject = _displayItemObjectPool.Spawn();
if (itemObject != null)
{
item = (DisplayItem)itemObject.Target;
}
else
{
item = Instantiate(_displayItemTemplate);
_displayItemObjectPool.Register(DisplayItemObject.Create(item), true);
}
item.transform.SetParent(_displayItemRoot, false);
item.gameObject.SetActive(true);
return item;
}
private void HideItem(DisplayItem item)
{
item.OnReset();
_activeItems.Remove(item);
item.gameObject.SetActive(false);
_displayItemObjectPool.Unspawn(item);
}
private void LayoutItems()
{
int columnCount = _fixedColumnCount > 0 ? _fixedColumnCount : CalculateColumnCount(GetRootWidth());
for (int i = 0; i < _activeItems.Count; i++)
{
DisplayItem item = _activeItems[i];
item.SetIndex(i);
LayoutItem(item, i, columnCount);
}
}
private void LayoutItem(DisplayItem item, int index, int columnCount)
{
RectTransform itemRect = item.GetComponent<RectTransform>();
float step = _itemSize + _itemSpacing;
int row = index / columnCount;
int col = index % columnCount;
itemRect.anchorMin = new Vector2(0f, 1f);
itemRect.anchorMax = new Vector2(0f, 1f);
itemRect.pivot = new Vector2(0f, 1f);
itemRect.sizeDelta = new Vector2(_itemSize, _itemSize);
itemRect.anchoredPosition = _startPosition + new Vector2(col * step, -row * step);
}
private float GetRootWidth()
{
RectTransform rootRect = (RectTransform)_displayItemRoot;
return rootRect.rect.width;
}
private int CalculateColumnCount(float rootWidth)
{
if (rootWidth <= 0f) return 1;
float step = _itemSize + _itemSpacing;
int columns = Mathf.FloorToInt((rootWidth + _itemSpacing) / step);
return Mathf.Max(1, columns);
}
private void UpdateCountText(int currentCount, int maxCount)
{
_countText.text = maxCount == -1 ? $"({currentCount})" : $"({currentCount}/{maxCount})";
}
}
}
@@ -1,14 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace SepCore.UI
{
public class DisplayListAreaContext : UIContext
{
public string Title;
public int CurrentCount;
public int MaxCount = -1;
public DisplayItemContext[] ItemContexts;
}
}
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 7af0b9398035e254cb6de37a6c301552
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 166c0ae58c1d0b34ababe6d3a3791efa
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,27 +0,0 @@
using GameFramework;
using GameFramework.ObjectPool;
using UnityEngine;
namespace SepCore.UI
{
public class DisplayItemObject : ObjectBase
{
public static DisplayItemObject Create(object target)
{
DisplayItemObject displayItemObject = ReferencePool.Acquire<DisplayItemObject>();
displayItemObject.Initialize(target);
return displayItemObject;
}
protected override void Release(bool isShutdown)
{
DisplayItem item = (DisplayItem)Target;
if (item == null)
{
return;
}
Object.Destroy(item.gameObject);
}
}
}
@@ -8,8 +8,8 @@ namespace SepCore.UI
public int RefreshPrice;
public int PlayerCoin;
public List<GoodsItemContext> GoodsItems;
public DisplayListAreaContext PropListContext;
public DisplayListAreaContext WeaponListContext;
public DisplayListContext PropListContext;
public DisplayListContext WeaponListContext;
// 控制字段
public bool NeedRefreshGoodsItems;
@@ -7,6 +7,13 @@ using UnityGameFramework.Runtime;
namespace SepCore.UI
{
public enum DisplayListAreaType : byte
{
None = 0,
Prop = 1,
Weapon = 2
}
public partial class ShopController
{
private async UniTask<ShopContext> BuildContext(ShopRawData rawData)
@@ -30,8 +37,8 @@ namespace SepCore.UI
{
GoodsItems = goodsItems,
PropListContext =
BuildDisplayListAreaContext(DisplayListAreaType.Prop, rawData.PropItems, rawData.PropMaxCount),
WeaponListContext = BuildDisplayListAreaContext(DisplayListAreaType.Weapon, rawData.WeaponItems,
BuildDisplayListContext(DisplayListAreaType.Prop, rawData.PropItems, rawData.PropMaxCount),
WeaponListContext = BuildDisplayListContext(DisplayListAreaType.Weapon, rawData.WeaponItems,
rawData.WeaponMaxCount),
NeedRefreshGoodsItems = true,
NeedRefreshPlayerCoin = true,
@@ -41,7 +48,7 @@ namespace SepCore.UI
};
}
private static DisplayListAreaContext BuildDisplayListAreaContext(DisplayListAreaType listType,
private static DisplayListContext BuildDisplayListContext(DisplayListAreaType listType,
IReadOnlyList<object> items, int maxCount)
{
DisplayItemContext[] itemContexts = new DisplayItemContext[items.Count];
@@ -66,25 +73,14 @@ namespace SepCore.UI
break;
}
return new DisplayListAreaContext
return new DisplayListContext
{
Title = GetDisplayListTitle(listType),
CurrentCount = itemContexts.Length,
MaxCount = maxCount,
ItemContexts = itemContexts
};
}
private static string GetDisplayListTitle(DisplayListAreaType listType)
{
return listType switch
{
DisplayListAreaType.Weapon => "武器",
DisplayListAreaType.Prop => "道具",
_ => string.Empty
};
}
private static DisplayItemContext BuildPropItem(PropItem propItem)
{
return new DisplayItemContext
@@ -105,7 +101,7 @@ namespace SepCore.UI
};
}
private static void AppendDisplayItemContext(DisplayListAreaContext listContext, DisplayItemContext newItem)
private static void AppendDisplayItemContext(DisplayListContext listContext, DisplayItemContext newItem)
{
int oldCount = listContext.ItemContexts.Length;
DisplayItemContext[] newContexts = new DisplayItemContext[oldCount + 1];
@@ -1,115 +1,56 @@
using System.Collections.Generic;
using SepCore.Event;
using TMPro;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace SepCore.UI
{
public class ShopForm : UGuiForm
public partial class ShopForm : UGuiForm
{
#region Property
[SerializeField] private TMP_Text _titleText;
[SerializeField] private TMP_Text _continueButtonText;
[SerializeField] private TMP_Text _refreshPriceText;
[SerializeField] private TMP_Text _playerCoinText;
[SerializeField] private ShopGoodsItem[] _goodsItems;
[SerializeField] private DisplayListArea _propListArea;
[SerializeField] private DisplayListArea _weaponListArea;
private readonly List<ShopGoodsItem> _goodsItems = new List<ShopGoodsItem>();
private ShopContext _context;
#endregion
private bool _listenersBound;
public void RefreshUI(ShopContext context)
{
EnsureButtonListeners();
_context = context;
// 首次打开或需要全量刷新时
if (context.NeedRefreshGoodsItems)
{
_titleText.text = $"商店 (Lv.{context.CurrentLevel})";
_continueButtonText.text = $"继续 (Lv.{context.CurrentLevel + 1})";
View.titleFormatText.Set(context.CurrentLevel);
View.continueButtonFormatText.Set(context.CurrentLevel + 1);
RefreshGoodsItems(context.GoodsItems);
context.NeedRefreshGoodsItems = false;
}
if (context.NeedRefreshPrice)
{
RefreshRefreshPrice(context.RefreshPrice);
View.refreshButtonFormatText.Set(context.RefreshPrice);
context.NeedRefreshPrice = false;
}
if (context.NeedRefreshPlayerCoin)
{
_playerCoinText.text = $"<sprite name=\"coin\"> {context.PlayerCoin}";
View.playerCoinFormatText.Set(context.PlayerCoin);
context.NeedRefreshPlayerCoin = false;
}
if (context.NeedRefreshPropList)
{
_propListArea.OnInit(context.PropListContext);
View.propList.OnInit(context.PropListContext);
context.NeedRefreshPropList = false;
}
if (context.NeedRefreshWeaponList)
{
_weaponListArea.OnInit(context.WeaponListContext);
View.weaponList.OnInit(context.WeaponListContext);
context.NeedRefreshWeaponList = false;
}
}
private void RefreshGoodsItems(List<GoodsItemContext> goodsItems)
{
foreach (var item in _goodsItems)
{
item.gameObject.SetActive(false);
}
int count = Mathf.Min(_goodsItems.Length, goodsItems.Count);
for (int i = 0; i < count; i++)
{
if (goodsItems[i] == null) continue;
_goodsItems[i].Init(goodsItems[i]);
_goodsItems[i].gameObject.SetActive(true);
}
}
private void RefreshRefreshPrice(int refreshPrice)
{
_refreshPriceText.text = $"刷新 -{refreshPrice} <sprite name=\"coin\">";
}
#region ButtonClick
public void OnContinueButtonClick()
{
GameEntry.Event.Fire(this, ShopContinueEventArgs.Create());
}
public void OnPurchaseButtonClick(int index)
{
GameEntry.Event.Fire(this, ShopPurchaseEventArgs.Create(index));
}
public void OnRefreshButtonClick()
{
GameEntry.Event.Fire(this, ShopRefreshEventArgs.Create(_context.RefreshPrice));
}
#endregion
#region FSM
protected override void OnOpen(object userData)
{
base.OnOpen(userData);
@@ -127,12 +68,67 @@ namespace SepCore.UI
{
_context = null;
_propListArea.OnReset();
_weaponListArea.OnReset();
for (int i = 0; i < _goodsItems.Count; i++)
{
_goodsItems[i].OnReset();
}
View.propList.OnReset();
View.weaponList.OnReset();
base.OnClose(isShutdown, userData);
}
#endregion
private void EnsureButtonListeners()
{
if (_listenersBound)
{
return;
}
_listenersBound = true;
View.continueButton.onClick.AddListener(OnContinueButtonClick);
View.refreshButton.onClick.AddListener(OnRefreshButtonClick);
}
private void RefreshGoodsItems(List<GoodsItemContext> goodsItems)
{
EnsureGoodsItemCount(goodsItems.Count);
for (int i = 0; i < _goodsItems.Count; i++)
{
ShopGoodsItem item = _goodsItems[i];
if (i < goodsItems.Count && goodsItems[i] != null)
{
item.Init(goodsItems[i], i);
item.gameObject.SetActive(true);
}
else
{
item.OnReset();
item.gameObject.SetActive(false);
}
}
}
private void EnsureGoodsItemCount(int count)
{
while (_goodsItems.Count < count)
{
ShopGoodsItem item = Instantiate(View.goodsItemTemplate, View.GoodsItemRoot);
_goodsItems.Add(item);
}
}
public void OnContinueButtonClick()
{
GameEntry.Event.Fire(this, ShopContinueEventArgs.Create());
}
public void OnRefreshButtonClick()
{
GameEntry.Event.Fire(this, ShopRefreshEventArgs.Create(_context.RefreshPrice));
}
}
}
@@ -0,0 +1,13 @@
// <auto-generated />
using UnityEngine;
namespace SepCore.UI
{
[DisallowMultipleComponent]
public partial class ShopForm : UGuiForm
{
[SerializeField] private ShopView _view = null;
public ShopView View => _view;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2b261ad08be35724a83c21227db57e6b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,26 +1,32 @@
using System;
using SepCore.Event;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace SepCore.UI
{
public class ShopGoodsItem : MonoBehaviour
{
[SerializeField] private IconArea _iconArea;
[SerializeField] private TMP_Text _titleText;
[SerializeField] private TMP_Text _typeText;
[SerializeField] private TMP_Text _descriptionText;
[SerializeField] private TMP_Text _costText;
#region Init
private int _index = -1;
public void Init(GoodsItemContext data)
private bool _listenersBound;
public void Init(GoodsItemContext data, int index)
{
EnsureListeners();
_index = index;
_iconArea.OnInit(data.Icon, data.Rarity);
_titleText.text = data.Name;
_typeText.text = data.ItemType switch
@@ -34,6 +40,32 @@ namespace SepCore.UI
_costText.text = $"{data.Price} <sprite name=\"coin\" index=0>";
}
#endregion
public void OnReset()
{
_index = -1;
}
private void EnsureListeners()
{
if (_listenersBound)
{
return;
}
_listenersBound = true;
CommonButton button = GetComponentInChildren<CommonButton>(true);
button.onClick.AddListener(OnPurchaseClick);
}
private void OnPurchaseClick()
{
if (_index < 0)
{
return;
}
GameEntry.Event.Fire(this, ShopPurchaseEventArgs.Create(_index));
}
}
}
@@ -0,0 +1,20 @@
// <auto-generated />
using UnityEngine;
namespace SepCore.UI
{
[DisallowMultipleComponent]
public partial class ShopView : MonoBehaviour
{
[SerializeField] public global::SepCore.UI.FormatTextUI titleFormatText;
[SerializeField] public global::SepCore.UI.FormatTextUI playerCoinFormatText;
[SerializeField] public global::SepCore.UI.CommonButton refreshButton;
[SerializeField] public global::SepCore.UI.FormatTextUI refreshButtonFormatText;
[SerializeField] public global::UnityEngine.RectTransform GoodsItemRoot;
[SerializeField] public global::SepCore.UI.ShopGoodsItem goodsItemTemplate;
[SerializeField] public global::SepCore.UI.CommonButton continueButton;
[SerializeField] public global::SepCore.UI.FormatTextUI continueButtonFormatText;
[SerializeField] public global::SepCore.UI.DisplayListForm weaponList;
[SerializeField] public global::SepCore.UI.DisplayListForm propList;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 10a546b2f753668479061813a7f5781f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: