Files
zeling_v2/Assets/_Game/Scripts/UI/HUD/SpellSlotWidget.cs
2026-05-25 11:54:37 +08:00

127 lines
5.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using BaseGames.Core;
using BaseGames.Spells;
namespace BaseGames.UI.HUD
{
/// <summary>
/// 法术槽 HUD 小组件。
///
/// 通过 ServiceLocator 获取 ISpellService显示
/// · 已装备法术图标(无法术时显示空槽占位)
/// · 冷却进度环_cooldownFill.fillAmount = CooldownFraction0 = 冷却完毕1 = 刚施放)
///
/// 图标刺新:订阅 ISpellService.OnSpellChanged 事件,装备切换实时响应(零延迟)。
/// 冷却进度:协程专职追踪,冷却中每帧更新,待机时每 0.1 秒轮询。
///
/// Inspector 必填:
/// _iconImage — 法术图标 Image
/// _cooldownFill — 覆盖在图标上的冷却遮罩 ImagefillMethod = 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);
}
}
}
}