How to Create Zero-Gravity Zones in Matter.js
This guide explains how to implement localized zero-gravity chambers in Matter.js simulations that otherwise use standard global gravity. By utilizing sensor bodies, collision detection events, and per-step upward force compensation, you can dynamically neutralize gravity for specific objects entering designated zones without affecting the rest of the simulation.
The Core Concept
Matter.js defines gravity at the engine level
(engine.gravity), applying a uniform downward acceleration
to all dynamic bodies in the world. Because Matter.js does not support
native per-region gravity settings, you must create a zero-gravity
effect by applying an equal and opposite counter-force to any body
occupying the designated zero-gravity volume.
The force of gravity exerted on any body in Matter.js is:
\[\text{Force}_{\text{gravity}} = \text{mass} \times (\text{gravity.y} \times \text{gravity.scale})\]
To make a body appear weightless, apply a counter-force in the negative direction on every physics step while the body resides inside the chamber.
Step 1: Define the Chamber Sensor
Create a body to act as the chamber boundary. Set
isSensor: true so other bodies can enter and pass through
it without physical collision resistance.
const zeroGChamber = Matter.Bodies.rectangle(400, 300, 200, 200, {
isSensor: true,
isStatic: true,
render: {
fillStyle: 'rgba(0, 150, 255, 0.2)',
strokeStyle: '#0096ff',
lineWidth: 2
}
});
Matter.Composite.add(engine.world, zeroGChamber);Step 2: Track Active Bodies
Maintain a Set to store references to any bodies
currently inside the chamber. Use Matter.js collision events to add
bodies when they enter and remove them when they leave.
const floatingBodies = new Set();
Matter.Events.on(engine, 'collisionStart', (event) => {
event.pairs.forEach((pair) => {
if (pair.bodyA === zeroGChamber && !pair.bodyB.isStatic) {
floatingBodies.add(pair.bodyB);
} else if (pair.bodyB === zeroGChamber && !pair.bodyA.isStatic) {
floatingBodies.add(pair.bodyA);
}
});
});
Matter.Events.on(engine, 'collisionEnd', (event) => {
event.pairs.forEach((pair) => {
if (pair.bodyA === zeroGChamber) {
floatingBodies.delete(pair.bodyB);
} else if (pair.bodyB === zeroGChamber) {
floatingBodies.delete(pair.bodyA);
}
});
});Step 3: Apply the Counter-Force
Hook into the beforeUpdate event on the engine. For
every registered body inside the chamber, compute the exact upward force
needed to cancel out the global gravity setting and apply it to the
body's center of mass.
Matter.Events.on(engine, 'beforeUpdate', () => {
const gravity = engine.gravity;
const gravityScale = gravity.scale;
floatingBodies.forEach((body) => {
// Calculate opposite force components
const counterForceY = -body.mass * (gravity.y * gravityScale);
const counterForceX = -body.mass * (gravity.x * gravityScale);
Matter.Body.applyForce(body, body.position, {
x: counterForceX,
y: counterForceY
});
});
});Step 4: Fine-Tune the Environment
Objects entering a zero-gravity chamber retain their existing velocity. To simulate a dense fluid or realistic space-like damping inside the chamber, apply linear and angular drag inside the update loop:
Matter.Events.on(engine, 'beforeUpdate', () => {
const dragFactor = 0.98; // Reduces speed by 2% per tick
floatingBodies.forEach((body) => {
Matter.Body.setVelocity(body, {
x: body.velocity.x * dragFactor,
y: body.velocity.y * dragFactor
});
Matter.Body.setAngularVelocity(body, body.angularVelocity * dragFactor);
});
});This approach leaves global gravity fully functional across the rest of the canvas while providing an isolated, frictionless, or damped zero-gravity experience inside the bounded area.