读书人

[Unity 3D] Unity 3D 性能优化 (1)

发布时间: 2013-09-15 19:58:13 作者: rapoo

[Unity 3D] Unity 3D 性能优化 (一)

听到过很多用Unity 3D开发游戏的程序员抱怨引擎效率太低,资源占用太高,包括我自己在以往项目的开发中也头疼过。最近终于有了空闲,可以仔细的研究一下该如何优化Unity 3D下的游戏性能。其实国外有不少有关U3D优化的资料,Unity官方的文档中也有简略的章节涉及这方面的内容,不过大多都是以优化美术资源为主,比如贴图的尺寸,模型静态及动态的batch以减少draw call,用lightmap替代动态光影,不同渲染模式在不同环境下的性能等等。鉴于此,加上美术资源方面的东西本人不是特别了解,所以都撇开不谈,这里先试着分析分析U3D脚本中常用代码段的执行效率


GetComponent

这是一个U3D脚本中使用频率最高的函数之一,这一族函数包括GetComponent,GetComponents,GetComponentInChildren,GetComponentsInChildren以及他们的泛型版本,此外GameObject类以及Component类上的很多属性也可以归于这一范畴,比如Component类的gameObject属性,GameObject类和Component类都有的transform属性等等这一系列从GameObject实例以及Component实例上获取其他挂载的内建组件的属性接口。

先来看看GetComponent函数的几种重载形式:

Vector3 pos = gameObject.transform.position;gameObject.collider.enabled = false;

以我们的直觉,GameObject类和Component类所提供的这些属性应该都是直接访问的事先缓存好的组件引用,因此对这些属性的使用便无所顾忌。但是事情真的是如我们所想的那样吗?如果我告诉你,有时候哪怕是用GetComponent函数的string参数形式都会比使用这些属性来的要快,你相信么?还是用实验数据说话吧。


设计实验——对某gameObject上的Transform组件,采用不同的方法,访问8×1024×1024次。

方案一,实现缓存gameObject上transform组件的引用,然后所有访问都直接取用缓存的引用;

方案二,在脚本中直接以Component类的transform属性调用的方式访问(U3D脚本都是从MoniBehaviour类派生,而MonoBehaviour又派生自Component类,所以在脚本中可以直接访问transform属性,这一点相信很多人都知道);

方案三,在脚本中以gameObject.transform的形式访问组件(注意哦,很多人都有这个习惯,觉得组件是gameObject的组件,所以访问时都喜欢加上gameObject);

方案四,在脚本中以GetComponent<Transform>()函数访问组件;

实验结果——方案一约30ms,方案二约550ms,方案三约850ms,方案四约1700ms。


吃惊吧?transform属性访问的开销居然比直接访问引用要大这么多!而且通过gameObject转一道手之后开销居然又增加了这么多!不过还好,直接属性调用还是比用GetComponent要快的多……别太早下结论,Transform组件在每个GameObject实例上都有,对它的访问是不会失败的,那么如果被访问的组件在GameObject上不存在的时候呢?比如访问一个Rigidbody组件,而gameObject上没有挂载这样的组件,这时有会怎样?接着看实验。


设计实验——尝试对某gameObject上的Rigidbody组件进行访问8×1024×1024次。

方案一,gameObject上确实挂载了Rigidbody组件,事先缓存组件的引用,访问时取用缓存的引用;

方案二,gameObject上确实挂载了Rigidbody组件,脚本中以Component类的rigidbody属性访问组件;

方案三,gameObject上确实挂载了Rigidbody组件,脚本中以gameObject.rigidbody的方式访问组件;

方案四,gameObject上确实挂载了Rigidbody组件,脚本中以GetComponent<Rigidbidy>()访问组件;

方案五,gameObject上没有Rigidbody组件,事先缓存组件(当然获取到的是null),访问时取用引用;

方案六,gameObject上没有Rigidbody组件,脚本中以Component类的rigidbidy属性访问组件;

方案七,gameObject上没有Rigidbody组件,脚本中以gameObject.rigidbody方式访问组件;

方案八,gameObject上没有Rigidbody组件,脚本中以GetComponent<Rigidbody>()访问组件;

实验结果——方案一约30ms,方案二约800ms,方案三约1200ms,方案四约1700ms,方案五约30ms,方案六不少于60000ms,方案七不少于60000ms,方案八约1700ms。


更吃惊了吧?这一次的实验,前四组跟上一次实验差别不太大,但对rigidbody属性的访问还是要比transform属性慢了一点,后四组数据才是吃惊的根源,在组件不存在的情况下,通过属性访问组件居然会有如此大的额外开销!相比之下,GetComponent方法倒是不在乎组件是否真的存在,开销一如既往。

由于属性实现的代码无法通过ILSpy查看,所以在这里我只能用猜的了。首先是,U3D在实现这些组件访问属性的时候,必然做了各种查询和容错处理,绝非简单的缓存和取用引用那么简单,这也是属性访问比事先缓存引用的访问方式要慢那么多的原因;其次,Transform组件在每个GameObject实例上都必然存在,因此transform属性的实现比其他组件访问属性的实现必然要少那么一些步骤,这就造成对transform属性的访问要比其他组件属性快上一些;最后,当组件不存在时,对组件属性的访问应该是走入了大量的容错处理代码,这就造成这种情况下属性访问开销大增。

从这个实验又可以得出结论,我们的脚本代码里经常会需要访问gameObject引用或者某个组件的引用,最好的方式当然是在脚本Awake的时候就把这些可能访问的东西都缓存下来;如果需要访问临时gameObject实例的某属性或者临时某组件的gameObject实例,在能够确保组件一定存在的情况下,可以用属性访问,毕竟它们比GetComponent要快上一倍,但是如果不能确定组件是否存在,甚至是需要对组件的存在性做判断时,一定不要用对属性访问结果判空的方式,而要用GetComponent,这里面节省的开销不是一点半点。

读书人网 >编程

热点推荐