How to Implement Sub-Stepping in Matter.js

Sub-stepping is an essential physics technique used to eliminate collision tunneling and enhance stability for fast-moving bodies by dividing a single frame's duration into smaller, discrete time increments. This article demonstrates how to implement manual sub-stepping in Matter.js by replacing the default runner with a custom requestAnimationFrame loop that executes Matter.Engine.update multiple times per display refresh.

Why Use Sub-Stepping?

By default, Matter.js processes physics updates once per animation frame (roughly every 16.67 milliseconds at 60Hz). When bodies move at high velocities, they may travel completely through thin barriers or other objects between frames—an issue known as tunneling.

Dividing the frame time into smaller slices (sub-steps) and running the physics solver for each slice ensures that collision detection occurs more frequently, preventing overlap errors and yielding more accurate physical responses.

Disabling the Default Runner

To control engine updates manually, avoid using Matter.Runner.run(runner, engine). The built-in Matter.Runner manages its own loop and does not natively support sub-stepping. Instead, manage the animation loop directly using standard browser APIs.

Implementing the Custom Update Loop

To execute multiple sub-steps per frame, determine the target frame delta and divide it by the desired number of iterations. Inside your requestAnimationFrame callback, run a loop that repeatedly calls Matter.Engine.update(engine, subDelta).

const Engine = Matter.Engine;
const Render = Matter.Render;

// 1. Create engine and renderer
const engine = Engine.create();
const render = Render.create({
  element: document.body,
  engine: engine,
  options: {
    width: 800,
    height: 600,
    wireframes: false
  }
});

// 2. Configure sub-stepping parameters
const subSteps = 4;
const targetFps = 60;
const frameDelta = 1000 / targetFps;
const subDelta = frameDelta / subSteps;

// 3. Define the custom game loop
function loop() {
  // Execute the physics engine multiple times per frame
  for (let i = 0; i < subSteps; i++) {
    Engine.update(engine, subDelta);
  }

  // Render the updated physics state once per frame
  Render.world(render);

  requestAnimationFrame(loop);
}

// 4. Start the loop
requestAnimationFrame(loop);

Key Considerations