Files
zeling_v2/Assets/Scripts/UI/FloatingDamageText.cs
2026-05-12 15:34:08 +08:00

141 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 System.Collections.Generic;
using UnityEngine;
using TMPro;
using BaseGames.Core.Events;
using BaseGames.Combat;
namespace BaseGames.UI
{
/// <summary>
/// 伤害飘字(架构 10_UIModule §10
/// 从对象池取出后由 FloatingDamageSpawner 调用 Show();协程完成后归还。
/// 挂在预制体根 GameObject 上Canvas_HUD 子节点World Space Canvas 或 Screen Space
/// </summary>
public class FloatingDamageText : MonoBehaviour
{
[SerializeField] private TMP_Text _text;
[SerializeField] private float _floatDistance = 1.5f;
[SerializeField] private float _duration = 0.8f;
private UnityEngine.Camera _cam;
private RectTransform _rectTransform;
private Coroutine _animCoroutine;
private void Awake()
{
_rectTransform = (RectTransform)transform;
_cam = UnityEngine.Camera.main;
}
/// <summary>
/// 在世界坐标位置显示伤害数字并开始飘动动画。
/// 动画结束后由对象池回收Deactivate
/// </summary>
public void Show(Vector2 worldPosition, int damage, DamageType type)
{
if (_animCoroutine != null) StopCoroutine(_animCoroutine);
_text.text = damage.ToString();
_text.color = GetColorForType(type);
// 将世界坐标转为屏幕坐标
if (_cam != null)
{
Vector2 screenPos = _cam.WorldToScreenPoint(worldPosition);
_rectTransform.anchoredPosition = screenPos;
}
_animCoroutine = StartCoroutine(FloatAndFade(worldPosition));
}
private IEnumerator FloatAndFade(Vector2 startWorld)
{
float elapsed = 0f;
var color = _text.color;
var startAlpha = color.a;
while (elapsed < _duration)
{
float t = elapsed / _duration;
// 向上飘动(屏幕坐标)
Vector2 currentWorld = startWorld + new Vector2(0, _floatDistance * t);
if (_cam != null)
_rectTransform.anchoredPosition = (Vector2)_cam.WorldToScreenPoint(currentWorld);
// alpha 淡出(后半段开始)
_text.color = new Color(color.r, color.g, color.b,
Mathf.Lerp(startAlpha, 0f, Mathf.Clamp01((t - 0.5f) / 0.5f)));
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
};
}
// ─────────────────────────────────────────────────────────────────────────
/// <summary>
/// 伤害飘字生成器(挂在 Canvas_HUD 上)。
/// 订阅 EVT_DamageDealt 事件频道,从 Addressables 池取出 FloatingDamageText 显示。
/// </summary>
public class FloatingDamageSpawner : MonoBehaviour
{
[Header("事件频道")]
[SerializeField] private DamageInfoEventChannelSO _onDamageDealt;
[Header("预制体(对象池 key = AddressKeys.PrefabUIFloatingDmgText")]
[SerializeField] private GameObject _floatingDmgPrefab; // FallbackInspector 直接拖入
private readonly Queue<FloatingDamageText> _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()
{
// 从池中找到已停用的实例
while (_pool.Count > 0)
{
var pooled = _pool.Dequeue();
if (pooled == null) continue;
if (!pooled.gameObject.activeSelf)
{
pooled.gameObject.SetActive(true);
return pooled;
}
_pool.Enqueue(pooled); // 仍在使用,放回
break;
}
// 没有可用实例则实例化
if (_floatingDmgPrefab == null) return null;
var go = Instantiate(_floatingDmgPrefab, transform);
var comp = go.GetComponent<FloatingDamageText>();
if (comp != null) _pool.Enqueue(comp);
return comp;
}
}
}