Unity UGUI实现图文混排组件——EmojiText(支持图标,动态表情,按钮,超链接)

分享下目前项目的图文混排解决方案,供读者参考

  1. 效果
    Unity UGUI实现图文混排组件——EmojiText(支持图标,动态表情,按钮,超链接)_第1张图片
图文混排效果
  1. 混排原理
    图文混排并非自己手动排序,而是利用Text中富文本占位符——让Text自动排序。我们通过占位符的vertex坐标获取占位符的位置信息,之后在占位符范围内创建图片或者动态表情。针对超链接的点击需求,则在文本上添加一张带有点击事件的透明图片(DummyImage),从而实现超链接效果。
    本组件目前四种类型的标签:动态表情,图标,按钮,超链接

  2. 关键步骤
    3.1、 标签信息提取与占位符的填充
    我定义图文混排的标签如下:

    :代表图标资源
    :代表动态表情资源
    :代表按钮资源
    :代表超链接

    占位符需要设置占位符的大小,但对于EmojiText的使用者而言,一般不需要关心图片或者表情的大小,有个满足显示要求的默认大小即可。
    所以我创建一个继承自Text的组件EmojiText,重写Text的SetText方法,把上述自定义的标签信息提取出来,之后把自定义标签转换为占位符,并设置占位符大小,再调用Text的SetText方法,让文本自动排序。项目中我把图片和表情的大小设置为字体大小的1.5倍。

    3.2、获取占位符的位置信息
    通过重写OnPopulateMesh方法,可以获取Text自动布局后每个字符的坐标信息,这时就可以把各个标签页的位置信息提取出来,并存储起来,供后续创建各种资源时使用。
    值得一提的是,占位符不止占位,还会在所占位置上显示一张文本贴图,这显然是我们所不需要的,在OnPopulateMesh方法中把占位符的vertex的uv值设置为0,把文本贴图隐藏。

    3.3、在Update方法中创建标签页的内容
    在OnPopulateMesh过程中创建资源是不被Unity所允许,所以我们只在调用OnPopulateMesh之后设置一个标志位,代表需要创建标签页内容,等执行到Update方法时再创建对应的标签内容,一来解决了上述问题,二来把性能热点错开

        private void Update()
        {
			...
            if (_dirty)
            {
                _dirty = false;
                CreateTag();
            }
        }
        private void CreateTag()
        {
			...
                foreach (TagData tagData in _tagDict.Values)
                {
                    ...
                    switch (tagData.Type)
                    {
                        case TagType.icon:
                            CreateIcon(tagData);
                            break;
                        case TagType.emoji:
                            CreateEmoji(tagData);
                            break;
                        case TagType.button:
                            CreateButton(tagData);
                            break;
                        case TagType.hyperlink:
                            CreateHyperlink(tagData);
                            break;
                    }
                 }
			...
        }
  1. 性能优化
    4.1、string.format优化
    一开始用string.format的用法是
    string.format("{0}", Id)
    这种做法会导致调用时有box操作,修改为下面的方式则可以避免,减少内存分配
    string.format("{0}", Id.ToString())
    参考链接
    4.2、 缓存
    每次OnPopulateMesh都会引起标签页内容的重新创建,所以缓存创建对象必不可少,而且缓存是基于所有EmojiText创建对象的缓存,而不止针对单个EmojiText所创建的对象,会有更高的复用率
    4.3、简化正则表达式
    最开始我定义的标签是,后来改为,正则表达式从"]+)>“改为”",效率有所提升
    4.4、SetActive优化
    众所周知,Unity中SetActive是相当耗费性能的,所以对于复用率较高的Image预设(图标),Button预设(按钮)和DummyImage预设(超链接),进入缓存池不选择调用SetActive,而是选择设置它的坐标到屏幕之外,对于复用率较低的表情,还是会选择把它隐藏(我不确定不隐藏会有哪些额外的消耗)
    4.5、List缓存池
    参考UI底层的ListPool,缓存List结构,避免List多次创建
用LScrollView滚动时的性能曲线
  1. 后续优化方向
    5.1、目前如果两次赋值的text一致我并没有过滤掉,后续可考虑相同文本不处理,但可能EmojiText的使用限制也会有所提升
    5.2、目前一个表情一张贴图,这是项目的历史遗留问题,后续考虑把所有表情贴图合并,减少DrawCall数量
    5.3、新版本的Unity可以考虑用TextMeshPro。目前这种用占位符的方式顶点数太多,是个硬伤。

  2. github地址 Unity版本5.6.6

  3. 参考资料
    https://blog.csdn.net/column/details/13021.html
    https://blog.uwa4d.com/archives/Sparkle_UGUI.html
    https://www.cnblogs.com/zsb517/p/6667050.html

你可能感兴趣的:(unity)