1.对于不能使用事件触发的代码段,并不意味着每一帧都要去处理。
void Update()
{
ExampleExpensiveFunction();
}
可以通过以下代码将这些逻辑每隔x 帧做一次处理。
void Update()
{
if(Time.frameCount % interval == 0)
ExampleExpensiveFunction();
}
或者可以通过如下代码将重量级的逻辑拆分到不同的帧去执行
void Update()
{
if(Time.frameCount % interval ==0)
ExampleExpensiveFunction1();
else if(Time.frameCount % interval == 1)
ExampleExpensiveFunction2();
}
2.尽量少使用的昂贵API
SendMessage() BroadcastMessage() 内部使用了反射,使用事件或代理替代。
Find() 需要Unity 遍历所有内存中的GameObject,建议不要使用。
Transform.rotation Transform.position 设置Transform 的旋转和世界坐标会触发OnTransformChange 通知其所有的子孙Transform,因此相对来说比较昂贵尤其是那些有很多子孙的Transform,应该尽量避免频繁地赋值。
获取位置信息时,优先使用Transform.localPosition 而不是Transform.position,后者每次调用时都会重新计算物体的世界坐标,而localPosition 则是一个缓存在Transform 中的变量,如果需要频繁使用世界坐标,那么建议缓存下来。
Update() LateUpdate() 等生命期函数都有隐藏的消耗,所以即使是函数内部什么也不做也有消耗,建议不要保留空的生命期函数,尤其是Update() 这种每帧都会调用的生命期函数。
Vector Vector 系列的magiture 方法和Distance 方法使用了平方根计算,当仅仅需要比较两个向量的长度的时候,使用sqrMagnitude 效率更高。
Camera.main 不要使用,原因是内部调用了Find() 方法。
3.利用是否在视椎体内的信息来优化代码。
private Renderer myRenderer;
void Start()
{
myRenderer = GetComponent<Renderer>();
}
void Update()
{
UpdateTransformPosition();
if(myRenderer.isVisible)
{
ExampleExpensiveFunction();
}
}
4.可以使用LOD 技术提供的信息来优化代码
Unity 提供了CullingGroup API 用来提供给开发者Culling 和LOD 相关的信息,使用这些信息可以实现类似meshrenderer 的基于距离来执行不同效果的逻辑。
5.关于垃圾回收相关的优化建议
减少代码产生垃圾的建议:
缓存:经常重复调用的方法中如果有堆内存分配和回收逻辑,应该在特定时期将这些引用缓存下来,避免每次调用都要分配对内存
void OnTriggerEnter(Collider other)
{
Renderer[] allRenderers = FindObjectsOfType<Renderer>();
ExampleFunction(allRenderers);
}
改为
private Renderer[] allRenderers;
void Start()
{
allRenderers = FindObjectsOfType<Renderer>();
}
void OnTriggerEnter(Collider other)
{
ExampleFunction(allRenderers);
}
不要在频繁调用的方法(Update)中分配堆内存
使用容器类时,使用Clear() 方法代替生成新容器(或者使用容器池)
创建新的容器类实例时会触发堆内存分配,可能会触发垃圾回收
List myList = new List();
改为
myList.Clear();
使用对象池技术处理频繁创建和销毁的物体
不要频繁操作(合并、截取等)string 类型的数据
string 是引用且不可变类型,每次操作string 类型后,都会重新创建新的string 类型,可能会触发垃圾回收
创建string 时如果需要进行合并操作,可以使用StringBuilder 类用于轻量级地创建string。
移除所有不需要的Debug.Log 方法调用,每个Debug.Log 都至少会创建和销毁至少一个string。
需要显示的string 数据,如果需要合并操作,将string 数据拆分成不变的部分和变化的部分,以移除+ 操作。
public Text timerText;
void Update()
{
timerText.text = "Time:" + DateTime.Now.ToString();
}
改为
public Text headerText, timerText;
void Start()
{
headerText.text = "Time";
}
void Update()
{
timerText = DataTime.Now.ToString();
}
警惕那些返回数组、容器等的Unity 内置API,因为每次调用他们都会返回一个新的引用,可能引发垃圾回收,如需频繁使用,建议获取一次后暂存下来。
尽量减少装箱和拆箱操作
创建协程会造成垃圾,因为Unity 需要为每个协程创建管理类实例。
yield return 0;
会造成装箱装换,改为
yield return null;
可以避免装箱引起的堆内存分配
while(!isComplete)
{
yield return new WaitForSenconds(1f);
}
改为
WaitForSeconds delay = new WaitForSeconds(1f);
while(!isComplete)
{
yield return delay;
}
可以减少垃圾产生。
匿名方法是引用类型会产生垃圾,尤其是闭包会显著增加内存使用和分配。
LINQ 和正则表达式会产生垃圾,因为其内部操作会昌盛装箱操作。
避免使用枚举作为字典的key,因为会产生装箱操作,必须使用时,实现IEqualityComparer 接口并将其实例作为字典的比较器。
public class MyEnumComparer : IEqualityComparer<MyEnum>
{
public bool Equals(MyEnum x, MyEnum y)
{
return x==y;
}
public int GetHashCode(MyEnum x)
{
return (int)x;
}
}