WeTest 导读

本文作者从自身多年的 Unity 项目 UI 开发及优化的经验出发,从 UGUI,CPU,GPU 以及 unity 特有资源等几个维度,介绍了 unity 手游性能优化的一些方法。


在之前的文章《手游内存占用过高?如何快速定位手游内存问题》中提到,Mono 内存和 native 内存是 PSS 内存主要的组成部分,mono 内存更多的起到内存调用的功能,因此常常成为了开发人员优化内存的起点;而在游戏的其他的进程中,同样有很多因素影响着游戏的性能表现。本文将从 UGUI 的优化角度,介绍 unity 游戏性能优化的一些内容。

一、UGUI 简介

UGUI 是 Unity 官方推出的 UI 系统,集成了所见即所得的 UI 解决方案, 其功能丰富并且使用简单,同时其源代码也是开放的,下载地址:https://bitbucket.org/Unity-Technologies/ui/src

相比于 NGUI,UGUI 有以下几个优点:

  1. 所见即所得的编辑方式,在 Scene 窗口中即可编辑。

  2. 智能的 Sprite packer 可以将图片按 tag 自动生成图集而无需人工维护,生成的图集合并方式比较合理,无冗余资源。

  3. 渲染顺序与 GameObject 的 Hierarchy 顺序相关,靠近根节点显示在底层,而靠近叶子节点显示在顶层;这样的渲染方式使得调整 UI 的层级比较方便和直观。

  4. RectTranForm 及锚点系统更适合于 2D 平面布局,并且非常方便多分辨率屏幕自适配。

二、UI 制作规范和指导方法

本文是关于 UGUI 优化的,或许你会觉得 UI 的制作规范及指导方法与优化无关,其实很多性能问题往往是资源的不合理使用造成的,比如使用了尺寸过大的图片、引用了过多的图集以及加载了不必要的资源等。如果从设计和制作 UI 一开始就遵守特定的规范,则可以规避不必要的性能开销。笔者根据参与的多个项目总结了以下几点通用的规范和指导方法(这些规范适用于所有项目,不管你使用 UGUI 还是 NGUI)。

1. 合理的分配图集

合理的分配图集可以降低 drawcall 和资源加载速度;具体细节如下:
(1)同一个 UI 界面的图片尽可能放到一个图集中,这样可以尽可能的降低 drawcall。

(2)共用的图片放到一个或几共享的图集中,例如通用的弹框和按钮等;相同功能的图片放到一个图集中, 例如装备图标和英雄头像等;这样可以降低切换界面的加载速度。

(3)不同格式的图片分别放到不同的图集中,例如透明 (带 Alpha) 和不透明(不带 Alpha) 的图片,这样可以减少图片的存储空间和占用内存。(UGUI 的 sprite packer 会自动处理这种情况)

2. resources 目录中应该只保存 prefab 文件,其它非 prefab 文件(例如动画,贴图,材质等)应放到 resource 目录之外

因为随着项目的迭代,可能会导致部分资源(动画,贴图)等失效,如果这些文件放在 resource 目录下,在打包时,unity 会将 resource 目录下文本全部打成一个大的 AssetBundle 包(非 resouce 目录下的文件只有在引用到时才会被打到包里),从而出现冗余,增加不必要的存储空间和内存占用。可以通过以下代码(Mac 环境下)在控制台窗口中查看当前目录下所有非 prefab 资源的代码:

find . -type f | egrep -v "(prefab|prefab.meta|meta)$"

例如在笔者的一次扫描中,发现在了如下结果:

3. 关卡内的 UI 资源不要与外围系统 UI 资源混用

在关卡内,需要加载大量的角色及场景资源,内存比较吃紧,一般在进入关卡时,都会手动释放外围系统的资源,以便使关卡内有更多的内存可以使用。如果战斗内的 UI 与外围系统的 UI 使用相同图集里的图片,则有可能会使得外围系统的图片资源释放不成功。对于关卡内与外围共用的 UI 资源需要特殊处理,一般来说复制一份出来专门给关卡内使用是比较好的选择。

4. 适当的降低图片的尺寸

有时 UI 系统的背景可能会使用全屏大小的图片,比如在 Iphone 上使用 1136*640 大小的图片;使用这样尺寸的图片代价是很昂贵的,可以和美术同学商量适当的降低图片的精度,使用更低尺寸的图片。

5. 在 android 设备上使用 etc 格式的图片

