优化UI控件 【译】

翻译自:https://unity3d.com/cn/learn/tutorials/topics/best-practices/optimizing-ui-controls?playlist=30089html

这一章节关注一些特定的UI控件。大部分UI控件在性能方面是类似的,其中有两个控件在游戏快完成时,可能会遇到许多性能问题。算法

UI text

Unity 内置的Text组件能够很方便的将光栅化的文本符号显示到UI中。然而又不少不被知道的问题,会频繁的产生性能开销点。当你像UI中添加文本时,请时刻记住每个文字的文本符号都是一个独立的四边形。根据形状的不一样,这些四边形可能的周围可能会包围着不少透明区域,并且很容易放置text组件,致使阻断其余能够合批的UI元素。canvas

Text 网格重建

一个很严重的问题就是UI text的重建。不管什么时候,当UI Text组件发生变化时,text 组件会从新计算实际显示的多边形。当一个文本的父级GameObject只是简单的disable & re-enabled,并无改变文本时,也会致使从新计算。api

这种行为对于那些显示大量文字标签的UI如排行榜和统计界面来讲,是有问题的。最多见的方式去隐藏和显示Unity UI就是enable/disable一个包含了UI的GameObject,当显示包含大量Text组件的文本时,常常会出现的意外的帧率峰值。缓存

有关此问题的解决方法,请参阅下一章的 Disabling Canvas Renderers 章节。布局

动态字体和字体图集

显示拥有大量可显示文字的字库,或者运行时没法预测的文本时,使用动态字体是一种很是便利的方式。在Unity中,这些字体根据UIText组件运行时使用到的字符,动态的建立一个字符图集。性能

每一个加载的字体对象都包含一个本身的纹理图集,即便它和另外一个字体有相同的font family。好比,在一个控件上使用Arial粗体,在另外一个控件上使用Arial Bold字体,它们的表现是相同的,可是Unity会保留两张独立的纹理贴图——一张给Arial字体使用,一张给Arial Bold字体使用。测试

Unity UI的动态字体会在字体的纹理图集中,每个大小,风格不一样的文字都会保留一个字符(glyph )。这意味着,若是一个UI包含两个Text组件,两个都显示字符‘A’,那么:字体

  • 若是两个文本的字体大小相同,那么在字体图集中只会有一个字符(glyph)。
  • 若是两个文本的字体大小不一样(如一个16-point,另外一个24-point),那么字体图集会包含字符‘A’的两个不一样大小的拷贝。
  • 若是一个文本是粗体,另外一个不是,那么字符图集中将包含一个粗体的‘A’和一个正常的‘A’。

当一个UI Text对象遇到一个尚未被字体图集光栅化的字符,那么字体图集必须重建。若是新的字符适合放入到当前的图集,它将被加入到图集中,图集会从新传递给绘制设备。然而,若是当前的图集很小,那么系统会尝试去重建图集。它分两个阶段进行。动画

第一步,图集会被重建成相同大小,它只包含active的UI Text组件(1)上显示的字符。若是系统成功的将全部当前使用的字符放到一个新的图集中,那么它将光栅化这个图集,而且跳过第二步。

第二步,若是当前使用的字符集合不能被放到和当前图集相同大小的图集中,一个是图集短边长度两倍的图集将被建立。好比512 * 215图集扩展到512 * 1024大小的图集。

基于上述算法,一个被建立的动态字体图集只会在大小上改变。考虑到重建纹理图集的开销,应尽可能在重建过程当中减小图集。有两种方式能够实现。

只要容许,尽量的使用非动态字体,预先设置所需支持的字符集。这适合用于使用有限字符集的UI,好比只使用Latin/ASCII字符,而且数量很小。

若是大量的文字须要被支持,好比整个Unicode字符集,那么字体必须设置为Dynamic。为了不一些可预测的性能问题,能够经过 Font.RequestCharactersInTexture 在启动时将合适大小的字符集预先填充到字体的字符图集中。

注意,任何一个Text组件改变都会触发字体图集的重建。当须要显示大量文本组件时,最好是先收集Text组件内容的全部字符,再去填充图集。这将确保字符图集只被重建一次,而不是每加入一个新字符时就须要重建一次。

同时须要注意的是,当字体图集触发重建时,任何没有被active Text组件包含的字符都不会放到新的图集中,即便它以前经过 Font.RequestCharactersInTexture已经打入到图集中。为了解决这个限制,能够监听Font.textureRebuilt回调,查询 Font.characterInfo 确保全部须要的字符包含在图集中。

Font.textureRebuilt委托,目前尚未文档。它是一个 single-argument Unity Event。它的参数是图集被重建的字体。监听这个事件应该按照如下格式:

public void TextureRebuiltCallback(Font rebuiltFont) { /* ... */ }

专门的字符渲染器

对于一些字符都是肯定的,而且字符之间的相对位置是固定的,那么实现自定义的组件去显示这些字符效率会更高。其中的一个例子就是分数显示。

对于分数,可显示的文字都在一个肯定的字符集中(数字0-9),而且不改变位置,和其余字符保持固定的距离。将整数分解成数字,并用数字sprite显示的开销是微不足道的。这种专门的的数字显示系统,可使用一种不须要分配,而且计算、动画、显示快速的方法去构建。它比Canvas驱动的UI Text组件更加高效。

后备字体(Fallback fonts)和内存占用

