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.