目前,几乎所有 android 设备都支持 etc1 格式的图片,etc1 的好处是第个像素点只战用 0.5 个字节而普通 rgba32 的图片每个像素点占 4 个字节,也就说一张 1024*1024 图片如果使用 rgba32 的格式所占用的内存为 4M 而 etc1 格式所占用的内存仅为 0.5M。但是使用 etc1 格式的图片有两个限制——长和宽必须是 POT 的(2 的 N 次方)并且不支持 alpha 通道,因此使用 etc1 时需要额外的一张图来存储 alpha 通道,并且使用特殊的 shader 来对 alpha 采样。具体的细节可参考:http://malideveloper.arm.com/resources/sample-code/etcv1-texture-compression-and-alpha-channels/

6. 删除不必要的 UI 节点、动画组件及资源

随着项目的迭代,可能有部分 ui 节点及动画已经失效,对于失效的节点及动画一定要删除,在很多项目中,有部分同学为了方便省事,只是将失效的节点及动画 disable 了。这样做虽然在运行时不会对 cpu 造成太多负担,但是在加载时会增加不必要的加载时间以及内存占用。对于废弃的 UI 图片资源,虽然未放到 Resource 目录最终不会打到包里,但是在 Editor 模式下仍然会打到图集中从而影响优化决策。笔者写了一个扫描未使用到 UI 贴图资源的工具,代码地址:https://github.com/neoliang/FindUnUsedUITexture

另外,对于废弃的脚本,可能还会有某些对象持有对它的引用,而加载这样的对象也比较耗时,笔者也写了一个扫描废弃脚本的工具,代码地址:https://github.com/neoliang/MissingScriptFinder

三、CPU 优化

一般来说,优化 cpu 性能应该先用 profiler 定位到性能热点,找到消耗最高的函数,然后再想办法降低它的消耗。经过笔者多次使用 profiler 对 UGUI 的分析来看,其 CPU 性能开销高主要原因之一是 Canvs 对 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 或者 craphic。该函数的调用过程时序图如下:
这里写图片描述

1.该过程由 CanvasUpdateRegistry 监听 Canvas 的 WillRenderCanvases(上图中 1)而执行,主要是对前标记为 dirty 的 layout 和 craphic 执行 rebuild。引起 layout 和 graphic 的 dirty 主要原因是因为 Canvas 树形结构下的 UI 元素发生了变化(例如增加删除 UI 对象,UI 元素的顶点,rec 尺寸改变等)调用了 Graphic.SetDirty(实际上最终都会调用 CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild)。

  1. 在 rebuild layout 之前会对 Layout rebuild queue 中的元素依据它们在 heiarchy 中的层次深度进行排序(上图中的 2),排列的结果是越靠近根的节点越会被优先处理。

  2. rebuild layout(上图中的 3),主要是执行 ILayoutElement 和 ILayoutController 接口中的方法来计算位置,Rect 的大小等布局信息。

  3. rebulid graphic(上图中的 4),主要是调用 UpdateGeometry 重建网格的顶点数据(上图中 5)以及调用 UpdateMeterial 更新 CanvasRender 的材质信息(上图中 6)。

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

a. 使用尽可能少的 UI 元素;在制作 UI 时,一定要仔细查检 UI 层级,删除不不必要的 UI 元素,这样可以减少深度排序的时间(上图中的 2)以及 Rebuild 的时间(上图中的 3,4)。

b. 减少 Rebuild 的频率,将动态 UI 元素(频繁改变例如顶点、alpha、坐标和大小等的元素)与静态 UI 元素分离出来,放到特定的 Canvas 中。

c. 谨慎使用 UI 元素的 enable 与 disable,因为它们会触发耗时较高的 rebuild(图中的 3、4),替代方案之一是 enable 和 disableUI 元素的 canvasrender 或者 Canvas。

d. 谨慎使用 Text 的 Best Fit 选项,虽然这个选项可以动态的调整字体大小以适应 UI 布局而不会超框,但其代价是很高的,Unity 会为用到的该元素所用到的所有字号生成图元保存在 atlas 里,不但增加额外的生成时间,还会使得字体对应的 atlas 变大。
这里写图片描述

e.谨慎使用 Canvas 的 Pixel Perfect 选项,该选项会使得 ui 元素在发生位置变化时,造成 layout Rebuild。(比如 ScrollRect 滚动时,如果开启了 Canvas 的 pixel Perfect,会使得 Canvas.SendWillRenderCanvas 消耗较高)
这里写图片描述

