UGUI的认识与优化

此文章仅收集整理个人学习之用, 部分内容来源见文尾

相比于NGUI, UGUI的优点:

  • 层级管理
    • UGUI采用的排序比较简单, 在C++层处理,效率更高
      • 在Canvas内部元素采用Hierarchy方式排序,
      • 在Canvas同级之间通过Sort Order或者是Hierarchy来进行排序
    • NGUI的排序是通过Depth、Z值、RenderQueue共同影响的,整体规则过于复杂
  • 3DMesh嵌套问题: 角色和粒子效果与UI嵌套的情况
    • UGUI修改的是粒子Renderer的Sorting Order来跟UI进行排序
    • NGUI中可以动态修改Mesh的RenderQueueMesh 的渲染序列加入到UIPanel里面
    • 使用RenderTexture, 来与其他的UI做排序
  • 合批逻辑
    • NGUI实质上是创建自定义Mesh,使用Mesh Renderer来做渲染的。对UI的合批操作逻辑在C#层
    • UGUI定义了Canvas组件,使用Canvas Renderer来做渲染,在C++层做合批逻辑,所以UGUI处理动态层的性能会比NGUI更佳
  • UI交互事件
    • UGUI中采用Event&Raycasters的设计,来替代NGUI中的Collider&SendMessage
    • 在NGUI中,默认控件是不参与交互的,除非加上Collider
    • 而在UGUI中,默认控件是参与交互的,除非手动禁止交互
    • NGUI的交互事件是通过SendMessage来发送消息的
    • UGUI采用EventSystem的分发模式,效率更高
    • 对于交互输入这种比较频繁调用的消息来说,Delegate的性能上提升不少,比较直接反馈是scroll view的滑动流畅度
  • 锚点
    • UGUI可以把任意UI元素作为参考点来进行锚点, 可能出现的问题是: 多重锚点之间,由于时序问题,控件“锚飞了”的情况
    • UGUI的锚点设计只能针对父节点来进行,是做不到跨层级锚点的
  • UI动画
    • NGUI整合了ITween,并将ITween的使用封装成脚本,可以非常方便制作出各种旋转、平移、缩放的效果,易用性很强
    • UGUI的动画实现可以通过整合DoTween来实现。相对于ITween来说,每帧耗时更短,效率更高,产生GC更少

UI制作建议

合理的分配图集

合理的分配图集可以降低drawcall和资源加载速度;具体细节如下:

  • 同一个UI界面的图片尽可能放到一个图集中,这样可以尽可能的降低drawcall。
  • 共用的图片放到一个或几共享的图集中,例如通用的弹框和按钮等;相同功能的图片放到一个图集中, 例如装备图标和英雄头像等;这样可以降低切换界面的加载速度。
  • 不同格式的图片分别放到不同的图集中,例如透明(带Alpha)和不透明(不带Alpha)的图片,这样可以减少图片的存储空间和占用内存。(UGUI的sprite packer会自动处理这种情况)

CPU优化

一般来说,优化cpu性能应该先用profiler定位到性能热点,找到消耗最高的函数,然后再想办法降低它的消耗。CPU性能开销高主要原因之一是Canvas对UI网格的重建,有很多情况会触发Canvas对网格的重建,例如Image,Text等UI元素的Enable及UI元素的长、宽或Color属性的变化等。Canvas中UI Mesh顶点较多的话,则该项将会出现较高的CPU开销。在Unity的Profiler中则对应的是Canvas.SendWillRenderCanvases或Canvas.BuildBatch占用过多的时间。

Canvas.BuildBatch主要功能是合并Canvas节点下所有UI元素的网格,合并后的网格会缓存起来,只有其下面的UI元素的网格发生改变时才会重新合并。而UI元素的网络变化主要是因为Canvas.SendWillRenderCanvases调用时,rebuild了Layout或者Graphic。该函数的调用过程时序图如下:

UGUI的认识与优化

  1. 该过程由CanvasUpdateRegistry监听Canvas的WillRenderCanvases(上图中1)而执行,主要是对前标记为dirty的layout和craphic执行rebuild。引起layout和graphic的dirty主要原因是因为Canvas树形结构下的UI元素发生了变化(例如增加删除UI对象,UI元素的顶点,rec尺寸改变等)调用了Graphic.SetDirty(实际上最终都会调用CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild)。
  2. 在rebuild layout之前会对Layout rebuild queue中的元素依据它们在heiarchy中的层次深度进行排序(上图中的2),排列的结果是越靠近根的节点越会被优先处理。
  3. rebuild layout(上图中的3),主要是执行ILayoutElement和ILayoutController接口中的方法来计算位置,Rect的大小等布局信息。
  4. rebulid graphic(上图中的4),主要是调用UpdateGeometry重建网格的顶点数据(上图中5)以及调用UpdateMeterial更新CanvasRender的材质信息(上图中6)。

基于以上UGUI的网格更新原理,我们可以做以下优化:

  1. 使用尽可能少的UI元素;在制作UI时,一定要仔细查检UI层级,删除不不必要的UI元素,这样可以减少深度排序的时间(上图中的2)以及Rebuild的时间(上图中的3,4)。
  2. 动静分离, 减少Rebuild的频率,将动态UI元素(频繁改变例如顶点、alpha、坐标和大小等的元素)与静态UI元素分离出来,放到特定的Canvas中。
  3. 谨慎使用UI元素的enable与disable, 因为它们会触发耗时较高的rebuild(图中的3、4),替代方案之一是enable和disable该UI元素的Canvasrender或者Canvas。
  4. 谨慎使用Text的Best Fit选项,虽然这个选项可以动态的调整字体大小以适应UI布局而不会超框,但其代价是很高的,Unity会为用到的该元素所用到的所有字号生成图元保存在atlas里,不但增加额外的生成时间,还会使得字体对应的atlas变大。
  5. 谨慎使用Canvas的Pixel Perfect选项,该选项会使得ui元素在发生位置变化时,造成layout Rebuild。(比如ScrollRect滚动时,如果开启了Canvas的pixel Perfect,会使得Canvas.SendWillRenderCanvas消耗较高)
  6. 使用缓存池来保存ScrollView中的Item, 对于移出或移进View外的的元素, 不要调用disable或enable,而是把它们放到缓存池里或从缓存池中取出复用。
  7. 禁用Raycast Target. 除了rebuild过程之外,UGUI的touch处理消耗也可能会成为性能热点。因为UGUI在默认情况下会对所有可见的Graphic组件开启raycast。对于不需要接收touch事件的grahic,一定要禁用raycast。对于unity5以上的可以关闭graphic的Raycast Target. 而对于unity4.6,可以给不需要接收touch的UI元素加上Canvasgroup组件。

GPU优化

一般来说,造成GPU性能瓶颈主要有两个原因:复杂的Vertex或pixel shader计算以及Overdraw造成过多的像素填充。在默认情况下UGUI中所有UI元素使用都使用UI/Defaut shader,因此在优化时可优先考虑解决Overdraw问题。Overdraw主要是因为大量UI元素的重叠引起的,查看Overdraw比较简单,在Scene窗口中选择Overdraw模式,场景中越亮的地方表示Overdraw越高

UGUI的认识与优化

为了降低Overdraw,可以做如下优化:

  1. 禁用不可见的UI,比如当打开一个系统时如果完全挡住了另外一个系统,则可以将被遮挡住的系统禁用。
  2. 不要使用空的Image, 在Unity中,raycast使用Graphic作为基本元素来检测touch, 使用空的image并将alpha设置为0来接收touch事件,这样会产生不必要的overdraw。>> 可以使用空白的Text或自定义脚本来处理

更多: