untiy优化—脚本优化

 这次我主要给大家讲一下使用unity中对脚本进行优化。

 1 使用最快的方法或者组件

    unity里面有几种不同的GetComponent()方法,unity中有3中重载GetComponent(string) ,  GetComponent<T>() 以及 GetComponent(typeof(T))。最快的版本取决于我们允许的unity的版本。在unity4.0中,GetComponent(typeof(T))是最好最快的方法。

我们通过一些简单的测试来证明:

int numTests = 1000000;

TestComponent test;

using (new CustomTimer("GetComponent(string)", numTests)) 

{

    for (var i = 0; i < numTests; ++i) 

    {

        test = (TestComponent)GetComponent("TestComponent");

    }

}

using (new CustomTimer("GetComponent<ComponentName>", numTests)) 

{

    for (var i = 0; i < numTests; ++i)

     {

        test = GetComponent<TestComponent>();

    }

}

using (new CustomTimer("GetComponent(typeof(ComponentName))", numTests)) 

{

    for (var i = 0; i < numTests; ++i)

     {

        test = (TestComponent)GetComponent(typeof(TestComponent));

    }

}

本代码测试每一个的getcomponent()一百万次。这是一个比典型项目更合理的测试,它足够用来证明我们的观点。GetComponent(typeof(T))比  GetComponent<T>()要快一些,比 GetComponent(string)要快差不多5倍。这个测试运行在Unity4.5.5版本上,在Unity3.X之前结果都是一样的。

GetComponent(string)不应该被使用,它是非常缓慢的,这里我是不建议使用的。它的存在只是它包含的比较完整而已。这个结果在我们运行在Unity5上做同样测试时发生了变化,在Unity5.0上类型引用的传递,作为结果,GetComponent<T>() GetComponent(typeof(T))基本上相同。

所以在unity5.0以后的版本中用GetComponent<T>() GetComponent(typeof(T))这2个方法效率是差不多的,我们我们可以根据你的习惯运用。

 2 删除空的回调申明
   当我们建立一个脚本时,unity会给我们提供2个样板方法,start和Update,unity引擎在初始化的时候会和这些方法挂钩,为她们添加一个方法表中在需要的时候回调。如果我们留下这些空的代码申明,当引擎唤醒它们的时候就会有一些小的开销。start方法只在首次实例化调用一次,也就是新的场景被加载的时候或者一个新的对象从预制被实例化的时候。所以留一个控的start方法可能不太明显,除非在启动时场景中有大量的对象被唤醒。然而,这也为任何GameObject.Instantiate()的调用增加了不必要的开销。同样如果我们场景中有好多个空的update在调用的话,这样也会浪费大量的处理器周期,严重破坏我们的帧率。所以如果我们的方法体里面什么都没有的时候,都应把它们去掉。
   3   在运行时应该避免find和sendmessage方法
         SendMessage()方法和GameObject.Find()方法是出来名的代价昂贵,应该不惜一切的避免。SendMessage的方法要比一个正常的功能慢2000倍,find方法的性能很差,因为它要遍历场景中每个物体。在awake和start初始化时调用时情有可原的,这是因为对象已经在场景中了。然而,在运行时调用就非常不合理,至于它存在的合理性,就只是为了适合初学者罢了,依赖于find和sendmessage是典型的槽糕设计,在c#和unity中是缺乏经验的表现,在原型设计时懒惰的表现。unity的技术员在他们的文档和会议反复提醒开发人员,不要去使用这2个毒瘤。至于脚本之间的通信,我会在一节中和大家详细的介绍。
   4  禁用未使用的脚本和对象
       场景很大的时候,越多的物体在update方法调用代码,槽糕的事情会变得更加严重,导致你的游戏变慢。然而,大部分被处理的东西可能是完全不必要的,比如有些物体时超出玩家视野范围内的,或者距离太远的话,我们是完全没有必要让它显示出来的,常见的比如第一人称设计游戏和赛车游戏。我们可以暂时禁用不可见的物体。这个问题可以通过onbecamevisible方法解决。通过MonoBehaviour的onbecamevisible进行回调,

如果要启用或者禁用整个游戏对象,我们需要改变一下方法:

void OnBecameVisible() { gameObject.SetActive(true); }

void OnBecameInvisible() { gameObject.SetActive(false); }

可以说, CPU 比较擅长浮点数乘法,但是计算平方根就不是很好了。每次我们调用 Vector3 magnitude 属性去计算距离或者用 Distance() 方法的时候,我们要求它进行一个平方根运算(按照毕达哥拉斯定理)。与许多其他类型的矢量数学计算相比,它可以花费大量的处理器开销。请注意,该技术可用于任何平方根的计算,而不仅仅是距离。这是你可能遇到的最常见的例子,来明确Vector3sqrMagnitude 属性的重要性-------Unity技术故意暴露给我们的一个属性。所以我们就更应该这样用了,而不是去开平方了。
 5 避免对象检索字符串属性
   通常,在C#中从一个对象检索一个字符串属性和检索任何其他引用类型的属性相同;它并没有添加额外的内存消耗。然而,隐藏在Unity源代码内的,是在内存中从重复的游戏对象中检索字符串属性和在堆分配中查找结果。这吸引了垃圾回收器的注意,如果我们不小心就会让CPU处于峰值影响运行时的性能。游戏对象中受到这种奇怪行为影响的2个属性就是标签和名字。不管任何理由检索这些属性都会导致不必要的对分配。因此,在游戏中使用这2个属性是不明智的,你应该只在性能不关紧要的时候去用它,比如在编辑脚本的时候。然而在运行的时候,对于开发人员来说,可能会造成重大的问题。
for (int i = 0; i < listOfObjects.Count; ++i) {
    if (listOfObjects[i].tag == "Player") {
       
    }
}
这些代码将会导致一个额外的堆内存分配。那么我们应该怎么去解决这个问题呢?当然肯定有办法去解决这样的问题的嘛!标签经常用于比较,同样unity为开发者提供了另外一种标签比较的办法,他不会引起堆分配的问题,那就是CompareTag()的方法。
  6   更新、协程和重复调用
       这个就设计到unity最本质的东西了,事实这也是xna核心的东西,unity给我们提供了一种新的编程思想,就是ECS的编程思想,在我看来,xan也是同样的一种思想,不过它没有可视化的界面罢了,所以我们在写unity的代码的时候,不妨采用哪种树的理念去设计你的处理脚本之间的关系,也就是哪种依赖关系,每个叶子节点我们可以自定义它的update方法,然后由最高层就是根节点的脚本去更新,然后update分发到每个叶子节点,叶子节点的update分发到叶子节点的叶子节点update。这样以便精细准确的对整个系统进行控制管理,比如菜单的暂停和冷却时间的操作的效果。而且这样便于理解。我认为国内的好多unity程序员对unity的组件式编程方式有所曲解了。
  7   Transform对缓存造成的变化
        Transform 组件只存储相对于它父类的数据。这意味着访问和修改Transform 组件的位置,旋转,大小可以导致大量的未预料到的矩阵乘法运算,来通过父对象的Transforms生成正确的Transform 表现。对象的深度越深,需要进行更多的计算以确定最终结果。更糟糕的是,改变Transform 组件也会通知内部的碰撞器、刚体、灯光和摄像机等必须处理的组件。然而,这也意味着使用localposition,localRotation和localScale相比较于它有一个很微小的成本开销,因为它们被传递的值可以被检索和写入。因此,要尽可能使用本地属性。然而,改变我们的数学计算从世界空间到局部空间会让原本简单的(甚至已解决的问题)变得复杂化,所以做出这样的改变可能破坏我们的实现和引入大量的bug。某些时候它也有一个小的性能提升来更容易解决复杂的三维数学问题!此外,在一些复杂的事件中它并不少见,我们在同一帧中替换Transform的属性多次(虽然这可能有一个警告标志着过度的设计)。我们可以通过将它们缓存在一个成员变量中考虑尽可能少的改变Transform 值的次数,并将它们提交到该帧的最后。
  8  快速的游戏对象空指针检索
       对一个Unity对象执行一个空引用检查调用本机托管桥的另一侧的方法(前面有提到过,将在第七章详细介绍)。正如预期的那样,在一些不必要的性能开销的结果:
if (gameObject != null) {
    // do something with gameObject
}
有一个简单的替代,产生同等功能的输出,但是快上一倍(尽管它确实有点混淆了代码的目的):
if (!System.Object.ReferenceEquals(gameObject, null)) {
    // do something with gameObject
}
当然这是unity优化中的冰山一角,我会在以后的章节中还会向大家介绍更深入的优化。


你可能感兴趣的:(优化)