Unity 关于代码(cpu)优化的建议摘录

2021-06-14  本文已影响0人  宅7

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;

    }

}

避免在结构体中添加引用类型字段(包括字符串类型),因为包含了引用类型的结构体需要被垃圾回收器完整检测,对那些不是引用类型的变量也做了不必要的检测。可以尝试将引用类型拆分出来。

减少不必要的引用缓存,可以减少垃圾回收器检索引用所用的时间。

在不会影响玩家体验的时间点(如场景加载显示Loading 界面时)调用System.GC.Collect() 来主动回收垃圾。

上一篇下一篇

猜你喜欢

热点阅读