Unity 实现伤害跳字

核心组件:

Dotween TextMeshPro

过程轨迹如下图:

代码如下:

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using TMPro;
using UnityEngine;
using UnityEngine.Pool;

public class DamageTextController : MonoBehaviour
{
    [Header("配置参数")]
    public GameObject textPrefab;
    public int poolSize = 20;
    public float floatHeight = 2f;
    public float duration = 1f;

    public GameObject bossGame;

    [Header("颜色配置")]
    public Color[] damageColors = { Color.black, Color.red, Color.green,
                                   Color.blue, Color.yellow, Color.magenta };
    public int maxDamageThreshold = 1000;

    [Header("字体大小")]
    public float minFontSize = 20f;
    public float maxFontSize = 40f;

    [Header("偏移配置")]
    // 垂直偏移
    public float maxVerticalOffset = 20f;

    private ObjectPool<TextMeshProUGUI> pool; // 替换原有队列

    private Camera mainCamera;

    private void Awake()
    {
        // 安全获取相机引用
        if (!mainCamera) mainCamera = Camera.main;

        // 初始化对象池
        pool = new ObjectPool<TextMeshProUGUI>(
            createFunc: () => 
            {
                var obj = Instantiate(textPrefab, transform);
                return obj.GetComponent<TextMeshProUGUI>();
            },
            actionOnGet: (text) => 
            {
                text.gameObject.SetActive(true);
                text.transform.localPosition = Vector3.zero;
            },
            actionOnRelease: (text) => text.gameObject.SetActive(false),
            actionOnDestroy: (text) => Destroy(text.gameObject),
            defaultCapacity: poolSize
        );

        // 预创建对象
        var preload = new List<TextMeshProUGUI>();
        for (int i = 0; i < poolSize; i++)
        {
            preload.Add(pool.Get());
        }

        foreach (var item in preload)
        {
            pool.Release(item);
        }
    }


    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            ShowDamage(bossGame.transform.position + Vector3.up, Random.Range(100, maxDamageThreshold));
        }
    }

    // 获取可用文字对象
    private TextMeshProUGUI GetTextObject()
    {
        return pool.Get(); // 简化获取逻辑
    }


    // 显示伤害文字(世界坐标版本)
    public void ShowDamage(Vector3 worldPosition, int damage)
    {
        var text = GetTextObject();
        // 添加空引用保护
        if (!mainCamera) return;
        text.transform.position = mainCamera.WorldToScreenPoint(worldPosition);

        // 提取颜色计算逻辑到独立方法
        text.color = CalculateDamageColor(damage);
        // 提取字体大小计算到独立方法
        text.fontSize = CalculateFontSize(damage);

        text.text = damage.ToString();

        StartCoroutine(PlayAnimation(text));
    }

    private Color CalculateDamageColor(int damage)
    {
        float ratio = Mathf.Clamp01((float)damage / maxDamageThreshold);
        // 修改索引计算方式,使最后一个颜色可以被访问到
        int index = Mathf.FloorToInt(ratio * damageColors.Length);
        index = Mathf.Clamp(index, 0, damageColors.Length - 1);
        return new Color(damageColors[index].r, damageColors[index].g, damageColors[index].b, 1f);
    }

    private float CalculateFontSize(int damage)
    {
        float ratio = Mathf.Clamp01((float)damage / maxDamageThreshold);
        return Mathf.Lerp(minFontSize, maxFontSize, ratio);
    }

    // 动画协程
    private IEnumerator PlayAnimation(TextMeshProUGUI text)
    {
        // 重置文本状态
        text.alpha = 1f;
        text.transform.localScale = Vector3.one;

        text.gameObject.SetActive(true);

        // ==== 出现阶段 (0.2秒) ====
        text.color = new Color(text.color.r, text.color.g, text.color.b, 0); // 初始透明
        Vector3 originalPos = text.transform.position;

        // 初始状态设置
        text.transform.localScale = Vector3.one * 0.2f;
        text.transform.position += Vector3.up * 50f; // 初始位置上方50像素

        // 第一阶段动画:淡入 + 放大 + 下落准备
        var phase1 = DOTween.Sequence()
            .Join(text.DOFade(1, 0.2f).SetEase(Ease.OutQuad))
            .Join(text.transform.DOScale(1f, 0.2f).SetEase(Ease.OutBack))
            .Join(text.transform.DOMoveY(originalPos.y + 30f, 0.2f));

        // ==== 显示阶段 (0.3秒) ====
        var phase2 = text.transform.DOMoveY(originalPos.y - 30f, 0.3f)
            .SetEase(Ease.Linear);

        // ==== 结束阶段 (0.3秒) ====
        var phase3 = DOTween.Sequence()
            .Append(text.transform.DOMoveY(originalPos.y - maxVerticalOffset, 0.3f).SetEase(Ease.InQuad))
            .Join(text.DOFade(0, 0.3f))
            .Join(text.transform.DOScale(0.5f, 0.3f));

        // 组合完整动画
        var fullSequence = DOTween.Sequence()
            .Append(phase1)
            .Append(phase2)
            .Append(phase3);

        yield return fullSequence.WaitForCompletion();

        // 在回收前重置属性
        text.alpha = 1f;
        text.transform.localScale = Vector3.one;
        // 修改回收部分
        text.gameObject.SetActive(false);
        pool.Release(text); // 使用对象池的Release方法
    }

    private void OnDestroy()
    {
        pool.Clear(); // 确保销毁时清理对象池
    }
}

使用TMP后期可以无缝切换位图字体,TMP可以直接制作,非常方便。

相关推荐
Sator13 小时前
Unity的FishNet相关知识
网络·unity·游戏引擎
AllBlue3 小时前
安卓调用unity中的方法
android·unity·游戏引擎
李岱诚4 小时前
epic商城下载,ue4报错处理
游戏引擎·ue4
jtymyxmz19 小时前
《Unity Shader》10.1.4 折射
unity·游戏引擎
在路上看风景19 小时前
12. Burst
unity
平行云PVT21 小时前
实时云渲染解决UE5 像素流插件迁移及传输数据受限问题
unity·ue5·xr·实时云渲染·云桌面·像素流·云推流
熬夜敲代码的小N1 天前
Unity WebRequest高级操作:构建高效稳定的网络通信模块
android·数据结构·unity·游戏引擎
萘柰奈1 天前
Unity【小问题】----URP项目中加载AssetBundle中的预设体即使加载了依赖的材质依然是紫色的问题
unity·游戏引擎·材质
wonder135791 天前
UGUI合批分析和优化方法整理
unity·ugui
海中有金1 天前
Unreal Engine 线程模型深度解析[2]
人工智能·游戏引擎·虚幻