Unity CJ 干货分享:全新的Unity移动游戏优化解决方案
在今天的CJ CGDC中国游戏开发者大会上,来自Unity大中华区的技术支持经理张鑫带来了一场关于《全新的Unity移动游戏优化解决方案》的精彩主题演讲。本次演讲分享的内容涵盖了从渲染模块、物理模块、动画模块的CPU优化,堆内存的管理以及针对内存泄露和资源冗余的解决方案,还有代码的优化处理。
CPU优化
首先,可通过Profiler工具来找出具体的性能瓶颈。利用Profiler,能够查看每一帧里每个函数的具体开销。
自身逻辑代码效率
若代码逻辑复杂,行数多达上万行,可借助Begin/EndSample方法对代码进行拆分,从而找出真正开销大的代码块。
渲染模块效率
在移动设备上,半透明渲染的开销需要特别关注(例如花、树、草等),因为这类渲染在移动设备上会造成更多的overdraw。不同的设备对Drawcall的敏感度不同,因此可以针对设备制定一个“LOD”策略,在高端机上允许更多的drawcall(即可以开启更多的特效等)。
动画模块效率
MeshSkinning.Update是蒙皮计算的开销,Animator.Update是骨骼动画的更新开销。OptimizeGameObject的优化选项默认是关闭的。在红米设备上进行的一个100人测试案例中,开启该选项后,MeshSkinning.Update的效率可提高70%,Animator.Update的效率可提高30%。
另一个在红米设备上的测试,200人同屏,单个角色约有400个顶点、560面左右,动画总时长为6秒,骨骼数量平均为12个。通过序列帧的形式将skinnedMeshRenderer转换为MeshRenderer,游戏可以达到25帧的帧率。
| 测试参数 | 数值 |
|---|---|
| 人数 | 200+ |
| 顶点数 | ~400 |
| 面数 | ~500 + 60 |
| 动画时长 | ~6 s |
| 骨骼数量 | ~12 |
UI模块效率
以下是一个UI的例子,该UI由一位工程师在两周左右的时间完成(美术资源来自scaleform在Assetstore上的资源)。
UI系统的渲染顺序由UI元素在Hierarchy中的顺序决定。UI系统会通过重排UI元素的渲染顺序来减少drawcall,但前提是不改变渲染结果。因此,当UI元素发生重叠时,drawcall可能会增加。
内存管理
游戏制作的中后期通常会开始遇到内存方面的问题。
总体内存
Used Total是当前帧的Unity内存、Mono内存、GfxDriver内存、Profiler内存的总和。
Mono内存
Mono内存用于记录代码堆内存的分配情况,由Mono进行控制。其特点是内存只增不减,建议将其控制在小于40MB。然而,约80%的团队不太关注Mono内存,但它对游戏的流畅性有着重要影响。
常见问题
- Log输出:StackTraceUtility.PostprocessStacktrace ()和StackTraceUtility.ExtractStackTrace() 这类Log的输出不仅会消耗CPU资源,同时也会引起较大的堆内存分配。
- 内存泄露:资源被强行Hold无法释放,表现为Profiler中内存增长趋势明显,且资源无法回收。可使用Detailed Memory Profiler进行检测。
- 资源冗余:资源通过AB加载,且资源在AB建立时存在多份。需要对AB打包机制进行详细排查,采用依赖关系打包的方式。
代码优化
游戏优化深度检测
通过Profiler逐帧查看和分析CPU占用较高的函数,例如GC.Alloc和Shader.Parse。Shader.Parse是Shader加载时的解析耗时,可通过将shader打入单独的assetbundle包,并在进入场景时进行预加载来避免这部分耗时。
同一时间Load过多的非“基础”AB,且不及时Unload,会导致过多的内存消耗,如WebStream和Serialized File的内存。
PersistentManager.Remapper的作用是:PersistentManager维护资源的持久化存储功能,Remapper保存的是加载到内存的资源heapID与源数据FileID的映射关系。但Remapper是memory pool,只增不减,因此建议不要一次性加载过多的assetbundle,而是采用流式的方式进行加载,以减小其峰值。
小结
性能优化没有固定的模式,需要根据具体的情况“因时因地”进行调整。