How to Create Low-Gravity Zones in Matter.js
This article explains how to build localized low-gravity areas in Matter.js where physics bodies experience increased jump heights and prolonged float times. Because Matter.js applies gravity globally across the entire engine world, regional gravity requires setting up sensor trigger zones, tracking overlapping bodies, and applying counteracting upward forces during each engine update cycle.
Understanding the Matter.js Gravity Limitation
By default, Matter.js handles gravity uniformly across all dynamic
bodies via engine.world.gravity. It does not provide
built-in localized gravity volumes. To simulate an area of reduced
gravity—such as an anti-gravity chamber or underwater zone—you must
neutralize a fraction of the downward gravitational pull by applying an
upward force to any dynamic body inside that boundary.
Step 1: Create the Low-Gravity Sensor Zone
Define the zone as a static body with isSensor: true. A
sensor detects collisions and overlaps without causing physical
deflections or solid boundaries.
const { Bodies, Composite } = Matter;
// Create a sensor rectangle representing the low-gravity zone
const lowGravityZone = Bodies.rectangle(400, 300, 200, 400, {
isStatic: true,
isSensor: true,
render: {
fillStyle: 'rgba(0, 150, 255, 0.2)', // Semi-transparent visual cue
strokeStyle: 'rgba(0, 150, 255, 0.8)',
lineWidth: 1
}
});
Composite.add(engine.world, lowGravityZone);Step 2: Track Bodies Inside the Zone
Maintain a registry (such as a Set) of dynamic bodies
currently floating inside the zone using Matter.js collision events.
const { Events } = Matter;
const bodiesInZone = new Set();
Events.on(engine, 'collisionStart', (event) => {
event.pairs.forEach((pair) => {
if (pair.bodyA === lowGravityZone && !pair.bodyB.isStatic) {
bodiesInZone.add(pair.bodyB);
} else if (pair.bodyB === lowGravityZone && !pair.bodyA.isStatic) {
bodiesInZone.add(pair.bodyA);
}
});
});
Events.on(engine, 'collisionEnd', (event) => {
event.pairs.forEach((pair) => {
if (pair.bodyA === lowGravityZone) {
bodiesInZone.delete(pair.bodyB);
} else if (pair.bodyB === lowGravityZone) {
bodiesInZone.delete(pair.bodyA);
}
});
});Step 3: Apply the Counteracting Anti-Gravity Force
Listen to the beforeUpdate engine event. During every
simulation tick, iterate over all registered bodies and apply an upward
force proportional to their mass.
Global gravity applies a downward acceleration of
engine.world.gravity.y * engine.world.gravity.scale. To
reduce gravity by a specific percentage (e.g., 75% reduction), apply an
upward force offsetting that fraction.
const { Body } = Matter;
const GRAVITY_REDUCTION_FACTOR = 0.75; // 0 = standard gravity, 1 = zero gravity
Events.on(engine, 'beforeUpdate', () => {
const worldGravity = engine.world.gravity;
const gravityScale = worldGravity.scale;
const gravityY = worldGravity.y;
bodiesInZone.forEach((body) => {
// Calculate standard gravitational pull per tick: F = m * a
const standardDownwardForce = body.mass * (gravityY * gravityScale);
// Calculate upward counter-force
const upwardForce = standardDownwardForce * GRAVITY_REDUCTION_FACTOR;
// Apply the opposing force at the body's center of mass
Body.applyForce(body, body.position, {
x: 0,
y: -upwardForce
});
});
});Step 4: Fine-Tuning Jump Heights and Float Times
While reducing gravity lowers downward acceleration, fine-tuning player responsiveness and hang time involves two additional parameters:
1. Air Friction Adjustment
Bodies in low gravity often feel unnatural if they drop too fast once
upward velocity reaches zero. Modifying frictionAir inside
the zone creates a gliding effect:
const ORIGINAL_AIR_FRICTION = 0.01;
const LOW_GRAV_AIR_FRICTION = 0.05;
Events.on(engine, 'collisionStart', (event) => {
event.pairs.forEach((pair) => {
const dynamicBody = pair.bodyA === lowGravityZone ? pair.bodyB : pair.bodyA;
if (dynamicBody && !dynamicBody.isStatic) {
dynamicBody.frictionAir = LOW_GRAV_AIR_FRICTION;
}
});
});
Events.on(engine, 'collisionEnd', (event) => {
event.pairs.forEach((pair) => {
const dynamicBody = pair.bodyA === lowGravityZone ? pair.bodyB : pair.bodyA;
if (dynamicBody && !dynamicBody.isStatic) {
dynamicBody.frictionAir = ORIGINAL_AIR_FRICTION;
}
});
});2. Upward Jump Impulses
Because downward gravity resistance is drastically reduced, identical
jumping impulses applied to a character will naturally launch them
significantly higher and keep them airborne longer. If players can
initiate jumps from within the zone, you can either keep the base jump
impulse unchanged for extreme height or clamp the maximum upward
velocity (body.velocity.y) to keep movement
controllable.