Skip to content

Particles

samme edited this page Nov 4, 2024 · 43 revisions

Particles are fun and not so difficult, but there are a lot of details. Read the docs and examples closely.

Particle emitter

const emitter = this.add.particles(0, 0, 'textureKey', config);

The emitter game object acts like a Container for its particles. Its x, y, scaleX, scaleY, and rotation transform the entire particle field.

There are lots of config options and you'll usually set these when you create the emitter.

You can reconfigure an emitter at any time with setConfig(config), or you can set individual properties like setFrequency(frequency). But updating ops values can be a little more complicated.

Emitters are on (flowing) when created unless you use { emitting: false }.

Particles last for lifespan and then disappear. The default lifespan is 1000 ms. You can use a long or even infinite lifespan, but in that case you'll need to eventually turn the emitter flow off or remove the particles manually.

An emitter's emitCallback and deathCallback are called at the start and end of each particle's life.

Flow

Emitters in flow mode (the default mode) release particles periodically, based on frequency and quantity:

Value Description
Period frequency ms
Frequency 1000 / frequency cycles per second
Output 1000 * quantity / frequency particles per second

The default flow is { frequency: 0, quantity: 1 }, which is 1 particle per game loop step. If you want a flow rate independent of the step rate, use a positive value for frequency. { frequency: 16.666 } is about 60 cycles per second.

The emitter creates particles on demand and then recycles the expired ones. When the flow is stable, the particle counts are approximately:

State Count
Alive quantity * lifespan / frequency
Dead quantity
Total quantity * (1 + lifespan / frequency)

The flow can be on or off. You can stop emitter flow with stop() and restart with start(), or toggle the emitter's emitting property between true and false. start() starts a new flow cycle immediately, resetting all the counters.

Explode

explode(count, x, y) switches out of flow mode and releases particles immediately. It uses the passed arguments (if given) or the emitter's current quantity, particleX, and particleY.

Emit particles manually

emitParticle(count, x, y) works like explode() but it doesn't switch the emitter out of flow mode.

emitParticleAt(x, y, count) is the same but with a different argument order.

Position

The final position of emitted particles is determined by

(x, y) → (particleX, particleY) or (follow + followOffset) → emit zone (x, y)

The emitter's own (x, y) shifts the position of all particles, like a Container.

Motion

Particles are "fired" from the emitter with a velocity determined by

  • the moveToX and moveToY coordinates if set; or
  • speedX and speedY if set; or
  • angle and speed

With moveTo the particles move continuously toward the target coordinates and arrive at the end of their life.

Emitter ops

Emit Phase Update Phase
accelerationX accelerationX
accelerationY accelerationY
alpha alpha
angle
bounce bounce
delay
lifespan
maxVelocityX maxVelocityX
maxVelocityY maxVelocityY
moveToX
moveToY
particleX particleX
particleY particleY
quantity
rotate rotate
scaleX scaleX
scaleY scaleY
speedX
speedY
tint tint

Emitter ops (operations? operators? — we will never know) can hold a single value or a rule for generating a value.

For "emit-only" ops you can give a single value, a random range, a stepped range, or a custom function.

For the other ops you can give a random range or a stepped range (for the emit phase); or a single value, an eased range, or a custom function (for the update phase); or an object with onEmit and onUpdate for both phases.

These will make more sense if you look at the examples.

It's a little complicated to change ops values individually. As of v3.86, you have to use the same type the op was loaded with. For example, if you created the emitter with alpha: { min, max }, you can only use setParticleAlpha({ min, max }) after that. The foolproof method is to reload the op instead:

emitter.ops.alpha.loadConfig({ alpha: { start: 0, end: 1 } });

Since speedX and speedY are emit only, you need to use a particle processor to change a particle's velocity after it's emitted.

Easings

Ops with an update phase can be eased, e.g.,

emitter.setAlpha({ start: 0, end: 1 });

The ease duration is the particle lifespan. You can use your own easing function for customization:

Yoyo ease
const Ease = Phaser.Math.Easing.Quadratic.InOut;
const YoYo = (t) => t < 0.5 ? 2 * t : 2 - 2 * t;

emitter.setAlpha({
  start: 0,
  end: 1,
  ease: (t) => Ease(YoYo(t))
});

Stepped ranges

A stepped range advances at one step per particle emit, so the step cycle interval (start to end) in milliseconds is

frequency * steps / quantity

For example,

{ frequency: 10, quantity: 1, angle: { start: 0, end: 360, steps: 100 } }

steps the angle from 0 to 360° over 1 second, where each step is 3.6° per 10ms (1000 ms == 10 ms * 100 steps / 1).

When quantity equals steps, the whole range is encompassed at once, making a uniform spread. For example,

{ frequency: 1000, quantity: 100, angle: { start: 0, end: 360, steps: 100 } }

makes a burst of 100 particles every second.

Death zones

Death zones kill particles when they enter or leave the zone, as you specify. Position the zone accordingly.

Emit zones

Random emit zones release each particle at a random position given by a source. The source can be a Geom shape, path, curve, or anything with a getRandomPoint(point) method. Geom sources give a random position within their area. getRandomPoint(point) is called each time a particle is released.

Edge emit zones release each particle from a sequence of positions given by a source. The source can be a Geom shape, path, curve, or anything with a getPoints(quantity, stepRate) method. Geom sources place the points on their edges, start to end. The point sequence is generated once when the zone is created; you can call edgeZone.updateSource() to create a new sequence.

Bounds

When set the particles bounce off the interior edges, depending on the emitter bounce.

Particle processors

Particle processors change the velocity of emitted particles.

const processor = {
  active: true,
  update: function (particle, deltaMs, deltaUs, lifeT) {
    particle.velocityX *= 1;
    particle.velocityY *= 1;
  }
};

emitter.addParticleProcessor(processor);

Gravity wells

Gravity wells are a built-in processor.

emitter.createGravityWell({ x: 0, y: 0, power: 2, epsilon: 100, gravity: 50 });

Particles

You receive a particle in an emitter's deathCallback and emitCallback callbacks; an emitter op's onEmit and onUpdate callbacks; and a particle processor's update callback.

  • life is its lifespan (duration) in ms, calculated from the lifespan value of the emitter.
  • lifeCurrent is its remaining life in ms, decreasing from life to 0.
  • life - lifeCurrent is its elapsed life in ms, increasing from 0 to life.
  • lifeT is (life - lifeCurrent) / life, increasing from 0 to 1.
Clone this wiki locally