Why Fast Objects Pass Through Walls in Matter.js
Fast-moving bodies sometimes pass straight through obstacles in Matter.js due to a physics engine phenomenon known as "tunneling." This article explains the mechanics of discrete collision detection that cause high-speed bodies to miss collisions, along with practical, actionable methods to resolve the issue in your physics simulations.
The Root Cause: Discrete Collision Detection
Matter.js relies on discrete collision detection. Instead of continuously tracking an object's trajectory through space and time, the engine samples positions at discrete intervals (time steps). During each step, the engine updates an object's position based on its velocity and checks whether any bodies currently overlap.
When an object moves at a high velocity, the distance it travels in a single time step can exceed the thickness of the obstacle it is approaching. In one frame, the body is completely in front of the barrier; in the very next frame, it is already completely behind it. Because the body was never intersecting the barrier at the exact instant an engine step was calculated, Matter.js registers no collision, and the body passes cleanly through.
Increase Engine Sub-stepping
The most direct way to mitigate tunneling is to increase the frequency of physics updates. By reducing the duration of each time step, objects move smaller distances between checks:
- Manual Sub-stepping: Instead of calling
Engine.update(engine, delta)once per render frame, call it multiple times with a fraction of the delta (for example, four updates withdelta / 4). This allows the engine to catch collisions that occur between display frames without altering game speed. - Solver Iterations: Increasing
engine.positionIterationsandengine.velocityIterationshelps resolve tight constraints and resting collisions, though it will not fix pure tunneling if the time step itself remains too large.
Increase Obstacle Thickness
Because tunneling occurs when an object's displacement per step exceeds an obstacle's depth, increasing the thickness of static walls is an easy and performant solution:
- Expand the dimensions of the static body backward, away from the playable area.
- If a visual asset must remain thin, decouple the visual representation from the physics body by using an invisible, thick physics collider underneath the thin sprite.
Cap Maximum Velocity
If sub-stepping introduces too much CPU overhead, clamp the maximum speed of moving bodies. You can implement a pre-update listener to limit velocity:
Matter.Events.on(engine, 'beforeUpdate', () => {
const maxSpeed = 20;
if (body.speed > maxSpeed) {
Matter.Body.setSpeed(body, maxSpeed);
}
});Ensure that maxSpeed * (deltaTime / 1000) is strictly
less than the minimum thickness of any collider in the scene.
Implement Raycasting for Swept Detection
For critical projectiles like bullets, discrete checks are often insufficient. You can implement manual Continuous Collision Detection (CCD) using raycasting:
- Store the body's previous position before each step.
- In the
beforeUpdateor custom update cycle, cast a ray from the previous position to the predicted next position usingMatter.Query.ray(bodies, startPoint, endPoint). - If an intersection is detected along that vector, manually trigger the collision response or position the body directly at the impact point before the regular solver runs.