字符串和文字
处理字符串和文本是Unity项目中性能问题的常见原因。在C#中,所有字符串都是不可变的
。对字符串的任何操作都会导致分配一个全新的字符串。这是相对昂贵的,并且当在大型字符串,大型数据集或紧密循环中执行时,重复的字符串连接可能会出现性能问题。
此外,由于N个字符串连接需要分配N-1个中间字符串,因此串行连接也可能是管理内存压力的主要原因。
对于必须在紧密循环或每个帧中连接字符串的情况,请使用StringBuilder执行实际的连接操作。StringBuilder实例也可以重用,以进一步减少不必要的内存分配。
按ID地址属性
Unity不使用字符串名称来解决Animator,Material和Shader
内部属性。对于速度,所有属性名称都被散列为属性ID,而这些ID实际上用于寻址属性。
因此,每当在Animator,Material或Shader上使用Set或Get方法时,请使用整数值方法而不是字符串值方法。字符串方法只执行字符串散列,然后将散列ID转发给整数值方法。
从字符串哈希创建的属性ID在单次运行过程中是确定性的。使用它们的最简单方法是为每个属性名称声明一个静态只读整数变量,并使用整数变量代替字符串。这些在启动期间自动初始化,无需进一步的初始化代码。
适当的API是Animator.StringToHash用于Animator属性名称,Shader.PropertyToID用于Material&Shader属性名称
与UnityEngine.Object子类进行空比较
Mono和IL2CPP
运行时处理以特定方式从UnityEngine.Object派生的类的实例。在实例上调用方法实际上会调用引擎代码,引擎代码必须执行查找和验证才能将脚本引用转换为本机引用。虽然很小,但是将此类型的变量与null进行比较的成本比与纯C#类的比较要昂贵得多。因此,请避免在紧密循环或每帧运行的代码中进行这些空比较。
矢量和四元数数学和操作顺序
对于向量和四元数
数学中的紧凑循环,请记住整数数学比浮点数学更快,浮点数学比矢量,矩阵或四元数学更快。
因此,每当交换或关联算术允许时,尝试最小化单个数学运算的成本:
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;
内置ColorUtility
对于必须在HTML格式的颜色字符串(#RRGGBBAA
)和Unity的本机Color
和Color32
结构之间进行转换的应用程序来说,通常使用统一社区中的脚本。由于字符串操作,此脚本速度很慢并导致大量内存分配。
从Unity 5开始,有一个内置的ColorUtility API可以有效地执行这些转换。应首选使用内置API。
Find和FindObjectOfType
一般的最佳做法是消除生产代码中的所有使用Object.Find
和Object.FindObjectOfType
生产代码。由于这些API要求Unity迭代所有GameObjects
内存和组件,随着项目范围的扩大,它们会迅速变得不具备性能。
可以在单例对象的访问器中对上述规则进行例外处理。全局管理器对象经常暴露“实例”属性,并且通常FindObjectOfType
在getter中调用以检测单例的预先存在的实例:
class SomeSingleton {
private SomeSingleton _instance;
public SomeSingleton Instance {
get {
if(_instance == null) {
_instance =
FindObjectOfType<SomeSingleton>();
}
if(_instnace == null) {
_instance = CreateSomeSingleton();
}
return _instance;
}
}
}
虽然这种模式通常是可以接受的,但检查代码并确保在场景中
调用访问器非常重要中单个对象不存在的位置。如果getter没有自动创建缺失单例的实例,那么很常见的是发现寻找单例的代码会导致重复调用FindObjectOfType
(通常每帧多次)并且会导致不希望的性能消耗
相机定位器
在内部,Unity的Camera.main
属性调用Object.FindObjectWithTag
,一个专门的变体Object.FindObject
。访问此属性并不比调用更有效Object.FindObjectOfType
。如果代码必须指向主摄像机
,强烈建议您执行以下两项操作之一:
- 访问
Camera.main
在一个Start
或OnEnable
回调并缓存所产生的参考。 - 构造一个
Camera Manager
可以为活动摄像头提供或注入引用的类。
调试代码和[条件]属性
该UnityEngine.Debug
日志记录API不是由非开发版本剥离,也写入日志文件,如果调用。由于大多数开发人员不打算在非开发版本中编写调试信息,因此建议在自定义方法中包含仅开发日志记录调用,如下所示:
namespace NxFish{
public static class Debug{
[Conditional("DEBUG")]
public static void Log(string logMsg) {
UnityEngine.Debug.Log(logMsg);
}
}
}
通过使用[Conditional]属性修饰这些方法,条件属性使用的定义或定义确定装饰方法是否包含在已编译的源中。
如果未定义传递给Conditional属性的任何定义,则编译出装饰方法和对trim方法的所有调用。效果与方法和对方法的所有调用都包含在#if … #endif
预处理程序块中时会发生的情况相同。
附加:使用Conditional属性的方法受到以下限制:
1、条件方法必须是类声明或结构声明中的方法。如果在接口声明中的方法上指定Conditional属性,将出现编译时错误;
2、条件方法不能是接口方法的实现。否则将发生编译时错误;
3、条件方法必须具有void返回类型;
4、不能用override修饰符标记条件方法。但是,可以用virtual修饰符标记条件方法。此类方法的重写方法隐含为有条件的方法,而且不能用Conditional属性显式标记。
注:环境变量(或条件编译符号)的设置方法有三:
1)用#define定义以及#undef取消定义,在所有using命名空间前面定义;
2)用编译器命令行选项(例如,/define:DEBUG),在“项目——右键——属性——生成——常规栏”下的条件编译符号(Y)中设置(如果多个,可以用英文逗号隔开)。DEBUG版本下,系统默认设置了DEBUG和TRACE;
3)用操作系统外壳程序中的环境变量(例如,set DEBUG=1)
说明:
1、Debug可以看做是声明的一个变量,但此变量没有真正的值,存在时#if Debug结果为true,否则为false;
2、#define单独用没什么意义,一般是和#if或者Conditional特性结合使用;
3、#define必须定义在所有using命名空间前面;
4、Debug与DEBUG是不同的,C#区分大小写