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

相关推荐
辻弋2012 天前
五年前的旅行视频糊成马赛克?Video2X用Real-ESRGAN逐帧重建细节,但只支持Windows、集显用户建议直接放弃
服务器·数据库·windows·游戏引擎·电脑
Martina_03213 天前
AI生成3D场景导入 Unity/Unreal 后,NPC 总串层?用6步检查导航高度与跨层连接
人工智能·游戏·数学建模·3d·unity·自然语言处理·aigc
小贺儿开发3 天前
Unity 局域网遥控幻灯片展示工具 3.0
科技·http·unity·工具·ppt·通信·网页
WarPigs5 天前
Unity对象不应该使用null传播的问题
unity
每日出拳老爷子6 天前
【VR】【Unity】抓走转放系列21|一天搭完门和抽屉工具槽:小场景按这个次序拼装
unity·vr·驱动·排错·snap zone
淡海水6 天前
02-02-原理篇-分代GC
算法·unity·c#·游戏引擎·.net·gc
小贺儿开发6 天前
Unity 文物新生 会讲故事的文物展墙
人工智能·科技·unity·ai·视频·互动·演示
淡海水7 天前
02-03-原理篇-大对象堆
unity·c#·游戏引擎·.net·gc
WarPigs7 天前
Unity在Create菜单添加创建自定义脚本模板选项
unity
郝学胜-神的一滴8 天前
C++ Templates 01:拨开迷雾,读懂C++模板经典著作
开发语言·c++·程序人生·游戏·游戏引擎·软件构建