对于必须支持大量字符集的应用程序,在字体导入器的“Font Names”字段中列出大量字体是很诱人的。当一个字符在主字体中不能被定位到,那么“Font Names”字段列出的全部字体都有可能被用来当作后备字体。选择后备字体的顺序取决于字体在“Font Names”字段列表中的顺序。

然而,为了支持这种操做,Unity 须要将“Font Names”字段列出的全部字体都加载到内存中。若是字体集很是大,那么后备字体将会占用很是多的内存。这种问题常常在包含象形文字的字体(如日本文字和汉字)时出现。

Best Fit 和 性能

一般,UI Text组件的 Best Fit 设置不该该被使用。

“Best Fit”能够动态的调整字体的大小,让字体在没有超出边界的状况下,将字体调整到最大的整数值,还能够设置最大/最小值来限制字体大小。然而因为Unity的渲染器将全部显示的不一样大小的字符单独的放入到字体图集中,使用Best Fit设置,不一样大小的字符将会很快的填满图集。

对于Unity5.3版本,使用Best Fit去检测大小的算法并非最好的。它会将每个用于尺寸增量测试的字符都加入到字体图集中,这进一步的增长了生成字体图集所须要的时间。这也有可能致使字体移除,部分以前的字符会被移除图集。因为Best Fit 须要大量的测试计算,常常会产生别的Text组件使用的字符被移除,而且在合适的字体大小被计算出来以后,字体图集又会被强制重建至少一次。

这个问题在Unity 5.4版本中被解决了,Best Fit 不会没必要要的扩展字符图集,可是它仍然比静态大小的字体慢不少。

频繁的字体重建会产生内存碎片,下降运行时的性能表现。设置Best Fit的文本组件越多,这个问题越严重。

Scroll Views

在填充率问题以后, Unity UI的Scroll View 是第二常见的运行时性能问题的来源。Scroll View一般须要大量的UI元素来当作它的内容。有两种基础的填充Scroll View的方式:

  • 将scroll view内容须要的全部元素都填充进去。
  • 缓存元素,当它们须要显示时,从新设置它们的位置。

两种方法都有问题。

第一种方法会随着须要实例化的UI元素增多,消耗更多的时间,而且会增长Scroll View重建的时间。若是ScrollView中只有少许的元素,例如在滚动视图中,只须要显示少许文本组件,那么这种方式更简洁。

第二种方法须要大量的方法去确保当前的UI和布局系统能显示正常。还有两种可行方案将在下面讲述。对于很是复杂的UI,一般使用缓存池的方法能够在必定程度上减小性能开销。

不管哪一种方法,在ScrollView上添加RectMask2D组件,均可以提高性能。当Canvas重建时,这个组件能够确保Scroll View视窗外的元素不包括在必须具备生成,排序和分析其几何的可绘制元素列表中。

简单的Scroll View 元素池

最简单的方式去实现scroll view的缓存池,同时保留Unity内置Scroll View组件的便利性就是使用一种混合的方法:

在UI中布局元素,须要容许布局系统正确的计算Scroll View内容的大小,而且让滚动条运行正常。要布局的时候,可使用挂载了Layout Element组件的GameObject为可见的UI占位。

而后,实例化一个可见元素的池,让可见元素能够填满Scroll View的可见部分,而且将它们的父级设置为占位的GameObject。当Scroll View滚动时,重用UI元素来显示已经滚动到视图中的内容。

这实质上是减小必须被合批的UI元素的数量,由于合批的开销只是依赖于Canvas中 Canvas Renderers的数量,而不是Rect Transform的数量。

这种简单方法的问题

当任何一个UI元素的父级变化或者它的sibling order发生变化,那么这个元素和它的子元素都会被设置为"dirty",而且强制它们的Canvas重建。

缘由是由于Unity还没去区分父级变化和silbing order变化的回调。这两个事件都会触发OnTransformParentChanged的回调。在Unity UI的Graphic类(源码中Graphic.cs)中,那个回调会调用SetAllDirty方法。由于被设置为dirty,系统会保证这些Grapihc会在下一帧渲染以前重建它的布局和顶点。

能够为每一个Scroll View中的元素的根RectTransform添加Canvas,这样只会去重建父节点变化的元素,而不是整个Scroll View的内容。然而,这种方式会增长Scroll View渲染的drawcall。此外,若是Scroll View中的单个元素很复杂,包含了很是多的Graphic组件,而且还包含了不少布局组件,那么在低端机上重建它们的开销会明显的下降帧率。

若是Scroll View的UI元素的大小不会变化, 那么没有必要对所有的组件的布局和顶点的从新计算。避免这种行为,须要实现一个机遇位置变化的而不是父级或者sibling-order变化的对象池解决方案。

基于位置的 Scroll View 缓存池

为了不上述问题,能够建立一个Scroll View经过移动它包含UI的RectTransform来实现缓存对象。这样避免了重建那些已经移动过的而且大小没有变化的RectTransform,能够显著的提高Scroll View的性能。

一般是经过写一个自定义的Scroll View类,或者写一个自定义的Layout Group组件来实现这个操做。后者一般是更简单的解决方案,能够经过实现Unity UI的LayoutGroup抽象基类的子类来实现。

自定义布局组能够分析潜在的源数据,以检查必须显示多少个数据元素,并能够适当地调整Scroll View的内容RectTransform的大小。能够监听Scroll View change events事件,相应的调整可见元素的位置。

尾注

  1. 这包括父级的Canvas是enabled,可是Canvas Renderers disabled 的Text组件。

转载请注明出处:http://www.cnblogs.com/chiguozi/p/6803710.html

相关文章
相关标签/搜索