动画控制富文本异色部分的Alpha

转载自: https://blog.csdn.net/weixin_34060299/article/details/87588865


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

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

  • 用自己的命名习惯书写
  • 改善下性能
5829570-896bd7f887a547a5.gif
改进后的效果

2、浅析

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

3、完整代码


  
  
  1. [ ExecuteInEditMode]
  2. public class RichTextAlphaUpdater : MonoBehaviour
  3. {
  4. public Text Txt;
  5. /// <summary>
  6. /// 匹配颜色值
  7. /// </summary>
  8. public static readonly Regex RichColorReg = new Regex( "<color=#([a-f0-9]{8})>", RegexOptions.IgnoreCase);
  9. public const int ColorMax = 255;
  10. private UnityAction _vertDirtyAction;
  11. private UnityAction VertDirtyAction
  12. {
  13. get
  14. {
  15. if ( null == _vertDirtyAction)
  16. {
  17. _vertDirtyAction = _OnVertDirty;
  18. }
  19. return _vertDirtyAction;
  20. }
  21. }
  22. /// <summary>
  23. /// 文字顶点变化的事件
  24. /// </summary>
  25. private void _OnVertDirty()
  26. {
  27. string alpha = _GetHexAlpha();
  28. string txt = Txt.text;
  29. Match match = RichColorReg.Match(txt);
  30. Group group = null;
  31. while (match.Success)
  32. {
  33. group = match.Groups[ 1];
  34. _ReplaceAlpha(txt, group.Index, alpha);
  35. match = match.NextMatch();
  36. }
  37. }
  38. /// <summary>
  39. /// 缓存数据,降低处理频率
  40. /// </summary>
  41. private int _prevAlpha = 0;
  42. private string _hexAlpha = null;
  43. /// <summary>
  44. /// 获取当前Alpha的Hex值
  45. /// </summary>
  46. private string _GetHexAlpha()
  47. {
  48. int alpha = Mathf.Clamp(( int) (Txt.color.a * ColorMax), 0, ColorMax);
  49. if ( null != _hexAlpha && alpha == _prevAlpha)
  50. {
  51. return _hexAlpha;
  52. }
  53. string hexAlpha = Convert.ToString(alpha, 16);
  54. if (hexAlpha.Length == 1)
  55. {
  56. return "0" + hexAlpha;
  57. }
  58. return hexAlpha;
  59. }
  60. private void _ReplaceAlpha( string txt, int colorIdx, string alpha)
  61. {
  62. unsafe
  63. {
  64. fixed ( char* hexPtr = txt)
  65. {
  66. hexPtr[colorIdx + 6] = alpha[ 0];
  67. hexPtr[colorIdx + 7] = alpha[ 1];
  68. }
  69. }
  70. }
  71. void OnEnable()
  72. {
  73. if ( null == Txt)
  74. {
  75. Txt = GetComponent<Text>();
  76. }
  77. if ( null != Txt)
  78. {
  79. Txt.RegisterDirtyVerticesCallback(VertDirtyAction);
  80. }
  81. }
  82. void OnDisable()
  83. {
  84. if( null == Txt) return;
  85. Txt.UnregisterDirtyVerticesCallback(VertDirtyAction);
  86. }
  87. }
  • 使用:
    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。
今天接到一个表现上的需求: 在有淡出动画的奖励提示上,异色标记稀有道具的名称
  本来是一个挺简单的功能,在提示文字中找出道具名的位置,然后在两端插入UGUI的<color>标签。测试的时候却发现,淡出过程中异色部分的透明度没有发生变化。
5829570-6d994cb83f5cdac1.gif
Alpha不变的情况

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

猜你喜欢

转载自blog.csdn.net/cui6864520fei000/article/details/88945869