基于TextMeshPro实现打印机效果

目标

屏幕上逐字显示文本的打印机效果,使用的显示文本控件为TextMeshPro和TextMeshProUGUI,动态效果如下
基于TextMeshPro实现打印机效果

实现方式

基本思路

一种最简单的想法就是,将文本拆分,依次传入给TextMesh组件。但这种方式需要产生多个substring,给后续GC带来压力,且每次对text赋值就需要重新计算文字的网格位置。既然原字符串显示的时候已经计算过了网格,那么就可以考虑利用这份网格数据,依次输出给renderer来实现字符依次显示。

具体实现

如图,每个字符都由4个顶点组成构成,只要想办法控制绘制顶点的数量,就能控制显示文字的长度。
基于TextMeshPro实现打印机效果

在TextMeshPro中,要显示的文字和网格信息都存储在textInfo这个变量中,变量的类型为TMP_TextInfo。TMP_TextInfo类型中,meshInfo缓存了文字网格数据,可以修改该变量中的内容,之后调用UpdateVertexData方法,即可将网格数据传递给相应的Renderer中。所以要做的就是在合适的时机修改meshInfo中的数值,主要实现代码如下

    IEnumerator PlayPrinterEffect(TMPro.TMP_Text textComponent, float speed)
    {
        if (textComponent == null)
        {
            Debug.LogError("Input component is null!!!");
            yield break;
        }
        if (speed <= 0)
        {
            Debug.LogError("Input speed should be larger than 0!!!");
            speed = 1;
        }

        TMPro.TMP_TextInfo textInfo = textComponent.textInfo;

        // 开始播放打印机效果,首先清空所有文字网格
        for (int i = 0; i < textInfo.materialCount; ++i)
        {
            textInfo.meshInfo[i].Clear();
        }
        textComponent.UpdateVertexData(TMPro.TMP_VertexDataUpdateFlags.Vertices);

        float timer = 0;
        float duration = 1 / speed;
        for (int i = 0; i < textInfo.characterCount; ++i)
        {
            while (timer < duration)
            {
                yield return null;
                timer += Time.deltaTime;
            }

            timer -= duration;

            TMPro.TMP_CharacterInfo characterInfo = textInfo.characterInfo[i];

            int materialIndex = characterInfo.materialReferenceIndex;
            int verticeIndex = characterInfo.vertexIndex;
            if(characterInfo.elementType == TMPro.TMP_TextElementType.Sprite)
            {
                verticeIndex = characterInfo.spriteIndex;
            }
            if (characterInfo.isVisible)
            {
                textInfo.meshInfo[materialIndex].vertices[0 + verticeIndex] = characterInfo.vertex_BL.position;
                textInfo.meshInfo[materialIndex].vertices[1 + verticeIndex] = characterInfo.vertex_TL.position;
                textInfo.meshInfo[materialIndex].vertices[2 + verticeIndex] = characterInfo.vertex_TR.position;
                textInfo.meshInfo[materialIndex].vertices[3 + verticeIndex] = characterInfo.vertex_BR.position;
                textComponent.UpdateVertexData(TMPro.TMP_VertexDataUpdateFlags.Vertices);
            }

        }
    }

每次TextMesh属性改变的时候,都会对显示文字重新计算位置,每个字符的显示信息通过结构体TMP_CharacterInfo来保存。因此在清除所有网格数据后,定时从该结构体中获取网格数据重新构建网格,来得到每个字符依次显示的动画效果。

注意点

在改变text后立即调用上述方法无法正常播放动效,因为尚未执行文字网格的重新计算,获得的characterInfo仍然是改变text之前的数据。可通过调用TextMesh的ForceMeshUpdate方法强制进行网格更新,在进行动效的播放。

优势

1、不用重新生成多个子字符串,也不会触发TextMesh对文字网格重新计算,减少多个string的GC和网格计算的消耗
2、可以轻松处理富文本,因为富文本携带的信息已经被转化为characterInfo中的数据存储好了

缺陷

1、对于fontStyle设置了下划线,删除线等效果暂时无法在动效中复现(目前暂未找到下划线和删除线的网格生成方式)
2、GeometrySorting设置为Reverse时,动效显示将出问题。(GeometrySorting的值影响网格数据的顶点排序,Reverse为反向保存的,即顶点数组从最后一个字符开始保存顶点坐标。目前还未研究清楚这种方式有何作用,暂时没想到合适的处理方式)

参考资料

TextMeshPro源码。源码可在本地的unity的packages文件夹下找到,下面是我本地保存的位置
C:\Users\Administrator\AppData\Local\Unity\cache\packages\packages.unity.com\com.unity.textmeshpro@3.0.0-preview.1

上一篇:Palindrome-(Manacher算法)


下一篇:1745. Palindrome Partitioning IV (回文树)