|
在使用Unity的过程中,我们总是想要使我们的游戏帧数跑的更稳定。因此优化总是不可避免的。我们一般都会遵循80-20原则,去优化那些带来性能瓶颈的一小部分代码。通常这已经能够满足我们的需求。而我今天要和大家讨论的就是剩余的80%代码我们所需要注意的地方。它们不一定需要优化,但是你一定不要劣化它们。使用数组的数组,而不是多维数组
在c#中,我们可以使用形如int[j]的数组的数组,也可以使用形如int[i,j]的多维数组。那么它们在性能上有什么区别呢?通常来说,数组的数组会有更好的性能。这是因为如果用IL disassembler查看中间代码的话,你会发现多维数组的访问会多一个函数的调用。正像在StackOverflow所提到的那样。
如果你使用Unity的profiler来查看它们的区别,可以看到巨大的差异。下面是使用不同数组对大小为同样都为100万的数据进行访问所花费的时间(迭代了100次):
一维数组 660ms
数组的数组 730ms
多维数组 3470ms
可以看到,一维数组和数组的数组的差别是很小的。而多维数组由于那多出的一次额外的函数调用而造成了巨大的性能差异。所以我们应尽可能使用一维数组或数组的数组;除非有特殊需求,不要使用多维数组。
用ID访问属性
Unity的animator以及material/shader都提供了属性的访问接口。虽然形如SetFloat()的方法都提供了以字符串为索引来访问属性,但Unity在内部实际都是以ID来索引属性的。这自然是从索引效率的方面来考虑的。毕竟字符串的比对实在是太慢了。那么那些以字符串为索引的方法虽然使用方便,但是都会在内部重新计算一次hash,这对于我们应用层来说是完全没有必要,也是可以节省的。只要为每一个属性的字符串名字在初始化时生成一个hash值,之后都用这个hash值就可以了。在计算hash值时要注意的是,animator和material/shader所用的hash值计算方式并不相同,animator需要使用Animator.StringToHash,而material/shader需要使用Shader.PropertyToID。
小心Transform
我们在做各种需求的时候,经常会需要改变物体的位置、旋转。而每当我们改变Transform组件的属性时,Transform组件会立刻发出一个OnTransformChanged消息,它不仅发送给当前改变的这个物体,还会发送给它所拥有的所有组件,以及物体的子物体及其组件。在子物体比较多(层级比较多)的情况下,这种递归的开销是值得警惕的。要减少这个开销,可以从两方面入手:
尽可能减少transform的属性修改。虽然是这样说,但是这通常都是比较困难的。毕竟transform的修改大部分情况下都是必需的。不过我们可以尽可能减少不必要的属性修改;比如计算中的一些中间结果不要set回transform中,而是最后把最终结果set一次。尽可能减少子物体的数量。这通常是我们需要重点关注的。场景中的动态物体一般不要设置的层级太深。比如对于带有动画的物体,勾选Optimize Game Object。这会使物体的transform层级大大降低。一般来说都是建议勾选的。只有一种情况不能勾选:你想要在脚本中改变角色的骨骼的位置、旋转等。如果你勾选了,那么物体骨骼的transform是取不到的,即使你设置了Extra Transforms to Expose,可以取到transform,但是这已经不是那根骨骼的transform的引用了,因此,修改它并不能影响骨骼的transform。
Unity的“常量”
Unity的一些类提供了很多“常量”给开发者。比如Vector3.zero或者Matrix4x4.identity等等。他们使用起来是非常方便的。然而Unity的内部实现也许并不是你想象的这样:- static readonly Quaternion identityQuaternion = new Quaternion(0F, 0F, 0F, 1F);
- public static Quaternion identity
- { get { return identityQuaternion; }}
复制代码 而是这样的:- public static Quaternion identity
- { get {return new Quaternion(0F, 0F, 0F, 0F); }}
复制代码 显然,目前的实现(Unity5.6)每次调用会多一次构建开销。在Unity的未来版本中,“常量”的实现将会逐渐改为第一种做法。因此对于这一点大家了解一下就好。
以上谈论的严格来讲都算不上优化,但是这可以防止你劣化你的代码,使你的代码更加高效。虽然它可能没有到达性能瓶颈的地步,但是这会提高我们的整体代码质量。 |
|