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

相关推荐
天人合一peng4 小时前
unity 生成标记根据背景色变色为明显的颜色
unity·游戏引擎
魔士于安4 小时前
Unity 超市总动员 超市收银台 超市货架 超市购物手推车 超市常见商品
游戏·unity·游戏引擎·贴图·模型
CandyU24 小时前
Unity —— 数据持久化
unity·游戏引擎
zh路西法4 小时前
【Unity实现Oneshot胶卷显形】游戏窗口化与Win32API的使用
游戏·unity·游戏引擎
迪捷软件5 小时前
显控系统虚拟仿真的工程化路径
游戏引擎·cocos2d
凡情9 小时前
android隐私合规检测
android·unity
小贺儿开发9 小时前
Unity3D 本地 Stable Diffusion 文生图效果演示
人工智能·unity·stable diffusion·文生图·ai绘画·本地化
Swift社区10 小时前
传统游戏引擎 vs 鸿蒙 System 架构
架构·游戏引擎·harmonyos
mxwin1 天前
Unity Shader 半透明物体为什么不能写入深度缓冲?
unity·游戏引擎·shader
晚枫歌F1 天前
三层时间轮的实现
网络·unity·游戏引擎