How to Remove a Body on Collision in Matter.js

This article explains how to safely remove a physics body upon a collision event in Matter.js without causing runtime errors or destabilizing the physics simulation. When handling collisions, attempting to delete a body immediately within a collision callback can disrupt the engine's internal update loop. By implementing a deferred removal queue using the afterUpdate event, you can ensure that bodies are cleanly purged from the simulation world once the physics calculations are complete.

Why Immediate Removal Causes Issues

In Matter.js, the collisionStart, collisionActive, and collisionEnd events fire while the engine is in the middle of resolving its broadphase, narrowphase, and constraint-solving steps.

If you call Matter.Composite.remove(world, body) directly inside these callbacks, you mutate the array of active bodies while the engine is iterating over it. This typically results in undefined object errors, visual glitches, or broken collision pairs during the remainder of that engine step.

The Safe Method: Deferred Removal

The standard and most robust solution is to register which bodies need to be destroyed during the collision event, and then remove them during the afterUpdate event, which executes once the engine has finished all step calculations.

Implementation Steps

  1. Create a collection (such as an array or a Set) to queue bodies marked for removal.
  2. In the collisionStart event listener, identify the target bodies and add them to the queue.
  3. Listen for the afterUpdate event on the engine.
  4. Iterate over the queued bodies, remove each one via Matter.Composite.remove(), and clear the queue.

Example Code

const { Engine, Render, Runner, Bodies, Composite, Events } = Matter;

const engine = Engine.create();
const world = engine.world;

// Set to track bodies queued for removal (prevents duplicate entries)
const bodiesToRemove = new Set();

// 1. Detect collisions and queue bodies
Events.on(engine, 'collisionStart', (event) => {
    const pairs = event.pairs;

    for (let i = 0; i < pairs.length; i++) {
        const pair = pairs[i];

        // Example condition: check for a custom label or property
        if (pair.bodyA.label === 'projectile') {
            bodiesToRemove.add(pair.bodyA);
        }
        if (pair.bodyB.label === 'projectile') {
            bodiesToRemove.add(pair.bodyB);
        }
    }
});

// 2. Safely remove queued bodies after physics calculations complete
Events.on(engine, 'afterUpdate', () => {
    if (bodiesToRemove.size === 0) return;

    bodiesToRemove.forEach((body) => {
        Composite.remove(world, body);
    });

    // Clear the queue for the next step
    bodiesToRemove.clear();
});

Additional Considerations