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
- Create a collection (such as an array or a
Set) to queue bodies marked for removal. - In the
collisionStartevent listener, identify the target bodies and add them to the queue. - Listen for the
afterUpdateevent on the engine. - 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
- Disabling Collisions Immediately: If you need a
body to stop interacting with other elements instantly without waiting
for the end of the tick, set
body.collisionFilter.mask = 0or changebody.isSensor = trueinside the collision callback before queuing it. - Cleaning Up References: If your application
maintains custom references, arrays, or rendering sprites paired with
the physics bodies, ensure those are disposed of within the same
afterUpdateroutine to prevent memory leaks.