Forces
Open the live demo · Read the source · View on GitHub
An emitter decides where a particle starts. Everything that happens to it afterwards is a stack of small, independent affectors, run in order, once a step.
Step 1: A stack of small rules #
Each affector does one thing. ParticleGravity pulls particles down.
ParticleDrag bleeds off speed exponentially, so a burst slows into a drift
instead of flying away forever. ParticleWind pushes everything one way.
ParticleTurbulence swirls particles through a standing flow field, which is
what makes smoke curl instead of rising in a straight line. ParticleSpin
turns a billboard over its life.
_affectors = <ParticleAffector>[
const ParticleGravity(-4.0),
const ParticleDrag(0.4),
ParticleWind(Vector3(0.6, 0.0, 0.0)),
const ParticleTurbulence(strength: 1.5, scale: 0.8),
const ParticleSpin(turnsPerSecond: 1.2),
];
Step 2: Attach them to an effect #
The list goes on the effect, in the order it should run. Nothing about the emitter changes: this is the same cone shape from the emitters page, with five things now happening to whatever it throws.
_debrisEffect = ParticleEffect(
count: _count,
emitter: const ConeEmitter(speed: Range(1.5, 3.0), halfAngleDegrees: 30),
lifetime: const Range(1.5, 2.5),
size: const Range(0.06, 0.1),
color: Vector4(0.9, 0.6, 0.3, 1.0),
affectors: _affectors,
);
Step 3: Burst it, and keep it moving #
A burst is a single instant; the affectors above only do anything once the system is advanced.
_particles.burst(
_debrisEffect,
Vector3.zero(),
direction: Vector3(0.0, 1.0, 0.0),
);
_contributor = context.renderer.addContributor(
ParticleContributor(_particles),
);
_particles.advance(dt);
Step 4: What changed #
Gravity and drag together mean a particle thrown upward stops climbing as fast as it started, and spin means it is turning by the time you look at it. Neither is visible from the pool's own numbers, so the check on this page runs the same affector list against a particle of its own and reads what came out the other side.
// The system keeps no public handle on one particle, so the claim is
// checked the way the affectors are unit tested: run the same list
// against a particle of our own and read what changed.
final Particle sample = Particle()
..velocity.setValues(0.0, 2.0, 0.0)
..age = 0.4
..lifetime = 2.0
..seed = 0.5;
for (final ParticleAffector affector in _affectors) {
affector.apply(sample, 1 / 60);
}
if (sample.velocity.y >= 2.0) {
throw StateError('gravity and drag should have slowed the climb');
}
if (sample.rotation == 0.0) {
throw StateError('spin should have turned the particle');
}
Note. Affectors are stateless and shared between every particle that uses them.
ParticleSpinreads each particle's own seed for its starting angle, which is what stops a whole burst turning in lockstep like a sheet of aligned squares.