[Unity] UGUI拓展 - 动画控制富文本异色部分的Alpha

1、问题背景

  今天接到一个表现上的需求:在有淡出动画的奖励提示上,异色标记稀有道具的名称
  本来是一个挺简单的功能,在提示文字中找出道具名的位置,然后在两端插入UGUI的<color>标签。测试的时候却发现,淡出过程中异色部分的透明度没有发生变化。

Alpha不变的情况

  在项目组询问一番,有大佬已经写脚本处理了这个问题。怀着不断造轮子的心态,对同事的脚本进行了改写(当然改写的脚本没有放入项目),改写的目的有两个:

  • 用自己的命名习惯书写
  • 改善下性能
改进后的效果

2、浅析

  简单分析(cai)下问题的原因。
  首先,UGUI改变color属性,是修改顶点色,而不是修改材质的属性。因此可以出现同一段文字,颜色(包含Alpha)不同的情况。
  然后,文字异色是通过<color>标签进行标记的,它使用的是一个8位十六进制数表示,顺序分别是RGBA。第一张图中虽然我用的是6位的#00ff00,但Unity内部应该会把它补成8位的#00ff00ff。(具体实现没细究,大概是取不到Alpha位就默认不透明吧)
  那么问题就能猜到了,淡出动画仅仅是修改了color属性,文本中的<color>标签没有任何变化,Unity依旧使用标签的信息去填充顶点色。解决方案也是针对<color>标签进行处理的。

3、完整代码

[ExecuteInEditMode]
public class RichTextAlphaUpdater : MonoBehaviour
{
    public Text Txt;

    /// <summary>
    /// 匹配颜色值
    /// </summary>
    public static readonly Regex RichColorReg = new Regex("<color=#([a-f0-9]{8})>", RegexOptions.IgnoreCase);
    public const int ColorMax = 255;
    
    private UnityAction _vertDirtyAction;
    private UnityAction VertDirtyAction
    {
        get
        {
            if (null == _vertDirtyAction)
            {
                _vertDirtyAction = _OnVertDirty;
            }
            return _vertDirtyAction;
        }
    }

    /// <summary>
    /// 文字顶点变化的事件
    /// </summary>
    private void _OnVertDirty()
    {
        string alpha = _GetHexAlpha();
        string txt = Txt.text;
        Match match = RichColorReg.Match(txt);
        Group group = null;
        while (match.Success)
        {
            group = match.Groups[1];
            _ReplaceAlpha(txt, group.Index, alpha);
            match = match.NextMatch();
        }
    }

    /// <summary>
    /// 缓存数据,降低处理频率
    /// </summary>
    private int _prevAlpha = 0;
    private string _hexAlpha = null;

    /// <summary>
    /// 获取当前Alpha的Hex值
    /// </summary>
    private string _GetHexAlpha()
    {
        int alpha = Mathf.Clamp((int) (Txt.color.a * ColorMax), 0, ColorMax);
        if (null != _hexAlpha && alpha == _prevAlpha)
        {
            return _hexAlpha;
        }

        string hexAlpha = Convert.ToString(alpha, 16);
        if (hexAlpha.Length == 1)
        {
            return "0" + hexAlpha;
        }
        return hexAlpha;
    }

    private void _ReplaceAlpha(string txt, int colorIdx, string alpha)
    {
        unsafe
        {
            fixed (char* hexPtr = txt)
            {
                hexPtr[colorIdx + 6] = alpha[0];
                hexPtr[colorIdx + 7] = alpha[1];
            }
        }
    }

    void OnEnable()
    {
        if (null == Txt)
        {
            Txt = GetComponent<Text>();
        }
        if (null != Txt)
        {
            Txt.RegisterDirtyVerticesCallback(VertDirtyAction);
        }
    }

    void OnDisable()
    {
        if(null == Txt) return;
        Txt.UnregisterDirtyVerticesCallback(VertDirtyAction);
    }
}
  • 使用:
    1)把脚本挂到要控制的Text组件上
    2)脚本挂到任意激活的GameObject上,自己关联Text组件

4、知识点

代码虽然简单,但也有几个小点值得记录备忘。

1)Text重建回调
  • Text提供了RegisterDirtyVerticesCallbackRegisterDirtyMaterialCallbackRegisterDirtyLayoutCallback等几个回调,让开发者可以在重建的时候做些事情
  • 回调执行后重建不是马上(同一帧)进行的,这里只是通知开发者,组件被加入了相应的Change List
  • 在回调中做引发重建的处理,会陷入死循环
    同事的方案中,是通过【取消 - 再注册】的方式避免死循环的,针对类似的情况应该是挺好的处理方法。
    我的方案可以不考虑死循环,因为是直接修改的string对象,不会触发重建。
2)Unity中使用指针

  为了减少字符串操作(减少GC),我尝试使用指针进行字符替换,然后得到了喜人的结果,性能和GC都有所提高~

  • 获取指针需要用fixed域固定内存的位置,仅使用unsafe是不够的
  • 为了让Unity能够编译unsafe代码,要在工程中加入一个smcs.rsp文件,里面仅写入-unsafe,并重启Unity!!

  这里有个小抉择,本来为了使用的时候方便,想支持6位色值的。写完指针方案后,我放弃了6位色值。因为它无法通过一对一的char替换完成,需要插入内容,那么GC就无法避免了。

3)正则表达式
  • 这套方案并不是无GC的,我在Editor中测试,一段简单的文字(十来个有用字符)动画过程中每帧也有1.4K左右的GC产生。虽没细测,但基本可以确定这部分开销是正则产生的。好吃易上火啊Orm
  • 遍历正则的匹配结果,可以用Matches()+Index或Match()+NextMatch(),测试发现,后者比前者产生的GC少0.1K。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,527评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,314评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,535评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,006评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,961评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,220评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,664评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,351评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,481评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,397评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,443评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,123评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,713评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,801评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,010评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,494评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,075评论 2 341

推荐阅读更多精彩内容

  • 111. [动画系统]如何将其他类型的动画转换成关键帧动画? 动画->点缓存->关键帧 112. [动画]Unit...
    胤醚貔貅阅读 12,914评论 3 90
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,285评论 25 707
  • 1. [C#语言基础]请简述拆箱和装箱。 答: 装箱操作: 值类型隐式转换为object类型或由此值类型实现的任何...
    胤醚貔貅阅读 4,757评论 1 29
  • 近日2个美国脑残青年在自己家后院做了一台暂时简称为“高达”的“机器人”,这可能是美国民间第一个根据电影动画等素材设...
    脑洞科技阅读 680评论 0 48
  • 女儿是父母的贴心小棉袄,以至于我家重女轻男,从来都是弟弟去干活,我和妹妹休息中。今天看到果汁的留言,不自觉心都化了...
    臻静阅读 337评论 0 3