FishPlayer

一个喜欢摸鱼的废物

0%

TextMeshPro 富文文本自定义颜色

TextMeshPro 支持富文本,富文本有颜色标签。对于一些武器/技能描述里的关键字,在文本表里我们都会用 <color=#FFFFFF> 这样的标记去给文本换色。
但是这样会有一些比较麻烦的问题,比如当美术希望全部把颜色换掉时,这再去表格里做替换就比较麻烦了,而且也无法区分两种用途不同但是数值一样的颜色。

于是乎交互和策划那边提来了需求希望能在富文本里使用自定义的颜色tag,为了方便做效果的测试和迭代。

当前我们已经在使用自定义的继承TMP的文本组件了,达成这个功能就会比较简单,只需要对TMP的代码做一些小修改然后再根据他的规则续写一下我们的自定义的颜色获取就好了。

TextMeshPro 源码获取

现在我们都是从 PackageManager 中下载的 TMP,一般是无法直接修改的。需要按照以下步骤才能修改:

  • PROJECT_ROOT/Library/PackageCache/ 中找到 TMP 的包,名字应该是 com.unity.textmeshpro@x.x.x
  • 把整个包拖到 PROJECT_ROOT/Packages/ 这个目录下,重启 Unity 工程
  • PROJECT_ROOT/Packages/com.unity.textmeshpro@x.x.x/ 的目录下随意修改源码

修改思路

TMP_Asset.cs 这个文件有差不多9000行,非常庞大。其中有一大部分是关于富文本标签的解析的。

我看到的大概的文本渲染的流程大概是这样的:

  • 文本赋值(从属性或面板上赋值)
  • 标记需要重建网格
  • (重建网格) 使用一个 uint 数组备份当前输入的文本。如果文本长于数组,那扩容到目标长度,如果文本短于数组,空位填0。
  • (重建网格) 准备一个 UnicodeChar(编码,原长度等)。如果文本长于数组,那扩容到2的n次幂,如果文本短于数组,空位填0。
  • (重建网格) 填充 UnicodeChar 数组,缓存准备操作的文本(会区分换行符和不同字符以及 br, style 标签)。
  • (重建网格) 遍历 UnicodeChar 数组以构建网格。
  • (重建网格) (遍历 UnicodeChar 数组) 检查每一个 UnicodeChar,如果是可以直接展示的字符,则根据当前的富文本标签区间和其它显示相关的参数来给 m_textInfo 里的 characterInfo(缓存需要绘制的字符的网格相关的信息) 数组中的元素赋值。
  • (重建网格) (遍历 UnicodeChar 数组) 检查每一个 UnicodeChar,根据组件上的大小写风格定义来换算当前实际需要使用的 char UnicodeChar。
  • (重建网格) (遍历 UnicodeChar 数组) 根据实际使用的 char code 去查询字形(如果是动态的贴图,查不到的会创建并写入贴图)。
  • (重建网格) (遍历 UnicodeChar 数组) 根据各种效果处理后的结果,更新m_textInfo 里的 characterInfo(缓存需要绘制的字符的网格相关的信息) 数组中的元素。
  • (重建网格) 根据整体的对齐模式计算整体的偏移,之后用于重新更新m_textInfo 里的 characterInfo。
  • (重建网格) 接下来还有一系列处理我头大了看不下去了 :)

整个流程很长我没看完,但还是看到了我需要的关键部分。internal bool ValidateHtmlTag(UnicodeChar[] chars, int startIndex, out int endIndex) 这个函数中就有富文本标签的解析而且正好有根据颜色标签来计算颜色的部分。再这个部分(TMP_Text.cs line 7766)中就正好是根据颜色名字来赋颜色,在这里我们加上用于解析自定义颜色的函数就可以了

TextMeshPro 超链接点击

这个功能也是我最开始提到的对于武器/技能描述里的关键字会需要的一个功能,点击超链接文本打开某某界面展示下详情。配合 TMP 也非常好好完成。
需要一个响应点击的脚本挂在有 TMP 的物体上或者直接继承 TMP 然后也继承响应点击的这个接口。

我们可以明确得到点击的富文本链接内容,需要打开界面的情况就自己切割参数解析即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

public class TempTMP : TextMeshProUGUI, IPointerClickHandler
{
public void OnPointerClick(PointerEventData eventData)
{
int linkIndex = TMP_TextUtilities.FindIntersectingLink(this, eventData.position, eventData.pressEventCamera);
if (linkIndex != -1)
{
/*
* e.g. <link="a2,b2">Mesh</link>
* linkInfo.GetLinkID() will "get a2,b2"
* linkInfo.GetLinkText() will get "Mesh"
*/
TMP_LinkInfo linkInfo = this.textInfo.linkInfo[linkIndex];
Debug.Log($"linkId {linkInfo.GetLinkID()} / linkText {linkInfo.GetLinkText()}");
// Do stuff u want here :D
}
}
}