using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using BaseGames.Core.Events;
using BaseGames.Combat.StatusEffects;
using StatusEffectType = BaseGames.Combat.StatusEffects.StatusEffectType;
namespace BaseGames.UI.HUD
{
///
/// 状态效果 HUD —— 在 HUD 上为玩家展示当前激活的状态效果图标、叠层数和倒计时。
///
/// 布局约定:挂在 HUD Canvas 内,持有一个 _slotTemplate(默认 inactive)和一个 _container。
/// 每种 StatusEffectType 最多对应一个槽位实例,通过字典管理。
///
/// 订阅的事件频道:
/// _onStatusEffectApplied → OnStatusEffectApplied
/// _onStatusEffectExpired → OnStatusEffectExpired
///
public class StatusEffectHUD : MonoBehaviour
{
// ── Inspector 字段 ────────────────────────────────────────────────────
[Header("Slot")]
[Tooltip("槽位模板预制(设为 inactive);每种效果类型会从此 Instantiate 一个实例。")]
[SerializeField] private GameObject _slotTemplate;
[Tooltip("槽位实例的父节点(水平/垂直 LayoutGroup 即可)。")]
[SerializeField] private Transform _container;
[Header("效果图标配置")]
[SerializeField] private SlotConfig[] _slotConfigs;
[Header("Event Channels")]
[SerializeField] private StatusEffectEventChannelSO _onStatusEffectApplied;
[SerializeField] private StatusEffectEventChannelSO _onStatusEffectExpired;
// ── 内部数据 ──────────────────────────────────────────────────────────
/// 每种 StatusEffectType 对应的 Inspector 配置(图标 Sprite)。
[System.Serializable]
public struct SlotConfig
{
public StatusEffectType effectType;
[Tooltip("该状态效果在 HUD 上显示的图标。")]
public Sprite icon;
}
private class SlotInstance
{
public GameObject root;
public Image iconImage;
public TMP_Text durationText; // 倒计时文本(可为 null)
public TMP_Text stackText; // 叠层数文本(可为 null)
public Coroutine countdown;
}
private readonly Dictionary _activeSlots = new();
private readonly CompositeDisposable _subs = new();
// 倒计时节流:缓存 WaitForSecondsRealtime 以避免每帧堆分配
private readonly WaitForSecondsRealtime _countdownTick = new(0.1f);
// 图标快速查找表: Awake 时一次性从 _slotConfigs 构建,避免 OnStatusEffectApplied 高频线性扫描
private Dictionary _iconLookup;
// ── 生命周期 ───────────────────────────────────────────────────────
private void Awake()
{
_iconLookup = new Dictionary(_slotConfigs?.Length ?? 0);
if (_slotConfigs != null)
foreach (var cfg in _slotConfigs)
_iconLookup[cfg.effectType] = cfg.icon;
}
private void OnEnable()
{
_onStatusEffectApplied?.Subscribe(OnStatusEffectApplied).AddTo(_subs);
_onStatusEffectExpired?.Subscribe(OnStatusEffectExpired).AddTo(_subs);
}
private void OnDisable()
{
_subs.Clear();
StopAllCoroutines();
foreach (var slot in _activeSlots.Values)
if (slot.root != null) slot.root.SetActive(false);
_activeSlots.Clear();
}
// ── 事件处理 ──────────────────────────────────────────────────────────
private void OnStatusEffectApplied(StatusEffectEvent e)
{
if (!_activeSlots.TryGetValue(e.EffectType, out var slot))
{
slot = CreateSlot(e.EffectType);
if (slot == null) return;
_activeSlots[e.EffectType] = slot;
}
slot.root.SetActive(true);
RefreshSlotDisplay(slot, e.StackCount, e.RemainingDuration);
// 重启倒计时协程(OnStack 刷新持续时间时也会走到这里)
if (slot.countdown != null)
StopCoroutine(slot.countdown);
slot.countdown = StartCoroutine(CountdownRoutine(slot, e.RemainingDuration));
}
private void OnStatusEffectExpired(StatusEffectEvent e)
{
if (!_activeSlots.TryGetValue(e.EffectType, out var slot)) return;
if (slot.countdown != null)
{
StopCoroutine(slot.countdown);
slot.countdown = null;
}
slot.root.SetActive(false);
}
// ── 协程 ─────────────────────────────────────────────────────────────
private IEnumerator CountdownRoutine(SlotInstance slot, float totalDuration)
{
float endTime = Time.realtimeSinceStartup + totalDuration;
while (Time.realtimeSinceStartup < endTime)
{
float remaining = endTime - Time.realtimeSinceStartup;
if (slot.durationText != null)
slot.durationText.text = remaining.ToString("F1");
yield return _countdownTick; // 0.1s 节忍,替代每帧 yield return null
}
slot.countdown = null;
slot.root.SetActive(false);
}
// ── 内部辅助 ──────────────────────────────────────────────────────────
private void RefreshSlotDisplay(SlotInstance slot, int stackCount, float duration)
{
if (slot.durationText != null)
slot.durationText.text = duration.ToString("F1");
bool multiStack = stackCount > 1;
if (slot.stackText != null)
{
slot.stackText.gameObject.SetActive(multiStack);
if (multiStack) slot.stackText.text = stackCount.ToString();
}
}
private SlotInstance CreateSlot(StatusEffectType type)
{
if (_slotTemplate == null || _container == null) return null;
_iconLookup.TryGetValue(type, out Sprite icon); // O(1) 查找
var go = Instantiate(_slotTemplate, _container);
go.SetActive(false);
var slot = new SlotInstance { root = go };
// 约定:第一个 Image 子节点作为图标;第一个 TMP_Text 作为倒计时;第二个作为叠层数
var images = go.GetComponentsInChildren(includeInactive: true);
if (images.Length > 0)
{
slot.iconImage = images[0];
if (icon != null) slot.iconImage.sprite = icon;
}
var texts = go.GetComponentsInChildren(includeInactive: true);
if (texts.Length > 0) slot.durationText = texts[0];
if (texts.Length > 1) slot.stackText = texts[1];
return slot;
}
}
}