项目开发过程中,特效往往是资源和性能开销的大户,一般都会使用池的方式来管理和重用特效资源,而不是每次在需要时就重新加载生成,这就需要有一套机制能够在特效播放结束后,自动回收特效,因而需要提前知道特效的播放时间长度。这是一种比较常见的情况,也可能会有其他的需求,比如特效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#扩展方法简介]。
在单个粒子系统中,有众多参数使用这个类型封装,因而我们有必要对其进行扩展,以简化其数值的获取。
用于获取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;
}
一个具体的粒子特效,往往由多个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());
}
}
扩展 GameObject 类型以实现对任意嵌套结构的特效对象进行时间计算,并提取出一些可能需要变动的参数:
includeChildren
: 是否包含所有子节点对象includeInactive
: 是否包含非激活对象allowLoop
: 是否允许计算循环特效的时长,如果允许则计算一个周期,不允许则返回-1public 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;
}
}
}
很简单,对任意特效Prefab
或者实例gameObject
对象直接调用该方法即可:
var duration = gameObject.GetParticleDuration();