一.代码规范和建议
-
避免Update LateUpdate等函数内频繁的GC Alloc,避免在Update和LateUpdate内有以下操作:
-
调用GetComponet()
-
调用FindObjectsOfType()
-
使用GameObject.Tag和GameObject.Name
-
等等其他有堆内存分配的操作
-
避免频繁调用ToString()分配内存
-
避免OnGUI的调用
-
禁止使用Debug.Log打印log,用框架中的LogManager统一管理
-
避免使用枚举或struct做key进行字典查找(除非使用定制的comparer)他们的GetHashCode都有装箱操作,每次调用TryGetValue查找都会有内存分配.
-
在使用协程时尽量复用WaitXXX对象,而不是每次分配
-
频繁创建和更新的字符串尽量缓存,比如CD时间等
-
尽量避免dict.Values操作,直接遍历取Values即可
-
使用CompareTag代替GameObject.Tag
-
用RayCastNoAlloc替换RayCastAll
-
用yield return null 代替 yield reture 0
-
如果文件中不需要Update或Start函数等,删掉对应的空函数
-
尽量少用lambda,创建带upvalue的lambda对象会产生124B的GC,禁止在Update或for循环中使用
-
使用数组的数组,而不是多维数组。如int[i][j]的数组的数代替int[i,j]的多维数组
-
尽可能避免使用携程
-
禁止使用接受字符串参数的GetComponent等类似函数的重载方法,使用泛型类型的
-
代码中需要访问到 Transform 组件的位置数据时, 尽可能使用 localPosition 代替对 position 属性的访问. localPosition存储在transform中,访问该值时,Unity会直接将其返回,而position在每次访问时都会重新计算,如果要经常获取position,可以将其缓存起来。
-
避免使用LINQ表达式,部分功能无法在某些平台上使用,会分配大量GC Alloc。
-
禁止高频的字符串拼接. 如无法避免, 必须使用 StringBuilder 代替 "+" 操作符进行字符串拼接.
-
Delegate 回调方法必须适时的解除注册, 否则回调方法所属的对象会一直有引用计数, 继而引起该对象所引用的资源无法得到释放.
-
尽量避免使用 Reflection(反射).
-
尽量避免使用可变参数(param object[] args), 避免装箱拆箱.
-
简单条件判断尽量使用三目运算符: b?x:y.
-
注意 List 等容器常用接口的复杂度, 尽量从尾部移除/批量移除(RemoveRange)等.
-
在频繁查询数据列表时, 建议使用 HashSet/HashTable 查找时间复杂度低的数据结构, 避免使用 List.
-
尽可能为快速产生和消灭的大量对象建立缓冲池.
-
尽可能将类或函数声明为 sealed, IL2CPP 会对 sealed 的类或函数进行优化, 变虚函数调用为直接函数调用.
-
尽量减少new的次数,预分配/成员变量替代临时new变量等.
-
最快的空串比较方法:最快的方法是str.Length == 0其次是str == String.Empty或str == "" 注:C#在编译时会将程序集中声明的所有字符串常量放到保留池中(intern pool),相同常量不会重复分配。(指的是这个"")
-
减少Find方法使用。Find()方法会遍历内存中的每个GameObject和组件,随着项目规模的扩张,它的开销将会越来越大。不要频繁的使用Find()和与其类似的方法,可以考虑在Inspector中设置对对象的引用,或者创建一个专门用于管理需要搜索的对象的引用的脚本。
-
避免使用Camera.main。 Camera.main 因此遭遇了和 Find() 一样的问题:在内存中搜索了所有的 GameObjects 和 Components,这个使用可能会消耗大。
-
尝试使用sqrMagnitude(即magnitude的平方)替代magnitude,减少开平方操作。
-
尽量少用模运算和除法运算,比如a/5f,要写成a*0.2f。
-
尽量避免在运行时为Transforms重新设置parent。Unity对于在同一个parent下的所有Transforms,内存排布是连续的类似动态数组,运行时重新设置parent可能会导致数组重新分配
-
变量和字段如果可以声明为const应该总是被声明为const,如果无法使用const,尝试使用readonly
二.其他规范和建议
-
禁止非图集贴图资源不合理的留白. 会影响 UGUI 运行时自动合批.
-
资源设置 UI 的贴图资源禁止勾选 Generate Mip Maps.
-
禁止使用修改 Alpha 值的方式来隐藏界面.
-
尽可能降低 Release 版中图集留白, 提高贴图利用率.
-
建议同一 Canvas 中使用到的图集数量控制在三个以内.
-
建立合理规划公共图集. 在内存占用/加载频度/引用复杂度之间确定合理的平衡点.
-
禁止匿名 GrabPass. 如需使用到 GrabPass 必须命名并尽可能复用.
-
Shader中尽可能减少多 Pass 渲染, 除非必须这么做.
-
尽可能降低 Release 版中 Shader 中的 Keyword 数量.
-
尽可能降低 SkinnedMeshRenderer 组件数量.
-
禁止逐帧直接使用名称对 Shader Uniform 量进行更新
-
尽量避免在 Shader 中使用复杂的计算如: pow/sin/cos/tan/log 等.
-
建议在 Shader 中采用预混合或实时混合纹理的方式代替实时地多次纹理采样
-
移动平台的 Shader 编码一定要考虑数据精度(float/half/fixed)的合理使用.
-
应尽可能减少每帧 Material.GetXX/Material.SetXX的次数, 比如把多个 uniform half 变量合并为 uniform half4.
-
尽可能避免将 Animator 的 Culling Mode 属性设置为 Always, 对于不使用 RootMotion 的项目建议选择 CullCompletely.
-
尽可能避免使用物理引擎. 建议自行编码模拟物理效果
-
使用物理模块的游戏, 建议在 PhysicsManager 中设置矩阵, 会有较大的性能提升
-
如使用第三方音频插件, 需禁用 FMOD 模块(Edit -> Project Settings -> Audio -> Disable -> UnityAudio).
-
若不需要立体音效, 音频导入设置需勾选 Force to Mono(注: 制作音频时就应该制作单通道的.)
-
音频格式: iOS平台一般使用 mp3, Android平台一般使用 ogg.
-
无需由逻辑代码访问的渲染资源禁止勾选 Read/Write Enabled, 如网格和图片.
-
导入蒙皮网格模型时建议勾选 Optimize GameObject 优化选项, 可极大地降低骨骼层次复杂度, 优化 CPU 性能.
-
无动作模型资源必须将 Animation Type 设置为 None, 否则会导致游戏对象挂在不必要的动画脚本, 大量的话会严重影响 CPU 性能.
-
导入的模型如果无需用到法线和切线, 必须将导入设置的 Normals 和 Tangents 选项设置为 None.
-
导入的模型如果无需参与Unity Lightmap 烘焙, 必须将导入设置的 Generate Lightmap UVs 选项设置为 None.
-
禁止在 Release 版中存在任何 OnGUI 相关代码
-
禁止在 Release 版中存在任何日常调试相关的 UnityEngine.DeBug 类日志输出
-
禁止在 Release 版中开启 "Development Build" 和 "Script Debugging" 选项
-
在发布时必须将游戏锁定至合适的帧率(建议 30/60 帧)
-
禁止在 Release 版中使用引擎提供的 SendMessage 方法.