【优化笔记】UGUI控件优化

  • Text组件

    优点:

    1. 容易使用,不必进行繁多设置
    2. 动态字体,较为灵活

    缺点:

    1. 组件变化会引起重建
    2. 组件启用禁用或父节点启用禁用都会引起重建
    3. text中的字符是按一个个面片来渲染的,面片周围有大量空白区域,容易造成图形重叠从而降低批处理效率。
    4. 字体放大后容易产生模糊
    5. 动态字体集实际上可能产生很大的内存消耗,因为需要为每种不同字体维护一个字形。
  • TextMeshPro字体集

    Unity中引入了文字插件TextMeshPro,很好地解决了Text带来的诸多问题,但自身也有一些不便的地方

    优点:

    1. 使用SDF算法,字体可以通过矢量计算进行放大缩小,而不产生模糊
    2. 通过更改材质来制作多种效果字体,包括具有描边的效果,避免了使用OutLine组件进行描边带来的大量性能消耗
    3. 另外可以通过更改材质制作多种效果的美术字,包括带光照效果,能够大大减少美术同学的工作量,同时使得文字的丰富程度大大提高
    4. 可以直接通过富文本标签的方式进行图文混排,图片的大小间距等均可以自定义设置,这让许多UI界面上图文混排的效果更容易实现
    5. 对于文本框的范围有多种适应模式
    6. 对于某些预言来说,只需要打出很小的字体图集就能满足需求,例如英文只需要26个大小写字母加10个阿拉伯数字加上常用标点符号即可

    缺点:

    1. 默认不支持中文,如果需要引入中文需要自行打出中文字体集,而中文的常用文字数量繁多,一张文字贴图就能达到30M左右,这仅仅是一种字体
    2. 针对每种字体必须分别打出一张字体图集,因此如果一个项目使用多种类型字体和艺术字那需要维护的字体和图集很多,不易管理。
    3. 插件本身附带多种设置文件格式文件,要用好需要花费点时间进行学习。

    解决方案

    1. 当全部可现实字符集很大或者在运行时期不确定时,可以用动态字体来显示文本。
      在Unity的实现中,这些字体在运行时根据Text组件中出现的字符构建一个字形图集(glyph atlas),动态字体为每种不同的结合(尺寸、样式&字符)在其纹理集中维护了一个字形。
      当使用动态字体的Text对象遇到了没有被栅格化到字体纹理集中的字形时,必须重建字体纹理集。
      这通过两步完成:

      1. 以相同的大小重建图集,只使用当前在活动的Text组件上显示的字形,这包含了父画布活动(active)但是禁用了CanvasRenderer的Text组件。如果系统成功地将当前使用的所有字形填充进新的图集中,将会栅格化此图集,不再继续进行2。 
      2. 如果当前使用的字形不能填充进同样大小的图集中,那么会以当前图集大小的短维乘2来创建一个更大的图集。例如,一个512x512的图集或被扩充到512x1024的图集。
      
    2. 对于字体比较固定,样式固定,用到的文字较少时,使用TMP_Text最佳。在内存占用很小的情况下,能够轻易制作出丰富的文字效果。

  • Layout
    常用布局组件有GridLayoutGroup,HorizontalLayoutGroup,VerticalLayoutGroup
    问题:
    Layout组件的性能开销比较大,因为每当它们的子元素被标记为脏元素时它都要重新计算子元素的尺寸和位置
    解决方案:

    1. 使用基于RectTransform位置计算的脚本代替Unity自身组件,例如在TQ1.0中使用到的ListPool,动态创建列表。不足之处是无法在编辑器模式下进行动态布局,但因为不会时刻去检测调整每个子对象的位置,因此也能节省一部分开销,子对象的变动也不会使所有其它子对象dirty
    2. 重写这部分源码,主要改写逻辑,在子对象相对静态的情况下不去dirty整个布局。
  • RectMask2D与Mask
    Mask是比较通用的裁剪组件,需要一个Image组件,其子对象都可以根据image的形状进行裁剪。

    优点:

    1. 可剪裁的形状根据image变化,且可以用于3D对象,通用性较强。

    缺点:

    1. 会额外增加一个dc,同时可能打断子对象中的合批额外产生dc
    2. 会产生OverDraw,因为剪裁范围下依旧会进行绘制,只是蒙版对比不过不会写入buffer而已。

    RectMask2D
    本身不必依赖于任何其它组件,特别是图形组件,只能剪裁矩形

    优点:

    1. 不产生额外dc

    缺点:

    1. 只能剪裁矩形,因为是根据自身rect的大小来进行剪裁的。
    2. 对于只有一半处于剪裁区域的对象无法剪裁,如拖拽一半到剪裁范围外时无法被剪裁。

    解决方案:

    1. 在2D中使用剪裁遮罩时,优先使用RectMask2D,在RectMask2D无法满足需求(缺点中1、2)的情况使用Mask
    2. 另外可以根据继承image或重新编写shader降低Mask可能产生的重绘开销,后面再继续研究。
  • 避免使用UI/Effect特别是Outline

    问题:

    1. Outline的描边效果是多次重复去绘制一个文字,这很容易打断合批
      使用TMP_Text的Outline材质替代可解决
  • ScrollView
    ScrollView通常伴随着大量填充的内容使用,如:几十上百项的列表,或长篇文本图片的混排,这样可能造成大量渲染浪费和内存占用,以及加载时的卡顿问题。

    优化:
    为ScrollView添加RectMask2D组件还是能够起到改善作用。这一组件能够确保ScrollView中位于显示区域之外的那些元素不在可绘制元素列表中,这样在重建画布时系统就不必对这些元素进行几何生成、排序和分析。

    对于创建大量子项的情况,有两种基本方式可以用来填充ScrollView:

    1. 将所有要在ScrollView中显示的内容一次性填充进ScrollView中
    2. 缓存要显示的元素,并按需重新设置元素位置来显示它们

    在第一种方式中,随着元素数量的增长,实例化元素所需的时间会增加,从而造成卡顿,重建ScrollView所需的时间也会增加。

    优化:
    可以通过分时加载,分摊实例化时可能造成的卡顿,但无法避免重建的时间增加,以及内存占用增大。

    第二种方式在当前UI和布局系统中实现起来需要编写相当多的代码来检测当前滑动位置,加载首尾的对象,更新对象的信息,重置多余对象的位置等

    优化:
    一般来说,为复杂的滚动UI编写存取池能够帮助避免性能问题。

    1. 简单ScrollView存取池
      核心思路:使用带有LayoutElement组件的GameObject作为占位符,而在滑动滑调时,将滚动到视图中的这些占位符设为UI的父节点,达到重用的效果。
      优点:大大减少必要的CanvasRenderer,只是增加RectTransform而已
      缺点:任何被重新设置父节点或者改变顺序(与同级兄弟相比)的UI元素和这个元素的所有子元素都会被标记为脏元素,并且会强制重建它们的画布。因此会增加重建画布的频率。

    2. 基于位置的ScrollView池
      为避免上述问题,可以简单地通过移动ScrollView中的UI元素的RectTransform来创建对象池。这样做之后,如果被移动的RectTransform的尺寸没有发生变化,就不需要重建其内容,可以显著提升性能。

    要实现这一功能,最好编写一个自定义的ScrollView子类或者自定义的LayoutGroup组件。后者实现起来更加简单,而且可以通过实现Unity UI系统的[LayoutGroup][1]抽象类来完成。
    在自定义的LayoutGroup中可以对底层数据进行分析,来判断有多少数据元素必选显示和如何对ScrollView Content的RectTransform进行适当的缩放。可以通过订阅ScrollRect.onValueChanged事件来按需重新设置可见元素的位置。

  • Graphic

    1. 不需要触发事件监听的图形类控件,主要是image和text,的Raycast Target,就设置未false,因为事件监测时会去遍历所有开启Raycast Target的对象,这样无效的监测对象过多会浪费计算开销。
    2. 对于需要隐藏的图形控件,可以设置其canvasRenderer.cull为true,这样不必SetActive来隐藏显示控件,造成每次OnEnable时的重建开销。
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页