Prevent Object Tunneling in Matter.js
In Matter.js, fast-moving or lightweight dynamic objects can completely bypass static barriers between animation frames, a phenomenon known as "tunneling." This happens because the engine evaluates collisions at discrete time steps rather than using continuous collision detection (CCD). This guide covers the most effective strategies to prevent tunneling in Matter.js, including increasing engine iterations, implementing manual sub-stepping, expanding wall boundaries, and clamping body velocities.
Increase Engine Iterations
Matter.js relies on an iterative solver to calculate positions and velocities. By default, dynamic bodies moving at high speeds might penetrate or pass through thin bodies before the solver can resolve the overlap. Increasing the iteration counts enhances collision resolution accuracy:
const engine = Engine.create({
positionIterations: 10, // Default is 6
velocityIterations: 8 // Default is 4
});Higher iteration counts improve stability when objects compress or collide, but this alone may not completely resolve tunneling if an object moves farther than the wall's thickness in a single frame.
Implement Engine Sub-Stepping
If an object travels fast enough that its position in frame \(A\) is in front of a wall and in frame \(B\) is behind it, the engine never detects a collision. You can eliminate this by splitting your physics update into smaller time slices (sub-steps) per render frame:
const subSteps = 4;
const delta = 1000 / 60; // 60 FPS standard delta
const subDelta = delta / subSteps;
function updatePhysics() {
for (let i = 0; i < subSteps; i++) {
Engine.update(engine, subDelta);
}
}Sub-stepping reduces the distance an object travels during any individual calculation, ensuring the collision is detected while the object is still intersecting the barrier.
Thicken Static Boundaries
Thin static walls are the most frequent cause of tunneling. Make static boundary bodies significantly thicker than their visible appearance on screen.
You can extend the body's thickness outward (away from the playable area) or decouple the physical body size from the visual render size:
// A boundary that appears 10px wide can have a 100px collision depth
const wall = Bodies.rectangle(x, y, 100, height, {
isStatic: true,
render: {
visible: true // or render a custom sprite sized to the visible area
}
});A thicker barrier ensures that even high-speed bodies will land inside the collision shape during an update step, allowing the solver to push them back out.
Clamp Maximum Velocity
Prevent objects from reaching speeds that exceed the width of your boundaries. You can cap body velocity within an update event:
const MAX_SPEED = 20;
Events.on(engine, 'beforeUpdate', () => {
const velocity = dynamicBody.velocity;
const speed = Vector.magnitude(velocity);
if (speed > MAX_SPEED) {
const unitVector = Vector.normalise(velocity);
Body.setVelocity(dynamicBody, Vector.mult(unitVector, MAX_SPEED));
}
});Setting MAX_SPEED lower than the thickness of your
thinnest barrier guarantees that an object cannot skip past the wall in
a single frame.
Summary Checklist
To reliably prevent tunneling:
- Ensure wall bodies are thicker than the maximum distance an object can travel in one frame.
- Use multiple
Engine.updatecalls with a fractionaldelta(sub-stepping) instead of a single large update. - Increase
positionIterationsto make collision responses stiffer. - Clamp dynamic object velocities to safe limits.