Attach Particle Emitters to Matter.js Bodies
This article explains how to attach dynamic particle emitters to Matter.js physics bodies to create realistic smoke trail effects. You will learn how to decouple physics simulation from visual rendering, calculate dynamic emission offsets based on body rotation, update particle states across animation ticks, and draw fading smoke trails using the HTML5 Canvas API.
Understanding the Architecture
Matter.js is primarily a 2D physics engine, not a full visual
framework. While it includes a built-in canvas renderer
(Matter.Render), custom visual effects like smoke, fire, or
sparks are best implemented by overlaying a secondary rendering loop or
hooking directly into the engine's update cycle via
Matter.Events.
To create a smoke trail:
- Track the physical body’s current position
(
body.position) and angle (body.angle). - Calculate the local offset where the smoke should originate (such as the rear of a rocket or vehicle).
- Spawn particles at that calculated global coordinate during each physics update.
- Render and update the particles independently so they drift, expand, and fade away in the world space.
Calculating the Emission Point
If you attach a thruster or exhaust to a body, the emission point must rotate alongside the body. You can translate a local offset \((x_{offset}, y_{offset})\) relative to the body's center into world coordinates using basic trigonometry:
function getEmitterPosition(body, localOffsetX, localOffsetY) {
const cos = Math.cos(body.angle);
const sin = Math.sin(body.angle);
return {
x: body.position.x + (localOffsetX * cos - localOffsetY * sin),
y: body.position.y + (localOffsetX * sin + localOffsetY * cos)
};
}Implementing the Smoke Particle System
A basic particle system requires a collection to hold active particles, a spawn function, and an update/render step.
class SmokeParticle {
constructor(x, y, baseAngle) {
this.x = x;
this.y = y;
// Slight spread backwards relative to body direction
const spread = (Math.random() - 0.5) * 0.5;
const speed = Math.random() * 1.5 + 0.5;
const travelAngle = baseAngle + Math.PI + spread;
this.vx = Math.cos(travelAngle) * speed;
this.vy = Math.sin(travelAngle) * speed;
this.radius = Math.random() * 4 + 4;
this.maxLife = Math.random() * 30 + 40;
this.life = this.maxLife;
}
update() {
this.x += this.vx;
this.y += this.vy;
this.radius += 0.3; // Smoke expands as it cools
this.life -= 1;
}
draw(ctx) {
const alpha = Math.max(this.life / this.maxLife, 0) * 0.4;
ctx.save();
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = `rgba(180, 180, 180, ${alpha})`;
ctx.fill();
ctx.restore();
}
isDead() {
return this.life <= 0;
}
}Hooking into the Matter.js Event Loop
Use Matter.Events.on(engine, 'afterUpdate', ...) to bind
particle emission directly to the physics simulation step. This ensures
that the smoke trails stay synchronized with your physics rate even
during frame drops.
const particles = [];
const exhaustOffset = { x: -30, y: 0 }; // Behind the center of mass
// Hook into the physics engine update
Matter.Events.on(engine, 'afterUpdate', () => {
// Only emit smoke if the body is moving significantly or actively accelerating
const speed = body.speed;
if (speed > 0.5) {
const origin = getEmitterPosition(body, exhaustOffset.x, exhaustOffset.y);
// Spawn 1-2 particles per update
particles.push(new SmokeParticle(origin.x, origin.y, body.angle));
}
// Update existing particles
for (let i = particles.length - 1; i >= 0; i--) {
particles[i].update();
if (particles[i].isDead()) {
particles.splice(i, 1);
}
}
});Rendering the Effect
Render the particles underneath or directly on top of the Matter.js
bodies. If you use Matter.Render, you can hook into its
render event:
Matter.Events.on(render, 'afterRender', () => {
const ctx = render.context;
for (let i = 0; i < particles.length; i++) {
particles[i].draw(ctx);
}
});If you are using an external renderer such as PixiJS or a separate
HTML5 Canvas, call particle.draw(ctx) in your main
requestAnimationFrame render loop right before or after you
draw your physics sprites.