How to Build a Hovercraft in Matter.js

This article explains how to build a responsive, floating hovercraft in Matter.js capable of transitioning seamlessly over terrain and water bodies. By setting up low-friction physics bodies, simulating dynamic upward lift through vertical spring forces or raycasting, and using collision sensors to detect water surfaces, you can achieve an authentic drifting experience that responds dynamically to different surface environments.

1. Defining the Hovercraft Body

A standard rigid body will stick or tumble if it contacts obstacles directly. To create a hovercraft, construct a main hull with rounded edges or a compound body to minimize sharp collisions with uneven ground.

const { Bodies, Body, Composite } = Matter;

const hovercraft = Bodies.rectangle(400, 200, 80, 30, {
  chamfer: { radius: 10 },
  density: 0.002,
  friction: 0.001,      // Minimal surface contact friction
  frictionAir: 0.01,    // Low air resistance to allow drifting
  restitution: 0.2      // Slight bounce on hard impact
});

Composite.add(engine.world, hovercraft);

2. Simulating the Air Cushion Lift

Real hovercrafts float on a cushion of pressurized air. In Matter.js, you can simulate this cushion without complex aerodynamics by applying an upward force whenever the craft approaches a surface beneath it, effectively acting as a suspension spring.

Use an update loop listener to evaluate the craft's height above the ground or water:

Matter.Events.on(engine, 'beforeUpdate', () => {
  const hoverHeight = 40;
  const stiffness = 0.0005;
  const damping = 0.0002;

  // Cast a ray or measure distance to ground surfaces below the craft
  const groundDistance = getDistanceToSurface(hovercraft.position);

  if (groundDistance < hoverHeight) {
    const compression = hoverHeight - groundDistance;
    const upwardForce = (compression * stiffness) - (hovercraft.velocity.y * damping);

    Body.applyForce(hovercraft, hovercraft.position, {
      x: 0,
      y: -Math.max(0, upwardForce)
    });
  }
});

3. Creating Land and Water Environments

To distinguish between solid terrain and water bodies:

  1. Solid Land: Standard static bodies with moderate restitution and friction.
  2. Water Bodies: Static sensor bodies (isSensor: true). Sensors detect overlaps without physically blocking the vehicle, allowing the craft to skim over or slightly submerge.
const land = Bodies.rectangle(200, 500, 400, 60, { isStatic: true });

const water = Bodies.rectangle(600, 520, 400, 60, {
  isStatic: true,
  isSensor: true,
  label: 'water'
});

Composite.add(engine.world, [land, water]);

4. Adjusting Dynamics Between Land and Water

A hovercraft drifts differently across land compared to water. Water creates more fluid drag on any submerged component, while flat land typically allows higher sideways slip.

Listen for collision events or active sensor overlaps to dynamically update the hovercraft’s damping:

let isOverWater = false;

Matter.Events.on(engine, 'collisionActive', (event) => {
  const pairs = event.pairs;
  isOverWater = pairs.some(pair => 
    (pair.bodyA === hovercraft && pair.bodyB.label === 'water') ||
    (pair.bodyB === hovercraft && pair.bodyA === 'water')
  );
});

Matter.Events.on(engine, 'beforeUpdate', () => {
  if (isOverWater) {
    // Water creates higher drag and dampens lateral sliding
    hovercraft.frictionAir = 0.03;
  } else {
    // Land allows faster, slicker drift
    hovercraft.frictionAir = 0.008;
  }
});

5. Implementing Drift and Propulsion Controls

To steer and propel the hovercraft, apply directional forces aligned with the craft's current angle rather than directly modifying velocity. This ensures realistic inertia and rotational momentum.

function applyThrust(craft, power) {
  const angle = craft.angle;
  const force = {
    x: Math.cos(angle) * power,
    y: Math.sin(angle) * power
  };
  Body.applyForce(craft, craft.position, force);
}

function applySteering(craft, torque) {
  Body.setAngularVelocity(craft, craft.angularVelocity + torque);
}

// User input mapping
if (keys.ArrowUp) applyThrust(hovercraft, 0.004);
if (keys.ArrowLeft) applySteering(hovercraft, -0.02);
if (keys.ArrowRight) applySteering(hovercraft, 0.02);

By decoupling propulsion direction from linear velocity and keeping contact friction negligible, the craft rotates freely while preserving directional momentum, achieving smooth drifting mechanics across all surface types.