using UnityEngine; using UnityEngine.UI; using TMPro; using BaseGames.Core; using BaseGames.Core.Events; using BaseGames.Equipment; namespace BaseGames.UI { /// /// 工具栏 HUD(架构 09_ProgressionModule §7.5)。 /// 显示 2 个工具槽的图标 + 剩余次数 + 冷却遮罩。 /// public class ToolHUD : MonoBehaviour { [SerializeField] private ToolSlotUI[] _slots; // 2 个 ToolSlotUI 组件 [SerializeField] private ToolUsedEventChannelSO _onToolUsed; // 通过 ServiceLocator 解析:避免 UI 直接持有 ToolSlotManager 具体类型引用 private IToolSlotService _slotManager; private readonly CompositeDisposable _subs = new(); // 每个槽独立的冷却追踪协程;仅在冷却期间运行,避免每帧全槽轮询 private Coroutine[] _cooldownCoroutines; private void Awake() { _cooldownCoroutines = _slots != null ? new Coroutine[_slots.Length] : System.Array.Empty(); } private void OnEnable() { _slotManager = ServiceLocator.GetOrDefault(); _onToolUsed?.Subscribe(RefreshSlot).AddTo(_subs); } private void OnDisable() { _subs.Clear(); StopAllCoroutines(); if (_cooldownCoroutines != null) for (int i = 0; i < _cooldownCoroutines.Length; i++) _cooldownCoroutines[i] = null; } private void RefreshSlot(ToolUsedPayload payload) { int i = payload.SlotIndex; if (_slots == null || i < 0 || i >= _slots.Length) return; if (_slotManager == null) return; // ToolSlotManager 尚未注册 _slots[i].Refresh( _slotManager.GetTool(i), _slotManager.GetRemainingUses(i), _slotManager.GetCooldownRatio(i)); // 启动(或重启)该槽的冷却追踪协程 if (_cooldownCoroutines[i] != null) StopCoroutine(_cooldownCoroutines[i]); _cooldownCoroutines[i] = StartCoroutine(TrackCooldown(i)); } /// /// 仅在该槽冷却未结束时每帧更新填充值;冷却归零后自动停止,不消耗额外 CPU。 /// private System.Collections.IEnumerator TrackCooldown(int slotIndex) { while (_slotManager != null && _slotManager.GetCooldownRatio(slotIndex) > 0f) { _slots[slotIndex].SetCooldownFill(_slotManager.GetCooldownRatio(slotIndex)); yield return null; } // 冷却结束,确保填充归零 if (slotIndex < _slots.Length) _slots[slotIndex].SetCooldownFill(0f); _cooldownCoroutines[slotIndex] = null; } } /// /// 单个工具槽 UI:图标 + 剩余次数文本 + 冷却遮罩 Image(FillAmount)。 /// public class ToolSlotUI : MonoBehaviour { [SerializeField] private Image _icon; [SerializeField] private TMP_Text _usesText; [SerializeField] private Image _cooldownMask; // type = Filled, Image Type = Filled public void Refresh(BaseGames.Equipment.ToolSO tool, int remainingUses, float cooldownRatio) { if (_icon != null) _icon.sprite = tool != null ? tool.icon : null; if (_usesText != null) _usesText.text = remainingUses < 0 ? "∞" : remainingUses.ToString(); SetCooldownFill(cooldownRatio); } public void SetCooldownFill(float ratio) { if (_cooldownMask != null) _cooldownMask.fillAmount = Mathf.Clamp01(ratio); } } }