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

IsAlive

U3D的粒子系统脚本接口相信很多人都用过,ParticleSyetem类的一系列接口都有一个bool类型的参数——withChildren,通过这个参数可以直接将相同的判断或者操作应用到一整个通过Transform父子关系树关联起来的ParticleSystem实例集合上。然而,但凡方便的功能,里面就必然有性能陷阱……

以IsAlive这个接口为例(用来判断粒子系统是否所有粒子都已经消亡,一般用在非loop的例子发射器上),看看U3D里是如何实现这个接口的:

[csharp] view plaincopy
  1. public bool IsAlive()  
  2. {  
  3.     bool withChildren = true;  
  4.     return this.IsAlive(withChildren);  
  5. }  
[csharp] view plaincopy
  1. public bool IsAlive(bool withChildren)  
  2. {  
  3.     if (withChildren)  
  4.     {  
  5.         ParticleSystem[] particleSystems = ParticleSystem.GetParticleSystems(this);  
  6.         ParticleSystem[] array = particleSystems;  
  7.         for (int i = 0; i < array.Length; i++)  
  8.         {  
  9.             ParticleSystem particleSystem = array[i];  
  10.             if (particleSystem.Internal_IsAlive())  
  11.             {  
  12.                 return true;  
  13.             }  
  14.         }  
  15.         return false;  
  16.     }  
  17.     return this.Internal_IsAlive();  
  18. }  

可以看到,如果传递的withChildren参数为true,那么函数会先尝试调用GetParticleSystems(this)来获取包括下级gameObject在内的所有能找得到的粒子系统组件,然后对这些粒子系统组件依次再调用IsAlive判断。而如果withChildren为false,就仅仅会判断自身。那么自然,开销大小与否,关键就在GetParticleSystems的实现上了。

[csharp] view plaincopy
  1. internal static ParticleSystem[] GetParticleSystems(ParticleSystem root)  
  2. {  
  3.     if (!root)  
  4.     {  
  5.         return null;  
  6.     }  
  7.     List<ParticleSystem> list = new List<ParticleSystem>();  
  8.     list.Add(root);  
  9.     ParticleSystem.GetDirectParticleSystemChildrenRecursive(root.transform, list);  
  10.     return list.ToArray();  
  11. }  
[csharp] view plaincopy
  1. private static void GetDirectParticleSystemChildrenRecursive(Transform transform, List<ParticleSystem> particleSystems)  
  2. {  
  3.     foreach (Transform transform2 in transform)  
  4.     {  
  5.         ParticleSystem component = transform2.gameObject.GetComponent<ParticleSystem>();  
  6.         if (component != null)  
  7.         {  
  8.             particleSystems.Add(component);  
  9.             ParticleSystem.GetDirectParticleSystemChildrenRecursive(transform2, particleSystems);  
  10.         }  
  11.     }  
  12. }  

U3D对获取所有下级gameObject实例上的粒子系统组件使用了递归的方式,并在递归结束后返回结果列表时做了一次列表元素复制(List.ToArray()),并且在获取粒子系统组件的时候用的是transform2.gameObject.GetComponent<ParticleSystem>(),而不是transform2.GetComponent<ParticleSystem>(),从上一篇文章里我们已经用实验证实了,前一种方式开销更大。看到这里,我们心里大概已经有谱了,那就是——效率绝对不会高到哪里去,影响性能的地方太多了……还是设计一个小实验来看看这种情况下应该用什么样的方式更好吧:

设计实验——一个两层结构,一个父gameObject挂载一个ParticleSystem组件,两个子gameObject分别挂载一个PariticleSystem组件,采用两种不同的方式对这个组合判断IsAlive各8×1024×1024次。
方案一,直接对父gameObject上的PariticleSystem调用IsAlive(true);
方案二,在循环前,先用GetComponentsInChildren将所有的PariticleSystem存入一个List,循环中对这个List做遍历,对List里每一个ParticleSystem调用IsAlive(false);
实验结果——方案一约3900ms,方案二约65ms。

结果对比很明显。其实,U3D提供的这个接口的意义在于,当不是需要进行那么频繁的调用时,可以用IsAlive(true)来省掉手动获取所有子粒子系统的过程,让代码简洁一些,虽然U3D目前对这个接口的实现有的地方还值得斟酌。ParticleSystem提供的这一族接口(IsAlive只是其中之一,此外还有Play,Pause,Stop等等),如果使用频率不是很高,比如仅仅是初始化或者销毁的时候做一次性调用,那么即便是withChildren参数是true也没有什么大不了的,还能少些很多代码,何乐而不为;但如果需要频繁调用,比如每帧都对粒子系统集合判断IsAlive,这种情况下,一定不能懒惰,该写的东西还是要写的。另外值得注意的一点,IsAlive这一族接口的无参形式,是默认withChildren为true的,使用的时候可别搞错了。

PS,留意一下GetDirectParticleSystemRecursive的实现方式,你会发现它有一个递归条件,就是节点上必须要有PariticleSystem组件,在递归过程中,一旦发现某个节点上没有ParticleSystem组件时,父子关系树上的这一枝就算遍历到头了,再往下即便是还有ParticleSystem存在也会被忽略。因此,如果你面对的ParticleSystem集合就恰好存在这样的断层,那最好还是自己勤快一点,自己动手用GetComonentsInChildren来查找所有的粒子系统组件。













本文转蓬莱仙羽51CTO博客,原文链接:http://blog.51cto.com/dingxiaowei/1366356,如需转载请自行联系原作者
上一篇:find 命令详解


下一篇:在循环中删除list中的元素