How to Update Matter.js Engine Manually
This article explains how to manually update the physics simulation
in Matter.js instead of relying on the built-in runner. By using the
Matter.Engine.update method within your own game loop, you
can achieve precise control over frame rates, pause states, physics
synchronization with rendering libraries like PixiJS or Three.js, and
variable time steps like slow-motion effects.
Bypassing the Built-In Runner
By default, Matter.js provides Matter.Runner to
automatically step the physics world forward using the browser's refresh
rate. To manually control updates, you simply omit initializing the
runner or stop an existing one:
// Stop an existing runner if one is active
Matter.Runner.stop(runner);Once the runner is stopped or avoided entirely, the engine will only advance when you explicitly command it to.
Using Engine.update
The core function for manual advancement is
Matter.Engine.update(engine, [delta]).
engine: The instance ofMatter.Engineyou want to advance.delta(optional): The time step in milliseconds. If omitted, it defaults to1000 / 60(approximately 16.666 ms, representing 60 frames per second).
// Advance the simulation by one standard frame (16.67ms)
Matter.Engine.update(engine);
// Or advance by a custom delta (e.g., 20ms)
Matter.Engine.update(engine, 20);Implementing
a Custom Loop with requestAnimationFrame
To integrate Matter.js into a standard render loop, call
Matter.Engine.update() inside a
requestAnimationFrame callback.
const { Engine, World, Bodies } = Matter;
const engine = Engine.create();
let lastTime = performance.now();
function gameLoop(currentTime) {
// Calculate the elapsed time in milliseconds
const delta = currentTime - lastTime;
lastTime = currentTime;
// Manually step the physics engine
Matter.Engine.update(engine, delta);
// Run custom rendering or game logic here
requestAnimationFrame(gameLoop);
}
// Start the loop
requestAnimationFrame(gameLoop);Implementing a Fixed Time Step
Physics engines can become unstable if the delta time fluctuates too wildly. For the most consistent collision detection and deterministic behavior, use a fixed time step combined with an accumulator:
const fixedDelta = 1000 / 60; // 60 updates per second
let accumulator = 0;
let lastTime = performance.now();
function fixedLoop(currentTime) {
const frameTime = currentTime - lastTime;
lastTime = currentTime;
// Prevent spiral of death on long frame delays
accumulator += Math.min(frameTime, 250);
// Consume accumulated time in fixed chunks
while (accumulator >= fixedDelta) {
Matter.Engine.update(engine, fixedDelta);
accumulator -= fixedDelta;
}
// Render your graphics here
requestAnimationFrame(fixedLoop);
}
requestAnimationFrame(fixedLoop);Controlling Simulation Speed
Because you control the delta argument passed to
Engine.update(), you can manipulate the perceived passage
of time directly:
- Pause: Do not call
Engine.update(). - Slow Motion: Pass a reduced delta (e.g.,
(1000 / 60) * 0.5for half-speed). - Fast-Forward: Pass an increased delta or call
Engine.update()multiple times within a single rendering frame.