Build Destructible Wooden Crates in Matter.js
This guide explains how to implement destructible wooden crates in Matter.js that shatter into smaller fragments when subjected to high-energy impacts. You will learn how to configure destructible physics bodies, calculate collision impact forces using collision events, replace intact crates with dynamic debris upon breakage, and manage fragment lifecycles to maintain optimal physics performance.
1. Defining the Destructible Crate
To make an object breakable, create a standard rectangular body and attach custom properties to track its state, such as its break threshold and health.
const { Bodies, Composite } = Matter;
function createCrate(x, y, size) {
const crate = Bodies.rectangle(x, y, size, size, {
density: 0.004,
friction: 0.5,
restitution: 0.1,
render: { fillStyle: '#8B5A2B' }
});
// Custom metadata for destruction
crate.isDestructible = true;
crate.crateSize = size;
crate.impactThreshold = 8; // Minimum relative speed required to shatter
return crate;
}2. Detecting Impact Force via Collision Events
Matter.js does not calculate internal stress automatically, but you
can detect destructive impacts by listening to the
collisionStart event and measuring the relative velocity
between colliding pairs.
Matter.Events.on(engine, 'collisionStart', (event) => {
event.pairs.forEach((pair) => {
const { bodyA, bodyB } = pair;
// Calculate relative velocity
const relativeVelocity = {
x: bodyA.velocity.x - bodyB.velocity.x,
y: bodyA.velocity.y - bodyB.velocity.y
};
const speed = Math.hypot(relativeVelocity.x, relativeVelocity.y);
if (bodyA.isDestructible && speed > bodyA.impactThreshold) {
shatterCrate(engine.world, bodyA, speed);
} else if (bodyB.isDestructible && speed > bodyB.impactThreshold) {
shatterCrate(engine.world, bodyB, speed);
}
});
});3. Spawning Fragments and Applying Outward Force
When the impact threshold is reached, remove the original crate from the world and replace it with a cluster of smaller bodies representing the fragments. Apply an outward velocity or force vector to simulate the explosive dispersal of the shattered wood.
function shatterCrate(world, crate, impactSpeed) {
// Prevent duplicate triggers if hit multiple times in the same tick
if (crate.isDestroyed) return;
crate.isDestroyed = true;
const { x, y } = crate.position;
const size = crate.crateSize;
const divisions = 3; // Produces a 3x3 grid of 9 fragments
const fragmentSize = size / divisions;
const fragments = [];
Composite.remove(world, crate);
for (let i = 0; i < divisions; i++) {
for (let j = 0; j < divisions; j++) {
const offsetX = (i - (divisions - 1) / 2) * fragmentSize;
const offsetY = (j - (divisions - 1) / 2) * fragmentSize;
const fragment = Bodies.rectangle(
x + offsetX,
y + offsetY,
fragmentSize,
fragmentSize,
{
density: crate.density * 0.8,
friction: 0.6,
restitution: 0.3,
collisionFilter: { group: -1 }, // Prevents debris from snagging on itself immediately
render: { fillStyle: '#A0522D' }
}
);
// Disperse fragments outward from center
const forceMagnitude = 0.002 * impactSpeed;
const angle = Math.atan2(offsetY, offsetX) + (Math.random() - 0.5) * 0.5;
Matter.Body.applyForce(fragment, fragment.position, {
x: Math.cos(angle) * forceMagnitude,
y: Math.sin(angle) * forceMagnitude
});
fragments.push(fragment);
}
}
Composite.add(world, fragments);
cleanupFragments(world, fragments);
}4. Cleaning Up Debris
A high number of active bodies in Matter.js can quickly cause performance degradation. Set a timer to remove the spawned fragments from the physics world once the destruction animation has concluded.
function cleanupFragments(world, fragments, delay = 3000) {
setTimeout(() => {
fragments.forEach((fragment) => {
Composite.remove(world, fragment);
});
}, delay);
}