Over-life curves
Open the live demo · Read the source · View on GitHub
ParticleSizeOverLife only ever goes from one number to another, and
ParticleFade only ever goes down. A puff of smoke that grows and then
shrinks needs both shapes on the same value, which is what a curve is for: a
value described as a handful of keys, sampled wherever a particle's life
happens to be.
Step 1: A curve for size #
Each CurveKey sits at a point in [0, 1] of the particle's life. Between
two keys the value is eased by whichever shape the earlier key names, and
outside the first and last key the curve holds still rather than
extrapolating.
// Puffs up to one and a half times its birth size by the middle of its
// life, then shrinks away, easing smoothly through the peak rather than
// arriving at it with a corner.
_size = ParticleCurve(<CurveKey>[
CurveKey(0.0, 0.4, ease: KeyEase.smooth),
CurveKey(0.5, 1.5, ease: KeyEase.smooth),
CurveKey(1.0, 0.0),
]);
Step 2: A gradient for colour #
A ParticleGradient is the same idea for a colour: GradientKeys instead of
numbers, sampled the same way. Four independent channel curves would let the
keys drift apart and produce a grey frame nobody asked for; a gradient's keys
are keys of the whole colour at once.
_color = ParticleGradient(<GradientKey>[
GradientKey(0.0, Vector4(1.0, 0.95, 0.6, 1.0)),
GradientKey(0.4, Vector4(1.0, 0.4, 0.1, 1.0), ease: KeyEase.smooth),
GradientKey(1.0, Vector4(0.2, 0.05, 0.05, 0.0)),
]);
Step 3: Read by an affector #
A curve and a gradient are values, not affectors. ParticleSizeCurve and
ParticleColorGradient are the small pieces that read them every step and
write the result onto the particle, the same shape every other affector on
the modifiers page takes.
_smokeEffect = ParticleEffect(
count: _count,
emitter: const DriftEmitter(speed: Range(0.4, 0.9)),
lifetime: const Range(1.2, 2.0),
size: const Range(0.08, 0.12),
color: Vector4(1.0, 1.0, 1.0, 1.0),
affectors: <ParticleAffector>[
ParticleSizeCurve(_size),
ParticleColorGradient(_color),
],
);
Step 4: What the shape actually is #
Neither a curve nor a gradient needs a running particle to check: they are plain data, sampled directly. This page's own check does exactly that, off the same two objects the burst above draws with.
// The curve holds its end values outside its keys and rises then falls
// in between, which is what "puffs up, then shrinks" means as numbers.
if (_size.sample(0.0) >= _size.sample(0.5) ||
_size.sample(0.5) <= _size.sample(1.0)) {
throw StateError('the size curve should rise then fall');
}
// The gradient fades to nothing: alpha at the last key is zero.
final Vector4 end = Vector4.zero();
_color.sampleInto(end, 1.0);
if (end.w != 0.0) {
throw StateError('the gradient should fade to nothing');
}
Note.
KeyEase.smootheases into and out of a key rather than turning a straight corner there, which is what keeps a size that puffs up and back down from looking like it hit a wall in the middle.