How to Use beforeUpdate in Matter.js

This article explains how to leverage the beforeUpdate event in Matter.js to implement continuous custom physics and game logic. By hooking into the physics engine immediately before every simulation step, developers can reliably handle real-time mechanics such as player input, custom forces, continuous trajectory adjustments, and frame-by-frame constraint modifications.

Understanding the beforeUpdate Event

In Matter.js, the physics simulation runs on a discrete loop managed by Matter.Engine. During each tick, the engine advances the simulation, resolves collisions, and updates body positions and velocities.

The beforeUpdate event fires right before the engine computes these changes. Because it runs prior to collision detection and position resolution, it is the safest and most effective lifecycle hook for altering the physical state of bodies without causing visual jitter or physics instability.

Registering the Event Listener

To listen for the beforeUpdate event, use the Matter.Events.on method, passing your engine instance, the event name, and a callback function.

const { Engine, Events } = Matter;

const engine = Engine.create();

Events.on(engine, 'beforeUpdate', (event) => {
    // Custom logic runs every frame here
});

The event object passed to the callback provides contextual details, including event.timestamp, which indicates the current simulation time.

Applying Continuous Forces and Velocity

The most common application of beforeUpdate is applying persistent forces or manually regulating velocity. Because forces applied via Matter.Body.applyForce are consumed in a single simulation step, they must be continuously reapplied inside beforeUpdate to simulate sustained effects like thrusters, wind, or localized gravity.

const { Engine, Events, Body, Vector } = Matter;

const engine = Engine.create();
const rocket = Matter.Bodies.rectangle(400, 500, 40, 80);

Events.on(engine, 'beforeUpdate', (event) => {
    // Apply an upward thrust continuously
    const upwardForce = Vector.create(0, -0.005);
    Body.applyForce(rocket, rocket.position, upwardForce);

    // Limit maximum falling speed
    const maxVelocityY = 10;
    if (rocket.velocity.y > maxVelocityY) {
        Body.setVelocity(rocket, {
            x: rocket.velocity.x,
            y: maxVelocityY
        });
    }
});

Handling Continuous User Input

Handling continuous keyboard or gamepad controls inside standard DOM events can result in variable execution rates that do not match the physics update cycle. Using beforeUpdate synchronizes user input directly with the simulation step.

const keys = {
    ArrowLeft: false,
    ArrowRight: false
};

window.addEventListener('keydown', (e) => { keys[e.code] = true; });
window.addEventListener('keyup', (e) => { keys[e.code] = false; });

Events.on(engine, 'beforeUpdate', () => {
    const moveSpeed = 5;

    if (keys.ArrowLeft) {
        Body.setVelocity(playerBody, { x: -moveSpeed, y: playerBody.velocity.y });
    }
    if (keys.ArrowRight) {
        Body.setVelocity(playerBody, { x: moveSpeed, y: playerBody.velocity.y });
    }
});

Implementing Dynamic Constraints and Tracking

You can use beforeUpdate to manually tether objects, align camera viewports to a moving body, or make a body look toward a specific target coordinate:

Events.on(engine, 'beforeUpdate', () => {
    // Rotate body towards a target point (e.g., mouse position)
    const angle = Math.atan2(
        target.y - follower.position.y,
        target.x - follower.position.x
    );
    Body.setAngle(follower, angle);
});

Performance Best Practices