127 lines
5.2 KiB
C#
127 lines
5.2 KiB
C#
using System.Collections;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using BaseGames.Core;
|
||
using BaseGames.Spells;
|
||
|
||
namespace BaseGames.UI.HUD
|
||
{
|
||
/// <summary>
|
||
/// 法术槽 HUD 小组件。
|
||
///
|
||
/// 通过 ServiceLocator 获取 ISpellService,显示:
|
||
/// · 已装备法术图标(无法术时显示空槽占位)
|
||
/// · 冷却进度环(_cooldownFill.fillAmount = CooldownFraction,0 = 冷却完毕,1 = 刚施放)
|
||
///
|
||
/// 图标刺新:订阅 ISpellService.OnSpellChanged 事件,装备切换实时响应(零延迟)。
|
||
/// 冷却进度:协程专职追踪,冷却中每帧更新,待机时每 0.1 秒轮询。
|
||
///
|
||
/// Inspector 必填:
|
||
/// _iconImage — 法术图标 Image
|
||
/// _cooldownFill — 覆盖在图标上的冷却遮罩 Image(fillMethod = Radial360 推荐)
|
||
/// _emptySlot — 无法术时显示的占位对象
|
||
/// </summary>
|
||
public class SpellSlotWidget : MonoBehaviour
|
||
{
|
||
// ── Inspector 字段 ────────────────────────────────────────────────────
|
||
|
||
[SerializeField] private Image _iconImage;
|
||
[Tooltip("冷却进度覆盖图(填充方式建议设为 Radial360 + 顺时针)。")]
|
||
[SerializeField] private Image _cooldownFill;
|
||
[Tooltip("无法术装备时显示的空槽占位对象。")]
|
||
[SerializeField] private GameObject _emptySlot;
|
||
|
||
// ── 私有状态 ──────────────────────────────────────────────────────────
|
||
|
||
private ISpellService _spellService;
|
||
private SpellSO _cachedSpell; // 延迟绑定期间班本记录,避免重复 SetSprite
|
||
|
||
// ── 生命周期 ──────────────────────────────────────────────────────────
|
||
|
||
private void OnEnable()
|
||
{
|
||
_cachedSpell = null;
|
||
StartCoroutine(TrackSpell());
|
||
}
|
||
|
||
private void OnDisable()
|
||
{
|
||
StopAllCoroutines();
|
||
if (_spellService != null)
|
||
{
|
||
_spellService.OnSpellChanged -= RefreshIcon;
|
||
_spellService = null;
|
||
}
|
||
_cachedSpell = null;
|
||
}
|
||
|
||
// ── 协程 ─────────────────────────────────────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 阶段一:延迟绑定——等到 ISpellService 就绪,订阅图标事件并同步初始状态。
|
||
/// 阶段二:冷却追踪——事件驱动图标(零延迟),协程专职更新进度环。
|
||
/// </summary>
|
||
private IEnumerator TrackSpell()
|
||
{
|
||
var pollWait = new WaitForSeconds(0.1f);
|
||
|
||
// 阶段一:延迟绑定(SpellManager 可能晚于此组件初始化)
|
||
while (_spellService == null)
|
||
{
|
||
_spellService = ServiceLocator.GetOrDefault<ISpellService>();
|
||
if (_spellService == null)
|
||
{
|
||
ShowEmpty();
|
||
yield return pollWait;
|
||
continue;
|
||
}
|
||
// 服务就绪:订阅装备变更事件(零延迟图标刷新)+ 同步当前状态
|
||
_spellService.OnSpellChanged += RefreshIcon;
|
||
RefreshIcon(_spellService.EquippedSpell);
|
||
}
|
||
|
||
// 阶段二:冷却进度追踪(图标已由事件驱动,协程仅负责冷却环)
|
||
while (true)
|
||
{
|
||
float f = _spellService.CooldownFraction;
|
||
if (_cooldownFill != null) _cooldownFill.fillAmount = f;
|
||
yield return f > 0f ? null : (object)pollWait;
|
||
}
|
||
}
|
||
|
||
// ── 视觉刷新 ──────────────────────────────────────────────────────────
|
||
|
||
private void RefreshIcon(SpellSO spell)
|
||
{
|
||
_cachedSpell = spell;
|
||
bool hasSpell = spell != null;
|
||
|
||
if (_emptySlot != null)
|
||
_emptySlot.SetActive(!hasSpell);
|
||
|
||
if (_iconImage != null)
|
||
{
|
||
_iconImage.gameObject.SetActive(hasSpell);
|
||
if (hasSpell) _iconImage.sprite = spell.icon;
|
||
}
|
||
|
||
if (_cooldownFill != null)
|
||
{
|
||
_cooldownFill.fillAmount = 0f;
|
||
_cooldownFill.gameObject.SetActive(hasSpell);
|
||
}
|
||
}
|
||
|
||
private void ShowEmpty()
|
||
{
|
||
if (_emptySlot != null) _emptySlot.SetActive(true);
|
||
if (_iconImage != null) _iconImage.gameObject.SetActive(false);
|
||
if (_cooldownFill != null)
|
||
{
|
||
_cooldownFill.fillAmount = 0f;
|
||
_cooldownFill.gameObject.SetActive(false);
|
||
}
|
||
}
|
||
}
|
||
}
|