本章讲探讨一些能够提升UGUI性能的优化方法。
一个Canvas的主要任务就是管理它层级下的所有UI元素,并且通过Draw Call去渲染它们。其中Canvas一个重要的事儿就是对这些元素进行合批来减少draw call。但是如果对Canvas或者其下边的child元素进行了修改,就会dirty这个Canvas,导致Canvas的重制。Canvas的重制开销很大,很容易引起性能的问题。Unity中很多东西都会导致Canvas的重制,如果当一些操作发生时我们发现UI导致了CPU极大的开销,就应该考虑用更多的Canvases。
一个常见的误区就是使用一个Canvas来制作UI。
如果只有一个Canvas,当UI中有任何变化时,它必须check每一个元素,随之Canvas越来越复杂,Check的开销也会越来越大。如果使用多个Canvas,就可以当某个元素发生变化时,只影响其所在Canvas,会影响较少的元素进行重建,从而减少性能开销。
多个Canvas的缺点是各Canvas之间不能合批,因此应该是尽量保证相似的使用相同material的元素在同一个Canvas下。
可以根据更新的时机和频率把UI元素分成三组:
Static (静态):一直不会改变的UI元素,比如背景图等
Incidental Dynamic(动态):可以改变,但是只是在需要的时候会改变,比如Button的press和hover响应
Continuous Dynamic(持续动态):持续动态改变,例如动画元素
根据这个分组,可以把UI元素放入三个不同的Canvas中,这样可以降低Canvas重建的开销。
拥有Raycast Target的UI元素可以对Click,Taps等用户操作产生响应。GraphicsRaycaster会循环遍历去check哪个Raycast Target是响应目标,关掉UI上并不需要的Raycast Target可以避免GraphicsRaycaster无意义的check,从而减少开销。
可以通过设置UI元素的所属Canvas是否enable来达到隐藏该Canvas下所有UI元素的目的,优点是可以避免Canvas重建,缺点是如果脚本中有Update(), FixedUpdate(), LateUpdate()或其他持续性的代码,会依然被调用,因为这只是使得Canvas不被渲染而已。
应该完全避免在UI中使用Animator,Animator会在每一帧中该UI元素的属性从而导致大量UI信息的重新生成。应该通过自己写tweening脚本或者一些插件来完成这些操作。
在2D和3D中Canvas中的UI元素都可以用来产生交互响应,这取决于这个Canvas的Render Moder设置为Screen Space (2D) 还是World Space (3D)。每当有响应时,Canvas会通过其event Camera属性来确定使用哪个摄像机。2D Canvas默认设置为Main Camera,但是3D默认设置为null。这会带来不必要的开销,因为即使设置为null,每次使用时也是使用Main Camera,但是会调用FindObjectWithTag()函数。这个函数虽然没有Find()函数开销大,但是也是不能忽视的。所以如果使用3D模式,要手动设置好合适的event Camera。
把UI元素中Colour属性的alpha设为0依然会产生Draw Call,应该通过IsActive来控制其是否隐藏,另一种方法是使用Canvas Group,使用Canvas Group可以控制所有Child的alpha值,通过Canvas Group设置alpha为0后,渲染时会剔除掉所有child 元素,因此不会有Draw Call的开销。
ScrollRect是移动游戏中常用的组件,但是ScrollRect的性能会根据其大小有非常大的开销,因为它需要不断的进行布局重建。接下来会介绍几条关于ScrollRect的优化。
使用scrolling UI最简单的一种方式可能就是将所需要展示的UI内容的Depth设置为低于ScrollRect,单这是一种非常不好的实现方式,因为整个Scroll Rect都会参与重建,没有culling的过程。
使用RectMask2D脚本可以对超出bound的的部分进行剪裁,它的消耗在于要计算哪些object需要剪裁掉,但是要比把所有内容都渲染出来强多了。
Pixel Perfect是Canvas中的一个设置,会强制要求UI元素在屏幕中要像素精准。对于持续动画和移动的物体,这个设置有些没必要,关掉这个设置可以节省ScrollRect的一些开销,但是要注意的是这个设置是对整个Canvas的设置,会对Canvas下的所有UI元素产生影响,要确定其他元素可以接受不是Pixel Perfect。
当ScrollRect即使移动的速度非常慢,每帧几个像素时,也依然会重制。因此我们可以通过ScrollRect.velocity获取速度值,判断其在低于某个速度范围下时,就调用 ScrollRect.StopMovement()停止移动,从而避免无谓的重建。
我们经常使用一个大UIImag,将其设置为透明覆盖在屏幕上,来作为透明遮罩强制用户点击最上层的弹框。但是这种操作有可能会打破batching,更好的方式是使用一个不带字体和文字的UIText,这样就不会有任何东西需要渲染,但在bounding-box范围内依然可以交互响应。
可以查看UnityUI的源代码:https://bitbucket.org/Unity-Technologies/ui.
如果我们对UI中一些问题不满意,我们可以自己手动修改UI源代码,重新编译后导入工程中。
上边只是列出了一些在文档中很少见或者非常重要的一些优化方法,关于UI还有很多其他优化点,本书也没办法都囊括进来,可以查看文档:
https://unity3d.com/learn/tutorials/temas/best-practices/guide-optimizing-unity-ui