此文档中提供了一些开发建议,帮助开发者在项目中得到更好的性能表现。
foreach语句在遍历时会生成迭代器对象,使用迭代器进行遍历,这会导致额外的内存分配和性能开销,尤其是需要频繁遍历包含大量元素的集合的情况。此外,foreach在实现遍历时会使用try-catch,并且会有final处理,也会有开销。虽然在大多数应用中这些开销可以忽略不计,但对于资源受限的小游戏可能会有显著的影响。
建议开发者谨慎考虑所使用的集合类型以及遍历时使用的方法,尽量避免在小游戏中使用foreach语句。
反射调用和字典访问在小游戏平台上的性能较低,大量的调用可能会导致游戏卡顿,性能下降,因此在小游戏上建议尽量减少反射调用和字典访问。
在开发过程中需要注意一些可能产生GC的操作:
字符串:C#的string是引用类型,而特殊的不可变性导致在尝试修改string对象时会创建一个新的字符串,原始字符串将被释放并进行垃圾回收,因此建议减少不必要的字符串创建或更改操作,如果需要在运行时构建字符串,可使用StringBuilder类。同时尽量避免解析JSON和XML等由字符串组成的数据文件,建议将数据存储于ScriptableObjects,或以MessagePack或Protobuf等格式保存,避免在解析时产生的临时字符串等带来的GC开销。
函数使用:部分函数会创建堆内存分配,建议缓存对数组的引用而不是在循环中分配新的数组。同时建议使用一些避免产生垃圾的函数,例如使用 GameObject.CompareTag 而不是使用 GameObject.tag 来手动比较,因为后者返回一个新的string会产生垃圾。
装箱:避免将一个值类型的变量传递给一个引用变量,这会导致系统创建一个临时对象,在背地里进行类型转换,从而产生垃圾回收的需求。尽量使用正确的类型覆写来传入想要的值类型,也可以使用泛型进行类型覆写。
协同:在新建 WaitForSeconds 对象时会产生垃圾回收,建议缓存并复用 WaitForSeconds 对象。
LINQ和正则表达式:两者都会在装箱时产生垃圾。在小游戏中尽量避免使用它们,建议使用for循环和list来创建新的数组。
空的 MonoBehaviours 也会占用资源,所以如果在脚本中没有使用到 Start() 等事件,删除它们可以提升代码性能。
调用 GameObject.Find、GameObject.GetComponent和 Camera.main(2020.2以下的版本)会产生较大的运行负担,因此这些方法不适合在 Update 中调用,而应在 Start 中调用并缓存。
//example
private Renderer myRenderer;
void Start()
{
myRenderer = GetComponent<Renderer>();
}
void Update()
{
ExampleFunction(myRenderer);
}
Unity底层代码不会使用字符串来访问 Animator、Material 和 Shader 属性。为了提高效率,所有属性名称都会转换成哈希值,作为实际的属性名称。
在 Animator、Material 或 Shader上使用 Set 或 Get 方法时,可以利用整数值而非字符串。使用 Animator.StringToHash 来转换 Animator 属性名称,用 Shader.PropertyToID 来转换 Material 和 Shader 属性名称。
字符串处理时,如果选择根据文化进行比较,会慢1–2个数量级。像 string.Compare(),EndsWith(),IndexOf() 等接口,尽可能传入 StringComparison.Ordinal 作为比较方式,避免多语言处理带来的性能开销。
当使用 Instantiate 的时候,如果 Object 有父级对象,建议将父级对象的 Transform 直接以参数的形式传入 Instantiate ,即使用 public static Object Instantiate(Object original, Transform parent)。
而不是单独使用 public static Object Instantiate(Object original),然后再给 Object 指定父级对象,这会有一些额外的性能开销。