using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using BaseGames.Core.Events;
using BaseGames.Combat;
namespace BaseGames.UI
{
///
/// 伤害飘字(架构 10_UIModule §10)。
/// 从对象池取出后由 FloatingDamageSpawner 调用 Show();协程完成后归还。
/// 挂在预制体根 GameObject 上(Canvas_HUD 子节点,World Space Canvas 或 Screen Space)。
///
public class FloatingDamageText : MonoBehaviour
{
[SerializeField] private TMP_Text _text;
[SerializeField] private float _floatDistance = 1.5f;
[SerializeField] private float _duration = 0.8f;
///
/// 父级 Canvas(用于 RectTransformUtility 坐标转换)。
/// 适配所有 Canvas 渲染模式(Overlay / Camera / World Space),
/// Screen Space - Overlay 时可传 null(会自动 fallback 到 null camera)。
///
[SerializeField] private Canvas _parentCanvas;
private RectTransform _rectTransform;
private Coroutine _animCoroutine;
// 每次 Show() 解析一次,协程期间(< 1s)复用,避免每帧走 FindObjectByTag
private Camera _cachedCamera;
private void Awake()
{
_rectTransform = (RectTransform)transform;
}
///
/// 在世界坐标位置显示伤害数字并开始飘动动画。
/// 动画结束后由对象池回收(Deactivate)。
///
public void Show(Vector2 worldPosition, int damage, DamageType type)
{
if (_animCoroutine != null) StopCoroutine(_animCoroutine);
// 每次 Show 解析一次摄像机:动画时长 < 1s,期间不会切换主摄像机;
// 若 Boss 过场后再次 Show,会自动获取新的主摄像机。
_cachedCamera = (_parentCanvas != null && _parentCanvas.renderMode == RenderMode.ScreenSpaceCamera)
? _parentCanvas.worldCamera
: UnityEngine.Camera.main;
_text.text = damage.ToString();
_text.color = GetColorForType(type);
SetAnchoredPosition(worldPosition);
_animCoroutine = StartCoroutine(FloatAndFade(worldPosition));
}
private void SetAnchoredPosition(Vector2 worldPosition)
{
var cam = _cachedCamera;
var screenPoint = cam != null
? (Vector2)cam.WorldToScreenPoint(worldPosition)
: Vector2.zero;
var canvasRect = _parentCanvas != null
? (RectTransform)_parentCanvas.transform
: null;
if (canvasRect != null)
{
RectTransformUtility.ScreenPointToLocalPointInRectangle(
canvasRect, screenPoint, cam, out var localPoint);
_rectTransform.anchoredPosition = localPoint;
}
else
{
_rectTransform.anchoredPosition = screenPoint;
}
}
private IEnumerator FloatAndFade(Vector2 startWorld)
{
float elapsed = 0f;
var color = _text.color;
var startAlpha = color.a;
while (elapsed < _duration)
{
float t = elapsed / _duration;
SetAnchoredPosition(startWorld + new Vector2(0, _floatDistance * t));
// alpha 淡出(后半段开始)—— 修改 struct 的 a 分量并回写,避免每帧 new Color 堆分配
color.a = Mathf.Lerp(startAlpha, 0f, Mathf.Clamp01((t - 0.5f) / 0.5f));
_text.color = color;
elapsed += Time.deltaTime;
yield return null;
}
gameObject.SetActive(false); // 归还对象池
}
private static Color GetColorForType(DamageType type) => type switch
{
DamageType.Fire => new Color(1f, 0.5f, 0f), // 橙
DamageType.Poison => new Color(0.3f,0.9f, 0.3f), // 绿
DamageType.True => new Color(1f, 0.95f,0.4f), // 黄
DamageType.Ice => new Color(0.5f,0.85f,1f), // 冰蓝
DamageType.Lightning=>new Color(0.9f,0.9f, 0.2f), // 闪黄
_ => Color.white
};
}
// ─────────────────────────────────────────────────────────────────────────
///
/// 伤害飘字生成器(挂在 Canvas_HUD 上)。
/// 订阅 EVT_DamageDealt 事件频道,从 Addressables 池取出 FloatingDamageText 显示。
///
public class FloatingDamageSpawner : MonoBehaviour
{
[Header("事件频道")]
[SerializeField] private DamageInfoEventChannelSO _onDamageDealt;
[Header("预制体(对象池 key = AddressKeys.PrefabUIFloatingDmgText)")]
[SerializeField] private GameObject _floatingDmgPrefab; // Fallback:Inspector 直接拖入
private readonly List _pool = new();
private readonly CompositeDisposable _subs = new();
private void OnEnable() => _onDamageDealt?.Subscribe(OnDamageDealt).AddTo(_subs);
private void OnDisable() => _subs.Clear();
private void OnDamageDealt(DamageInfo info)
{
if (info.FinalDamage <= 0) return;
var text = GetOrCreate();
if (text != null)
text.Show(info.SourcePosition, info.FinalDamage, info.Type);
}
private FloatingDamageText GetOrCreate()
{
// 线性扫描全部已创建实例,找首个未激活的复用
for (int i = 0; i < _pool.Count; i++)
{
var pooled = _pool[i];
if (pooled != null && !pooled.gameObject.activeSelf)
{
pooled.gameObject.SetActive(true);
return pooled;
}
}
// 没有可用实例则实例化新的,加入列表供下次复用
if (_floatingDmgPrefab == null) return null;
var go = Instantiate(_floatingDmgPrefab, transform);
var comp = go.GetComponent();
if (comp != null) _pool.Add(comp);
return comp;
}
}
}