Screen Wrapping Boundaries in Matter.js
This article explains how to implement seamless screen wrapping boundaries in Matter.js physics simulations. Instead of constraining dynamic bodies with static barrier walls, you will learn how to detect when a body exits the viewport and instantly reposition it on the opposite edge. By tracking physics update events and applying manual coordinate adjustments using built-in Matter.js methods, you can easily build classic wrap-around mechanics suitable for arcade-style games like Asteroids.
The Screen Wrapping Concept
In a standard physics engine setup, boundaries are typically created by placing thick, static rectangular bodies along the edges of the canvas. Screen wrapping—often referred to as toroidal space—removes these static walls. Instead, the simulation monitors each body's coordinates during every engine tick. When an object crosses an outer threshold, its position is shifted across the canvas while maintaining its current linear and angular velocities.
Listening to Engine Update Events
To check body positions continuously, hook into the
beforeUpdate or afterUpdate event of your
Matter.js engine. The beforeUpdate event is generally
preferred because adjusting positions before physics calculations run
prevents visual jittering and collision glitches.
Use Matter.Events.on to attach a callback function to
your engine instance:
const { Events, Body, Composite } = Matter;
Events.on(engine, 'beforeUpdate', () => {
// Wrapping logic runs here
});Writing the Wrapping Logic
When repositioning bodies, account for their dimensions using an offset (or padding) based on their bounding box. If an object is repositioned the exact moment its center reaches an edge, the object will noticeably pop out of view. Using an offset ensures the body fully leaves one side before appearing on the other.
Use Matter.Body.setPosition rather than directly
mutating body.position.x or body.position.y.
Direct mutation can break the engine's internal velocity and
previous-position caches.
Here is the implementation:
const width = 800;
const height = 600;
Events.on(engine, 'beforeUpdate', () => {
const bodies = Composite.allBodies(engine.world);
bodies.forEach((body) => {
// Skip static bodies such as scenery or anchors
if (body.isStatic) return;
// Calculate body dimensions to prevent visual popping
const bodyWidth = body.bounds.max.x - body.bounds.min.x;
const bodyHeight = body.bounds.max.y - body.bounds.min.y;
const halfWidth = bodyWidth / 2;
const halfHeight = bodyHeight / 2;
let newX = body.position.x;
let newY = body.position.y;
let wrapped = false;
// Horizontal wrapping
if (body.position.x > width + halfWidth) {
newX = -halfWidth;
wrapped = true;
} else if (body.position.x < -halfWidth) {
newX = width + halfWidth;
wrapped = true;
}
// Vertical wrapping
if (body.position.y > height + halfHeight) {
newY = -halfHeight;
wrapped = true;
} else if (body.position.y < -halfHeight) {
newY = height + halfHeight;
wrapped = true;
}
// Apply updated position safely
if (wrapped) {
Body.setPosition(body, { x: newX, y: newY });
}
});
});Best Practices
- Selective Wrapping: Do not wrap all bodies
universally if your simulation includes off-screen spawners, particles,
or UI elements. Add a custom property (such as
body.canWrap = true) to dynamic bodies and check for this flag before calculating boundaries. - Sleeping Bodies: If your world uses sleeping bodies
(
engine.enableSleeping = true), moving a sleeping body off-screen will not automatically wake it. Screen wrapping should wake the body usingMatter.Sleeping.set(body, false)if necessary. - Compound Bodies: When wrapping compound bodies,
body.boundsaccurately accounts for the entire compound structure, so the same offset logic will work without separating child parts.