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.

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.


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:

  1. Obtain the center point and radius of the explosion.
  2. Query the composite using Matter.Query.point or filter bodies by distance.
  3. Remove affected static blocks.
  4. 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: