目标
屏幕上逐字显示文本的打印机效果,使用的显示文本控件为TextMeshPro和TextMeshProUGUI,动态效果如下
实现方式
基本思路
一种最简单的想法就是,将文本拆分,依次传入给TextMesh组件。但这种方式需要产生多个substring,给后续GC带来压力,且每次对text赋值就需要重新计算文字的网格位置。既然原字符串显示的时候已经计算过了网格,那么就可以考虑利用这份网格数据,依次输出给renderer来实现字符依次显示。
具体实现
如图,每个字符都由4个顶点组成构成,只要想办法控制绘制顶点的数量,就能控制显示文字的长度。
在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