Create a Destructible Environment in Matter.js
This article explains how to build a destructible environment using Matter.js, the 2D rigid body physics engine for the web. You will learn the primary architectural approaches for destruction—including grid-based voxel decomposition and dynamic polygon slicing—along with collision force detection, performance optimization techniques, and practical implementation steps to make terrain or obstacles break apart dynamically upon impact.
Core Architectural Approaches
Matter.js does not provide built-in destructible geometry out of the box, so you must implement it using one of two primary architectural strategies:
1. Tile or Grid-Based Destruction
This method builds structures out of small, individual rigid bodies (such as squares or hexagons) assembled into a compound body or a single composite. When a block receives sufficient damage or force, it is removed from the physics world.
- Best for: Pixel-art games, brick-breaker mechanics, digging games, and voxel-style destruction.
- Pros: Simple to implement, stable physics behavior, predictable collisions.
- Cons: High body counts can quickly degrade performance if not properly managed.
2. Dynamic Polygon Slicing
This method models an object as a single concave or convex polygon. Upon impact, geometry clipping algorithms (using external libraries like PolyK or poly-decomp) slice the polygon along a cut-plane or impact radius, replacing the original body with two or more smaller child bodies.
- Best for: Slicing mechanics, procedural terrain craters, and realistic shattering.
- Pros: Smooth, organic-looking destruction with fewer total bodies.
- Cons: Complex polygon decomposition math; potential physics instabilities with tiny degenerate triangles.
Step-by-Step Implementation: Grid-Based Destruction
The grid-based approach is the most reliable starting point for real-time web games.
Step 1: Generate the Destructible Structure
Group individual blocks together using Matter.Composite
to maintain organizational control over the destructible zone.
const { Engine, Render, Runner, Bodies, Composite, Events } = Matter;
const engine = Engine.create();
const world = engine.world;
const blockSize = 20;
const rows = 10;
const cols = 15;
const destructibleComposite = Composite.create({ label: 'DestructibleTerrain' });
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
const block = Bodies.rectangle(
200 + c * blockSize,
150 + r * blockSize,
blockSize,
blockSize,
{
isStatic: true,
label: 'DestructibleBlock',
render: { fillStyle: '#885533' }
}
);
// Custom health or durability attribute
block.health = 30;
Composite.add(destructibleComposite, block);
}
}
Composite.add(world, destructibleComposite);Step 2: Measure Impact Force via Collision Events
To make destruction dynamic, remove blocks based on kinetic energy
rather than any simple contact. Listen to the
collisionStart or collisionActive event on the
engine.
Events.on(engine, 'collisionStart', (event) => {
event.pairs.forEach((pair) => {
const { bodyA, bodyB } = pair;
const block = bodyA.label === 'DestructibleBlock' ? bodyA :
bodyB.label === 'DestructibleBlock' ? bodyB : null;
const projectile = block === bodyA ? bodyB : bodyA;
if (block && projectile && projectile.label === 'Projectile') {
// Calculate relative velocity (kinetic energy estimation)
const speed = Math.hypot(
projectile.velocity.x,
projectile.velocity.y
);
const damage = speed * projectile.mass * 5;
block.health -= damage;
if (block.health <= 0) {
// Destroy the block
Composite.remove(destructibleComposite, block);
}
}
});
});Step 3: Area of Effect (Explosive) Destruction
For explosions that clear an entire radius of terrain:
- Obtain the center point and radius of the explosion.
- Query the composite using
Matter.Query.pointor filter bodies by distance. - Remove affected static blocks.
- Optionally spawn small, non-static particle debris to simulate realistic rubble without permanent performance costs.
function explode(world, composite, epicenter, radius) {
const bodies = Composite.allBodies(composite);
bodies.forEach((body) => {
const dist = Math.hypot(body.position.x - epicenter.x, body.position.y - epicenter.y);
if (dist <= radius) {
Composite.remove(composite, body);
}
});
}Optimizing Performance
Physics engines struggle when tracking hundreds of dynamic or closely packed bodies simultaneously. Use these rules to maintain 60 FPS:
- Keep Terrain Static: Keep destructible pieces
static (
isStatic: true) until they are damaged or detached. Static bodies bypass expensive velocity and trajectory integration steps. - Enable Body Sleeping: Set
enableSleeping: trueon your engine instance. Inactive bodies enter sleep mode and stop triggering collision solver calculations. - Batch Visual Rendering: Do not use default
Matter.js canvas debug rendering for large destructible environments.
Track the physics data in Matter.js, but render the visual layer to an
offscreen buffer, WebGL texture, or single HTML5
<canvas>via tilemap blitting. - Prune Tiny Bodies: Set a minimum volume threshold. When slicing polygons or shattering blocks, discard fragments smaller than a predetermined size to avoid solver instability and micro-collisions.