Files
zeling_v2/Assets/_Game/Scripts/UI/FloatingDamageText.cs
Joywayer b7baf7ad6a Add WeaponFeedback component and AddressableManagerWindow meta file
- Implemented WeaponFeedback class for handling weapon-related feedbacks such as hit effects and attack sounds.
- Added meta file for AddressableManagerWindow to manage addressable assets.
- Included a new jump.data file for profiler data.
2026-05-22 22:03:32 +08:00

166 lines
6.3 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;
/// <summary>
/// 父级 Canvas用于 RectTransformUtility 坐标转换)。
/// 适配所有 Canvas 渲染模式Overlay / Camera / World Space
/// Screen Space - Overlay 时可传 null会自动 fallback 到 null camera
/// </summary>
[SerializeField] private Canvas _parentCanvas;
private RectTransform _rectTransform;
private Coroutine _animCoroutine;
// 每次 Show() 解析一次,协程期间(< 1s复用避免每帧走 FindObjectByTag
private Camera _cachedCamera;
private void Awake()
{
_rectTransform = (RectTransform)transform;
}
/// <summary>
/// 在世界坐标位置显示伤害数字并开始飘动动画。
/// 动画结束后由对象池回收Deactivate
/// </summary>
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 淡出(后半段开始)
_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 List<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()
{
// 线性扫描全部已创建实例,找首个未激活的复用
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<FloatingDamageText>();
if (comp != null) _pool.Add(comp);
return comp;
}
}
}