优化Unity游戏中的脚本

代码性能不佳的原因

  1. 代码浪费,结构不良
    例子:重复调用只能调用一次的函数代码
  2. 对其他代码进行了不必要的昂贵调用
    例子:可能是导致托管代码和引擎代码之间不必要的调用的代码。
  3. 虽然我们的代码是高效的,但它在不需要时被调用
    例子: 可能是模拟敌人视线的代码。代码本身可能执行得很好,但是当玩家离敌人很远的时候运行这段代码是很浪费的。
  4. 我们的代码要求太高了。
    例子:可能是一个非常详细的模拟,其中大量代理正在使用复杂的人工智能。如果我们已经用尽了其他可能性并尽可能优化了这段代码,那么我们可能只需要重新设计我们的游戏以降低要求:例如,伪造我们的模拟元素而不是计算它们。

提高代码的性能

  1. 编写高效的代码
  1. 尽可能将代码移除循环
  2. 考虑代码是否必须运行每一帧
  1. 仅在事情发生变化时运行代码
  2. 每[x]帧运行一次代码
    如果代码需要频繁运行并且不能被事件触发,这并不意味着它需要每帧运行。
private int interval = 3;
void Update()
{
    if(Time.frameCount % interval == 0)
    {
        ExampleExpensiveFunction();
    }else if(Time.frameCount % interval == 1)
    {
        AnotherExampleExpensiveFunction();
    }
}
  1. 使用缓存
void Update() 
{
    Renderer myRenderer = GetComponent();
    ExampleFunction(myRenderer);
}
优化,把Renderer 提出成成员变量,只取一次。
  1. 使用正确的数据结构
    了解算法复杂性以及不同数据结构的优缺点将有助于我们创建性能良好的代码。
    关于C#中的集合和数据结构
    大O表示法
  2. 尽量减少垃圾收集的影像
  3. 使用对象池

避免对Unity API的昂贵调用

  • SendMessage() 和 BroadcastMessage()
    • 这些函数用到了反射,反射是代码在运行时而不是在编译时检查并做出关于自身的决定的术语。
    • 建议仅用于原型设计,并尽可能使用其他函数。
    • 使用观察者模式
  • Find()
    • 功能强大但代价昂贵。需要Unity遍历内存中的每个GameObject和Component。
    • 随着项目复杂性的增加,使用起来会变得更加昂贵。
  • Transform
    • 设置Transform的位置或旋转会导致内部OnTransformChanged事件传播到该变换的所有子级。
    • 应避免过于繁杂地设置这些属性的值。
    • 例如把两次变换计算合并为一次。
    • Transform.position是访问器的一个示例,它会在后台进行计算。
    • 调用Transform.localPosition只返回该值。但是,每次调用Transform.position时都会计算变换的世界位置。
  • Update()

Update()、LateUpdate()有隐藏开销,每次调用都需要引擎代码和托管代码之间的通信。
Unity在调用这些函数之前会执行一些安全检查(GameObject处于有效状态,未被销毁等。)
这种开销对于任何单个调用都不是特别大,但是数千个MonoBehaviours加起来开销可就大了。

  • Vector2,Vector3
  • 如果我们在代码中执行许多频繁的 Vector2 和 Vector3 数学运算,例如在大量 GameObjects 的Update()中的嵌套循环中,我们很可能会为 CPU 创建不必要的工作.
  • 可以通过执行 int 或 float 计算来节省性能.
  • CPU执行平方根运算比简单乘法指令要慢。
  • Vector2.magnitude 和 Vector3.magnitude (向量大小) ,都涉及平方根运算。
  • Vector2.Distance 和 Vector3.Distance在幕后使用magnitude
  • 我们可以通过使用Vector2.sprMagnitude和Vector3.sprMagnitude来避免平方根计算。
  • Camera.main
    • Camera.main,返回对第一个启用的带有"主摄像头"标记的摄像头组件的引用。
    • 看起来像变量实际上是个访问器。
    • 在后台调用Find(),因此,Camer.mainFind()也有相同问题。(它搜索内存中的所有游戏对象和组件)
    • 为避免浪费,缓存Camera.main结果。

仅在需要运行时运行代码

  • Culling (剔除)
    Unity包含检查对象是否在相机的截锥体内的代码。如果它们不在相机的截锥体内,则与渲染这些对象相关的代码不会运行。
    frustum culling(截锥体剔除)
    private Renderer myRenderer;
    void Start()
    {
        myRenderer = GetComponent();
    }
    
    void Update()
    {
        UpdateTransformPosition();
        //检查敌人的渲染器是否在任何相机的截锥体内。
        if(myRenderer.isVisible)
        {
            //与敌人视觉状态相关的代码只有在敌人可见时才会运行。
            UpdateAnimations();
        }
    }
  • Level of detail (详细程度,层次细节)
    Level of detail (LOD),一种常见的渲染优化技术。

使用详细的网格和纹理以完全保真度渲染离玩家最近的对象。远处的物体使用不太详细的网格和纹理。
代码可效仿这种方法。
Unity 的CullingGroup API 允许我们连接到 Unity 的 LOD 系统以优化我们的代码。

你可能感兴趣的:(优化Unity游戏中的脚本)