Manual Physics Steps in Matter.js
Turn-based simulations require strict control over physics
calculations rather than relying on a continuous, real-time animation
loop. This guide explains how to disable the default runner in Matter.js
and manually advance the physics engine using the
Engine.update method. By decoupling the physics calculation
from the browser's refresh rate, you can execute discrete, deterministic
physics steps, fast-forward actions, and synchronize your rendering
strictly when a player executes a turn.
1. Avoid Using
Matter.Runner
By default, Matter.js applications use
Matter.Runner.run(runner, engine) to tie updates to the
browser's requestAnimationFrame. In a turn-based game, you
must omit Matter.Runner completely, or stop it using:
Matter.Runner.stop(runner);Without the runner, the simulation remains paused until you explicitly tell the engine to advance.
2. Advance the Engine
Using Engine.update
To advance the physics world manually, call
Matter.Engine.update(). This method takes two primary
arguments: your engine instance and a fixed time step
(delta) in milliseconds.
const delta = 1000 / 60; // Represents one frame at 60 FPS (~16.66ms)
// Advance the simulation by exactly one step
Matter.Engine.update(engine, delta);Using a fixed delta value ensures that physics calculations remain deterministic and consistent across different devices and hardware capabilities.
3. Synchronize the Renderer
If you are using Matter.Render for visuals, the canvas
will not update automatically without the continuous runner. You must
manually call Render.world after your physics updates:
Matter.Engine.update(engine, delta);
Matter.Render.world(render);4. Executing Multi-Step Turns
In turn-based games (such as artillery games, billiards, or bowling), a turn often requires the physics to run until all moving bodies come to a complete stop.
You can run multiple steps synchronously in a loop, or iteratively with an animation frame if you want the player to see the turn play out:
function stepTurn(engine, render, maxSteps = 300) {
const delta = 1000 / 60;
for (let i = 0; i < maxSteps; i++) {
Matter.Engine.update(engine, delta);
// Check if all bodies have settled
const allSleeping = engine.world.bodies.every(body => body.isSleeping || body.isStatic);
if (allSleeping) {
break;
}
}
// Draw the final state of the turn
Matter.Render.world(render);
}To enable the isSleeping check, ensure you set
enableSleeping: true in your engine configuration:
const engine = Matter.Engine.create({
enableSleeping: true
});Complete Implementation Example
const { Engine, Render, Bodies, Composite } = Matter;
// 1. Initialize Engine and Renderer
const engine = Engine.create({ enableSleeping: true });
const render = Render.create({
element: document.body,
engine: engine,
options: { width: 800, height: 600, wireframes: false }
});
// 2. Add sample bodies
const ball = Bodies.circle(100, 100, 20, { restitution: 0.8 });
const ground = Bodies.rectangle(400, 580, 810, 40, { isStatic: true });
Composite.add(engine.world, [ball, ground]);
// Initial render
Render.world(render);
// 3. Trigger manual step on demand (e.g., button click or game action)
function triggerTurnStep() {
const fixedDelta = 1000 / 60;
// Step the simulation once
Engine.update(engine, fixedDelta);
// Update the visual frame
Render.world(render);
}
document.getElementById('turnButton').addEventListener('click', triggerTurnStep);