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可以直接制作,非常方便。

相关推荐
qq_428639612 小时前
虚幻基础:碰撞&帧运算
游戏引擎·虚幻
Qiao胖胖5 小时前
unity曲线射击
unity·游戏引擎
JuicyActiveGilbert6 小时前
【C++游戏引擎开发】第9篇:数学计算库GLM(线性代数)、CGAL(几何计算)的安装与使用指南
c++·线性代数·游戏引擎
虾球xz8 小时前
游戏引擎学习第217天
c++·学习·游戏引擎
Lareina~9 小时前
【元表 vs 元方法】
unity·lua
虾球xz12 小时前
游戏引擎学习第215天
网络·学习·游戏引擎
Clank的游戏栈13 小时前
Unity IL2CPP内存泄漏追踪方案(基于Memory Profiler)技术详解
unity·游戏引擎
归海_一刀15 小时前
Unity跨平台输入系统
unity·游戏引擎·输入系统
虾球xz15 小时前
游戏引擎学习第197天
java·学习·游戏引擎
向宇it15 小时前
【unity游戏开发入门到精通——动画篇】Animator反向动力学(IK)
开发语言·unity·c#·编辑器·游戏引擎