【Unity / Particle】计算 粒子特效 ParticleSystem 的播放时间长度

需求分析

项目开发过程中,特效往往是资源和性能开销的大户,一般都会使用池的方式来管理和重用特效资源,而不是每次在需要时就重新加载生成,这就需要有一套机制能够在特效播放结束后,自动回收特效,因而需要提前知道特效的播放时间长度。这是一种比较常见的情况,也可能会有其他的需求,比如特效A、B、C需要在每个特效播放完后立即播放另一个特效,实现连续播放多个特效的效果,由于特效是可能随时修改效果的,特效的播放时长不宜在程序内写死,做成配置也并不够方便,最好能够自动获取特效的播放时长。

先看单个Particle System,单个粒子系统的时间长度,最直观的是由其duration参数决定,但粒子系统还有很多其他参数会影响播放时长,比如开始播放延时startDelay,还有很多其他参数也会影响播放时长,这儿就不一一列举了,有兴趣的可以参考粒子系统的相关文档。

duration参数是一个float类型,直接计算即可。我们继续看startDelay参数,它在编辑器中,可以选择固定数值、随机范围和曲线等方式来决定实际值,因而我们无法直接获取其数值,反编译源代码可以看到以下类型。
ParticleSystem.MinMaxCurve类型的startDelay:

      public ParticleSystem.MinMaxCurve startDelay
  	[NativeType(CodegenOptions.Custom, "MonoMinMaxCurve", Header = "Runtime/Scripting/ScriptingCommonStructDefinitions.h")]
    [Serializable]
    public struct MinMaxCurve

由于一个粒子特效是由多个对象和数据类型嵌套而成,为了提高方法重用率和扩展性,我们决定使用扩展方法的方式,分层实现最终需求,关于扩展方法的介绍和使用,可以参考系列文章[C#扩展方法简介]。

01.扩展 MinMaxCurve

在单个粒子系统中,有众多参数使用这个类型封装,因而我们有必要对其进行扩展,以简化其数值的获取。
用于获取MinMaxCurve类型实际最大值的扩展方法:

        public static float GetMaxValue(this ParticleSystem.MinMaxCurve minMaxCurve)
        {
            switch (minMaxCurve.mode)
            {
                case ParticleSystemCurveMode.Constant:
                    return minMaxCurve.constant;
                case ParticleSystemCurveMode.Curve:
                    return minMaxCurve.curve.GetMaxValue();
                case ParticleSystemCurveMode.TwoConstants:
                    return minMaxCurve.constantMax;
                case ParticleSystemCurveMode.TwoCurves:
                    var ret1 = minMaxCurve.curveMin.GetMaxValue();
                    var ret2 = minMaxCurve.curveMax.GetMaxValue();
                    return ret1 > ret2 ? ret1 : ret2;
            }
            return -1f;
        }

02.扩展 Particle System

一个具体的粒子特效,往往由多个ParticleSystem组成,因而这个特效的实际播放时长,由其中时间最长的那个粒子系统所决定,所以我们需要单独计算每个粒子系统的时间并取最大值,所以我们先扩展出计算单一粒子系统时间的方法。

对于 looping 的循环特效,可以认为不存在时长直接返回-1,也可以在必要时计算其一个周期的时长。

        public static float GetDuration(this ParticleSystem particle, bool allowLoop = false)
        {
            if (!particle.emission.enabled) return 0f;
            if (particle.main.loop && !allowLoop)
            {
                return -1f;
            }
            if (particle.emission.rateOverTime.GetMinValue() <= 0)
            {
                return particle.main.startDelay.GetMaxValue() + particle.main.startLifetime.GetMaxValue();
            }
            else
            {
                return particle.main.startDelay.GetMaxValue() + Mathf.Max(particle.main.duration, particle.main.startLifetime.GetMaxValue());
            }
        }

03.扩展 GameObject

扩展 GameObject 类型以实现对任意嵌套结构的特效对象进行时间计算,并提取出一些可能需要变动的参数:

  • includeChildren : 是否包含所有子节点对象
  • includeInactive : 是否包含非激活对象
  • allowLoop : 是否允许计算循环特效的时长,如果允许则计算一个周期,不允许则返回-1
public static float GetParticleDuration(this GameObject gameObject, bool includeChildren = true, bool includeInactive = false, bool allowLoop = false)
        {
            if (includeChildren)
            {
                var particles = gameObject.GetComponentsInChildren<ParticleSystem>(includeInactive);
                var duration = -1f;
                for (var i = 0; i < particles.Length; i++)
                {
                    var ps = particles[i];
                    var time = ps.GetDuration(allowLoop);
                    if (time > duration)
                    {
                        duration = time;
                    }
                }

                return duration;
            }
            else
            {
                var ps = gameObject.GetComponent<ParticleSystem>();
                if (ps != null)
                {
                    return ps.GetDuration(allowLoop);
                }
                else
                {
                    return -1f;
                }
            }
        }

04.使用

很简单,对任意特效Prefab或者实例gameObject对象直接调用该方法即可:

var duration = gameObject.GetParticleDuration();

你可能感兴趣的:(Unity3D,C#,.NET)