UI系统优化

This commit is contained in:
2026-05-25 11:54:37 +08:00
parent c7057db27d
commit 3c812cfb41
130 changed files with 4738 additions and 477 deletions

View File

@@ -0,0 +1,233 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using UnityEngine.EventSystems;
using BaseGames.Core;
using BaseGames.Core.Events;
using BaseGames.Equipment;
using BaseGames.Localization;
namespace BaseGames.UI
{
/// <summary>
/// 护符装备面板。
///
/// 布局:
/// 顶部 — 标题 + 凹槽容量进度条(已用/总数)
/// 左侧 — 已装备护符区域(动态格子列表)
/// 右侧 — 已收集护符目录(可滚动,点击即装备/卸下)
///
/// 数据来源ServiceLocator 获取 EquipmentManager读取 Collected / Equipped 列表。
/// 反应式更新:订阅 _onEquipmentChanged VoidEventChannelSO变更时重建两侧列表。
///
/// Inspector 必填:
/// _notchText — "X / Y 格子" 文本
/// _equippedContainer — 已装备列表父节点
/// _catalogContainer — 收藏目录列表父节点
/// _charmCardTemplate — 护符卡片预制kept inactive
/// _btnClose — 关闭按钮
/// _onEquipmentChanged — EquipmentManager 广播的装备变更事件频道
/// </summary>
public class CharmEquipPanel : MonoBehaviour, IFocusable
{
// ── Inspector 字段 ────────────────────────────────────────────────────
[Header("UI 根节点")]
[SerializeField] private TMP_Text _notchText;
[SerializeField] private Image _notchBarFill;
[SerializeField] private Transform _equippedContainer;
[SerializeField] private Transform _catalogContainer;
[SerializeField] private GameObject _charmCardTemplate; // kept inactive
[SerializeField] private Button _btnClose;
[Header("Event Channels")]
[SerializeField] private VoidEventChannelSO _onEquipmentChanged;
// ── 私有状态 ──────────────────────────────────────────────────────────
private IEquipmentService _manager;
private readonly List<GameObject> _equippedCards = new();
private readonly List<GameObject> _catalogCards = new();
private readonly Queue<GameObject> _cardPool = new(); // O(1) 取用归还
private readonly CompositeDisposable _subs = new();
// ── 生命周期 ──────────────────────────────────────────────────────────
private void OnEnable()
{
_manager = ServiceLocator.GetOrDefault<IEquipmentService>();
if (_btnClose != null)
{
_btnClose.onClick.RemoveAllListeners();
_btnClose.onClick.AddListener(OnCloseBtnClicked);
}
_onEquipmentChanged?.Subscribe(Rebuild).AddTo(_subs);
Rebuild();
// 手柄导航:面板打开时将焦点置于关闭按钮
EventSystem.current?.SetSelectedGameObject(_btnClose?.gameObject);
}
private void OnDisable()
{
_subs.Clear();
RecycleCards(_equippedCards);
RecycleCards(_catalogCards);
}
// ── 重建 UI ───────────────────────────────────────────────────────────
private void Rebuild()
{
if (_manager == null) return;
RefreshNotchBar();
RebuildEquippedList();
RebuildCatalogList();
}
private void RefreshNotchBar()
{
int used = _manager.UsedNotches;
int total = _manager.TotalNotches;
if (_notchText != null)
_notchText.text = $"{used} / {total}";
if (_notchBarFill != null)
_notchBarFill.fillAmount = total > 0 ? (float)used / total : 0f;
}
private void RebuildEquippedList()
{
RecycleCards(_equippedCards);
if (_equippedContainer == null) return;
foreach (var charm in _manager.Equipped)
{
var card = SpawnCard(_equippedContainer, charm, isEquipped: true);
if (card != null) _equippedCards.Add(card);
}
}
private void RebuildCatalogList()
{
RecycleCards(_catalogCards);
if (_catalogContainer == null) return;
foreach (var charm in _manager.Collected)
{
bool isEquipped = _manager.Equipped.Contains(charm);
var card = SpawnCard(_catalogContainer, charm, isEquipped);
if (card != null) _catalogCards.Add(card);
}
}
// ── 卡片生成 ──────────────────────────────────────────────────────────
private GameObject SpawnCard(Transform parent, CharmSO charm, bool isEquipped)
{
if (_charmCardTemplate == null || parent == null) return null;
if (!TryDequeueCard(out GameObject go))
go = Instantiate(_charmCardTemplate);
go.transform.SetParent(parent, worldPositionStays: false);
go.SetActive(true);
// Icon第一个 Image
var images = go.GetComponentsInChildren<Image>(includeInactive: true);
if (images.Length > 0 && charm.icon != null)
images[0].sprite = charm.icon;
// 文本约定TMP_Text[0] = 名称, TMP_Text[1] = 凹槽消耗, TMP_Text[2] = 描述)
var texts = go.GetComponentsInChildren<TMP_Text>(includeInactive: true);
if (texts.Length > 0)
{
string name = LocalizationManager.Get(charm.displayNameKey, LocalizationTable.Items);
texts[0].text = string.IsNullOrEmpty(name) || name == charm.displayNameKey
? charm.charmId : name;
}
if (texts.Length > 1)
texts[1].text = charm.notchCost.ToString();
if (texts.Length > 2)
{
string desc = LocalizationManager.Get(charm.descriptionKey, LocalizationTable.Items);
texts[2].text = string.IsNullOrEmpty(desc) || desc == charm.descriptionKey
? string.Empty : desc;
}
// 按钮(约定:第一个 Button = 装备/卸下切换)
var btn = go.GetComponentInChildren<Button>(includeInactive: true);
if (btn != null)
{
btn.onClick.RemoveAllListeners();
CharmSO captured = charm;
bool equip = !isEquipped;
if (equip)
{
btn.onClick.AddListener(() =>
{
string err = _manager?.TryEquipCharm(captured);
if (err != null)
Debug.LogWarning($"[CharmEquipPanel] 装备失败: {err}");
});
}
else
{
btn.onClick.AddListener(() => _manager?.UnequipCharm(captured));
}
// 已装备时使用不同视觉(可选:标记文本)
if (texts.Length > 3)
texts[3].text = isEquipped ? "✓" : string.Empty;
}
return go;
}
// ── 回收 ─────────────────────────────────────────────────────────────
/// <summary>取队列头第一个非空实例;队列为空时返回 false。</summary>
private bool TryDequeueCard(out GameObject go)
{
while (_cardPool.Count > 0)
{
go = _cardPool.Dequeue();
if (go != null) return true;
}
go = null;
return false;
}
private void RecycleCards(List<GameObject> cards)
{
foreach (var card in cards)
{
if (card == null) continue;
card.SetActive(false);
_cardPool.Enqueue(card);
}
cards.Clear();
}
private void OnCloseBtnClicked()
{
var uiMgr = ServiceLocator.GetOrDefault<IUIManager>();
if (uiMgr != null) uiMgr.CloseTopPanel();
else gameObject.SetActive(false);
}
// ── IFocusable ────────────────────────────────────────────────────────
/// <summary>面板恢复为栈顶时将焦点移回关闭按鈕。</summary>
public void OnFocusRestored()
=> EventSystem.current?.SetSelectedGameObject(_btnClose?.gameObject);
}
}