记一次Unity粒子系统引起的问题的修复及性能优化过程

发表于2017-07-03
评论0 8.6k浏览


发现问题:今天开发英雄技能时遇到一个问题:场景中某些特效(使用的是Unityshuriken ParticleSystem)在设置为隐藏后(通过设置gameObjectlayer为“Hide”)再显示(通过设置gameObjectlayer为“Actor”或“Particle”)时,显示不出来了,重新对该特效的gameObject进行SetActive(true)操作后才又显示出来。断点看发现该ParticleSystemisPlayingfalseisStoppedtrue。跟了我们代码,没有发现在对gameObject设置layer时有对ParticleSystem.Stop()接口的主动调用。猜想是Unity引擎(version4.6.9)在粒子系统被摄像机culling掉时调用了Stop停掉了该粒子,而重新被渲染时由于某种原因(很可能是bug)没有重新Play


着手修改:既然粒子系统在Hide的时候被停掉了,那么在将粒子设为可见layer时就手动调用一次Play吧。代码类似于

记一次Unity粒子系统引起的问题的修复及性能优化过程

但是这样写发现有严重的性能问题,会产生较大的CPU消耗和gcAlloc。由于该段代码在gameObjectSetLayerRecursively中,该函数不但几乎每帧都会被设置actor可见性的上层逻辑调用,且同一帧内会被递归调用多次。


      开始优化:想办法减少该段代码的调用次数:实际上我们只有父对象为英雄类型的actor时才会用到这类会在多次ShowHide之间一直持续的特效,且仅在一个接口ForceSetVisible中真正改变actor的可见性,那么其他调用SetLayer的地方都不需要执行上图中的代码。优化后的代码类似于

记一次Unity粒子系统引起的问题的修复及性能优化过程

上层只在确实需要时才传bPlayParticletrue,执行粒子的Play动作。


继续优化:优化之后代码调用次数大大减少,局内性能提高了不少。但还是时不时要来几k到几十k的内存分配(每个SetLayerRecursively会分配几k)。

记一次Unity粒子系统引起的问题的修复及性能优化过程

记一次Unity粒子系统引起的问题的修复及性能优化过程

还需要优化。注意到新增代码的消耗只可能出现在两个地方:gameObject.GetComponentparticle.Play,前者大家知道Unity的实现效率不高,并且会有gcAlloc;后者如果Unity在实现时没有判断当前是否已经在Play,而是一股脑直接执行播放动作(gameObject.SetActive就干过这事),那么也会有效率问题。经查看源码发现particle.Play里是提前判断了当前状态的,排除这个地方。然后就是想办法优化GetComponent:在产生根粒子对象时,若其父actor为英雄,则将该对象缓存到父actor上;而根粒子对象本身是从内存池中取出的,在内存池初始化时就其ParticleSystem组件缓存下来,该操作对每个内存池中的对象只执行一次,且发生在开局前的loading阶段。这样为了解决粒子bug而引入的CPU消耗在局内就基本为0了。


其他方案:后面在网上看到也有其他人遇到了同样的问题,有人提出了一个解决方案:

记一次Unity粒子系统引起的问题的修复及性能优化过程

经验证是能解决这个问题的。另外勾上“Sub Emitters”之后出现这个tips,也证实了之前对自动剔除的判断。

记一次Unity粒子系统引起的问题的修复及性能优化过程

但我们的特效一般不需要sub emitter,勾上这个之后可能会有副作用,所以不采用这个方法。


后续情况:最近找到一篇详细解释ParticleSystem的剔除机制的blog,写的比较详细,但没提到上述这个bug,也许最新版本的Unity已经修复。感兴趣的同学可以看一下

https://blogs.unity3d.com/cn/2016/12/20/unitytips-particlesystem-performance-culling/

 

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

0个评论