f. 使用缓存池来保存 ScrollView 中的 Item,对于移出或移进 View 外的的元素,不要调用 disable 或 enable,而是把它们放到缓存池里或从缓存池中取出复用。

g. 除了 rebuild 过程之外,UGUI 的 touch 处理消耗也可能会成为性能热点。因为 UGUI 在默认情况下会对所有可见的 Graphic 组件调用 raycast。对于不需要接收 touch 事件的 grahic,一定要禁用 raycast。对于 unity5 以上的可以关闭 graphic 的 Raycast Target 而对于 unity4.6,可以给不需要接收 touch 的 UI 元素加上 canvasgroup 组件。

unity5.x
这里写图片描述

unity4.6

这里写图片描述

四、GPU 优化

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

这里写图片描述

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

  1. 禁用不可见的 UI,比如当打开一个系统时如果完全挡住了另外一个系统,则可以将被遮挡住的系统禁用。

  2. 不要使用空的 Image,在 Unity 中,RayCast 使用 Graphi 作为基本元素来检测 touch,在笔者参与的项目中,很多同学使用空的 image 并将 alpha 设置为 0 来接收 touch 事件,这样会产生不必要的 overdraw。通过如下类 NoDrawingRayCast 来接收事件可以避免不必要的 overdraw。

  3. public class NoDrawingRayCast : Graphic

  4. {

  5. public override void SetMaterialDirty()

  6. {

  7. }

  8. public override void SetVerticesDirty()

  9. {

  10. }

  11. protected override void OnFillVBO(List vbo)

  12. {

  13. vbo.Clear();

  14. }

}

五、总结

优化 UGUI 性能没有万能的方法,笔者这些经验总结也只能作为参考。优化性能往往是在各种选择之间做出平衡,比如 drawcall 与 rebuild 平衡、内存战胜与 cpu 消耗平衡以及 UI 图片精度与纹理大小的平衡等。每一次优化都有可能使得瓶颈出现在其它的环节上,要善于使用 profiler,找到性能热点,对症下药。

六、关于资源占用问题

UI 资源优化是 UGUI 性能优化的重点,腾讯 WeTest 也在资源方面提供了性能的测试。以下通过 “纹理” 资源,介绍腾讯 WeTest 性能测试在资源方面的测试情况。

1、登录http://wetest.qq.com/cube/ ,点击 “Android 版 下载”,或者在页面末尾扫描二维码直接下载腾讯 WeTest 的手游客户端性能分析工具 Cube。打开工具,选择 “Unity 资源分析”。

2、上传测试报告后,我们可以通过测试报告,了解 unity 游戏的资源情况。

资源结论概况

进入资源数据的报告之后,首先可以看到所有资源数据的概况结果,总体上了解存在问题的数据,继续下拉,可以了解该指标的具体情况。
这里写图片描述

资源数据概况

下面将以 “纹理资源” 为例,对 cube 资源测试报告进行解读。

纹理资源

Cube 测试报告的 “纹理资源”,根据腾讯标准,是期望<50MB 的,从下图可见,如果超出红色虚线,就说明纹理资源存在超标。

这里写图片描述

点击具体数据点,获取具体资源数据

另外,点击图表中的绿色线条中的具体数据点,可以看到这个点的当前数据,所有数据根据资源大小进行排序:

这里写图片描述
所有数据根据资源大小进行排序

在这个表之下,有一个 “资源大小 top20” 的表格,罗列了资源排名前 20 的资源内容。其中资源大小超过建议值的会呈现红色,资源大小非 2 的 n 次幂的呈现黄色。点击任意一个资源名称,可以在图表上观察这个资源所影响的区域:
这里写图片描述

点击具体资源了解影响区域

这里写图片描述

了解资源调用的影响区域

针对手游的性能优化,腾讯 WeTest 平台的 Cube 工具提供了基本所有相关指标的检测,为手游进行最高效和准确的测试服务,不断改善玩家的体验。目前功能还在免费开放中。

体验地址:http://wetest.qq.com/cube/
帮助中心:http://wetest.qq.com/help/documentation/10096.html

如果对使用当中有任何疑问,欢迎联系腾讯 WeTest 企业 qq:800024531


↙↙↙阅读原文可查看相关链接,并与作者交流