How to Create Shockwaves in Matter.js
This article explains how to implement a radial shockwave in
Matter.js that propels nearby dynamic bodies outward while ignoring
static geometry. By filtering bodies in the physics world, calculating
directional vectors from the explosion epicenter, and applying
distance-attenuated impulses using Matter.Body.applyForce,
you can simulate realistic explosive forces without altering immovable
terrain or boundaries.
Understanding the Shockwave Logic
A standard shockwave requires an epicenter, an effective radius, and
a maximum force magnitude. To ensure static bodies remain unaffected,
you must explicitly check the isStatic flag on each body
before applying any force vector.
The process involves four steps:
- Identify all bodies within the physics world or a defined bounding area.
- Filter out static bodies (
body.isStatic === true). - Calculate the distance and direction from the epicenter to each dynamic body.
- Scale the force based on proximity and apply it using
Matter.Body.applyForce.
Implementation
The following function creates a shockwave at a specified coordinate:
function createShockwave(engine, epicenter, radius, forceMagnitude) {
const bodies = Matter.Composite.allBodies(engine.world);
bodies.forEach(body => {
// Ignore static bodies completely
if (body.isStatic) return;
// Calculate vector from shockwave center to the body
const delta = Matter.Vector.sub(body.position, epicenter);
const distance = Matter.Vector.magnitude(delta);
// Check if the body is within the shockwave radius
if (distance > 0 && distance < radius) {
// Normalize direction vector
const direction = Matter.Vector.normalise(delta);
// Linear falloff: force is strongest at center, zero at edge
const falloff = 1 - (distance / radius);
const force = Matter.Vector.mult(direction, forceMagnitude * falloff);
// Apply force directly to the body's center of mass
Matter.Body.applyForce(body, body.position, force);
}
});
}Key Considerations
Preventing Zero-Distance Errors
If a dynamic body sits exactly at the epicenter,
distance will equal zero. Normalizing a zero-length vector
results in NaN coordinates, which corrupts the body's
transform matrix. Always verify that distance > 0 before
normalizing.
Choosing an Attenuation Model
The example above uses a linear falloff
(1 - distance / radius), providing predictable results in
2D space. For a more aggressive, realistic blast, use an inverse-square
falloff:
const falloff = 1 / Math.max(1, distance * distance * 0.001);Performance Optimization with Spatial Queries
If your scene contains hundreds of bodies, iterating over
Composite.allBodies() every frame can degrade performance.
Instead, use Matter.Query.region() with an axis-aligned
bounding box (AABB) to only evaluate bodies within the shockwave's
square bounds:
const bounds = {
min: { x: epicenter.x - radius, y: epicenter.y - radius },
max: { x: epicenter.x + radius, y: epicenter.y + radius }
};
const nearbyBodies = Matter.Query.region(Matter.Composite.allBodies(engine.world), bounds);Looping over nearbyBodies rather than the entire world
composite reduces vector calculations and ensures optimal frame
rates.