优化代码的方式多种多样,就像导致性能问题的原因一样。一般来说,强烈建议开发人员在尝试应用 CPU 优化之前仔细分析他们的应用程序。但是,有一些简单的 CPU 优化方法具有普遍适用性。
Unity 在内部不使用字符串名称来访问 Animator、Material 和 着色器在 GPU 上运行的程序。 更多信息
查看 词汇表 属性。为了提高速度,所有属性名称都通过哈希运算转换为属性 ID,实际上使用这些 ID 来访问属性。
因此,在 Animator、Material 或 Shader 上使用 Set 或 Get 方法时,请使用整数值方法,而不是字符串值方法。字符串方法只是执行字符串哈希运算,然后将哈希运算得到的 ID 传递给整数值方法。
从字符串哈希运算创建的属性 ID 在单个运行过程中是确定性的。最简单的方法是为每个属性名称声明一个静态只读整型变量,并在字符串位置使用该整型变量。这些变量会在启动期间自动初始化,无需任何其他初始化代码。
合适的 API 是 Animator.StringToHash 用于 Animator 属性名称,以及 Shader.PropertyToID 用于 Material 和 Shader 属性名称。
在 Unity 5.3 及更高版本中,引入了所有 Physics 查询 API 的非分配版本。将 RaycastAll 调用替换为 RaycastNonAlloc,SphereCastAll 调用替换为 SphereCastNonAlloc,依此类推。对于 2D 应用,所有 Physics2D 查询 API 也具有非分配版本。
Mono 和 IL2CPPUnity 开发的脚本后端,您可以将其用作在构建某些平台的项目时替代 Mono 的方法。 更多信息
查看 词汇表 运行时以特定方式处理从 UnityEngine.Object 派生的类的实例。对实例调用方法实际上会调用引擎代码,该代码必须执行查找和验证操作,以将脚本引用转换为本机引用。尽管很小,但将此类型变量与 null 进行比较的成本远远高于与纯 C# 类进行比较的成本。因此,在紧密循环或每帧运行的代码中避免进行这些空比较。
对于位于紧密循环中的向量和 四元数Unity 用于将旋转表示为数据的标准方法。在编写处理旋转的代码时,通常应使用 Quaternion 类及其方法。 更多信息
查看 词汇表 数学运算,请记住整型数学运算比浮点型数学运算快,而浮点型数学运算比向量、矩阵或四元数数学运算快。
因此,只要允许使用交换律或结合律,请尝试最大程度地减少单个数学运算的成本。
Vector3 x;
int a, b;
// Less efficient: results in two vector multiplications
Vector3 slow = a * x * b;
// More efficient: one integer mult, one vector mult
Vector3 fast = a * b * x;
对于必须在 HTML 格式的颜色字符串 (#RRGGBBAA
) 和 Unity 的原生 Color
和 Color32
结构之间进行转换的应用程序,通常会使用 Unify Community 中的脚本。该脚本既慢又会导致因字符串操作而大量分配内存。
从 Unity 5 开始,有一个内置的 ColorUtility API,可以有效地执行这些转换。应优先使用内置 API。
一般最佳实践是消除生产代码中所有 GameObject.Find
和 Object.FindObjectOfType
的用法。由于这些 API 要求 Unity 遍历内存中的所有 游戏对象Unity 场景中的基本对象,可以表示角色、道具、场景、摄像机、路点等。游戏对象的功用由附加到它的组件定义。 更多信息
查看 词汇表 和组件,因此随着项目范围的扩大,它们的性能会迅速下降。
上述规则的例外情况可能是单例对象的访问器。全局管理器对象通常公开一个“instance”属性,并且通常在 getter 中包含一个 FindObjectOfType
调用,以检测单例的预先存在的实例。
class SomeSingleton {
private SomeSingleton _instance;
public SomeSingleton Instance {
get {
if(_instance == null) {
_instance =
FindObjectOfType<SomeSingleton>();
}
if(_instance == null) {
_instance = CreateSomeSingleton();
}
return _instance;
}
}
}
尽管这种模式通常是可以接受的,但重要的是要检查代码并确保访问器在单例对象不存在的 场景场景包含游戏的环境和菜单。将每个唯一的场景文件视为一个独特的关卡。在每个场景中,您放置环境、障碍物和装饰,本质上是以片段的形式设计和构建您的游戏。 更多信息
查看 词汇表 中被调用。如果 getter 不会自动创建缺失的单例的实例,则很常见的是发现查找单例的代码会导致重复调用 FindObjectOfType
(通常每帧多次),并对性能造成不良影响。
UnityEngine.Debug
日志记录 API 不会从非开发版本中剥离,并且会在调用时写入日志文件。由于大多数开发人员不打算在非开发版本中写入调试信息,因此建议将仅开发的日志记录调用包装在自定义方法中,如下所示
public static class Logger {
[Conditional("ENABLE_LOGS")]
public static void Debug(string logMsg) {
UnityEngine.Debug.Log(logMsg);
}
}
通过使用 [Conditional] 属性装饰这些方法,Conditional 属性使用的定义将决定装饰的方法是否包含在编译后的源代码中。
如果传递给 Conditional 属性的定义中没有一个被定义,则装饰的方法以及所有对装饰方法的调用都会被编译出来。其效果与将方法和所有对方法的调用包装在 #if … #endif
预处理器块中相同。
有关 Conditional
属性的更多信息,请参阅 MSDN 网站:msdn.microsoft.com.