【unity】TextMeshPro文本抖动效果

发布时间 2023-06-14 17:46:19作者: AshScops

文本抖动效果

前言

在部分电子游戏中,当角色处于狂喜、紧张或恐惧等激动情绪时,角色对话框中的文字会触发抖动等效果,这为游戏增色不少,如下。

image

当我在网上查找相关资料时,没找到相关的实现,也可能是我搜索的关键词不对。

总之今天来实现一下这个效果。

实现思路

目标效果是:在同一帧的动效中,文本中的每个字符各自随机方向偏移了一小段距离。

传统的Text组件我不清楚能不能成,但是TextMeshPro一定能成,因为TextMeshPro中的文本渲染是基于Mesh的,只要能拿到每个字符对应的Mesh顶点数据,就能单独给每个字符设置位置、颜色等数据。如果不明白的话去补一下Mesh相关知识就好。

实现思路如下:

  1. 获取TextMeshPro中的顶点数组和每个字符。
  2. 遍历每个字符,对每一个字符,都随机生成偏移量,并将该偏移量应用在这个字符对应的所有顶点数据上。
  3. 更新组件渲染。

可能的困难

我们需要在同一帧内随机生成各个字符的偏移,这些偏移要保证各不相同。

在同一帧中,除非改变传入的minmax的值,否则无论调用多少次,引擎中的Random.Range(min, max) 的返回值都会是同一个数。

解决方案:基于内存地址,使用C#System.Random,详情见->Unity并发取随机导致相同解决方法

代码实现

using System;
using System.Collections;
using System.Runtime.InteropServices;
using TMPro;
using UnityEngine;

public class FontSingleBeat : MonoBehaviour
{
    TextMeshProUGUI text;
    /// <summary>
    /// 速度(时间间隔)
    /// </summary>
    public float shakeSpeed = 0.05f;

    /// <summary>
    /// 幅度
    /// </summary>
    public float shakeAmount = 1f;

    private Vector3[] m_rawVertex;

    private void Awake()
    {
        text = this.GetComponent<TextMeshProUGUI>();
        text.ForceMeshUpdate();
        
    }

    private void Start()
    {
        GetRawVertex();
        StartCoroutine(ShakeText());
    }

    private void GetRawVertex()
    {
        if(text.textInfo.characterCount > 0)
        {
            TMP_CharacterInfo charInfo = text.textInfo.characterInfo[0];
            TMP_MeshInfo meshInfo = text.textInfo.meshInfo[charInfo.materialReferenceIndex];
            //创建对象来保存初始值
            m_rawVertex = new Vector3[meshInfo.vertices.Length];
            for (int i = 0; i < meshInfo.vertices.Length; i++)
            {
                m_rawVertex[i] = new Vector3(meshInfo.vertices[i].x, meshInfo.vertices[i].y, meshInfo.vertices[i].z);
            }
        }
        else
        {
            Debug.LogError("GetRawVertex Failed.");
        }
    }

    IEnumerator ShakeText()
    {
        while (true)
        {
            for (int i = 0; i < text.textInfo.characterCount; i++)
            {
                // 获取字符信息和MeshInfo
                TMP_CharacterInfo currentCharInfo = text.textInfo.characterInfo[i];
                TMP_MeshInfo meshInfo = text.textInfo.meshInfo[currentCharInfo.materialReferenceIndex];
                int vertexCount;
                if (i < text.textInfo.characterCount - 1)
                {
                    TMP_CharacterInfo nextCharInfo = text.textInfo.characterInfo[i + 1];
                    vertexCount = nextCharInfo.vertexIndex - currentCharInfo.vertexIndex;
                }
                else
                {
                    vertexCount = meshInfo.vertices.Length - currentCharInfo.vertexIndex;
                }

                // 获取起始顶点索引
                int vertexIndex = currentCharInfo.vertexIndex;

                // 随机生成位移量
                int mult = 100;
                float xOffset = GetRandom((int)-shakeAmount * mult, (int)shakeAmount * mult, i);
                float yOffset = GetRandom((int)-shakeAmount * mult, (int)shakeAmount * mult, i + text.textInfo.characterCount);
                Vector3 offset = new Vector3(xOffset, yOffset) / 100f;
                //print(xOffset + ", " + yOffset);

                // 顶点偏移
                Vector3[] vertices = meshInfo.vertices;
                for (int j = vertexIndex; j < vertexIndex + vertexCount; j++)
                {
                    vertices[j] = m_rawVertex[j] + offset;
                }

                // 刷新单个字符
                //text.SetVerticesDirty();
                //text.SetMaterialDirty();
            }

            text.UpdateVertexData();
            yield return new WaitForSeconds(shakeSpeed);
        }
    }


    /// <summary>
    /// 获取基地址的
    /// </summary>
    /// <param name="o"></param>
    /// <returns></returns>
    public int GetMemory(object o)
    {
        GCHandle h = GCHandle.Alloc(o, GCHandleType.WeakTrackResurrection);
        IntPtr addr = GCHandle.ToIntPtr(h);
        return int.Parse(addr.ToString());
    }

    /// <summary>
    /// 产生随机数
    /// 调用它就可以产生你要的随机数了,如果有需求可以自己重载
    /// </summary>
    /// <param name="min">最小值</param>
    /// <param name="Max">最大值</param>
    /// <returns></returns>
    public float GetRandom(int min, int Max, int iSeed)
    {
        System.Random rd = new System.Random(GetMemory(iSeed));
        return (rd.Next(min, Max));
    }
}

最终效果

上面一排是目标效果;下面一排是文本整体偏移抖动。

image

参考资料

Unity并发取随机导致相同解决方法_fairen的博客-CSDN博客