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);
}