Create Sticky Quicksand Zones in Matter.js

Creating sticky quicksand zones in Matter.js requires combining a non-colliding sensor body with custom velocity damping applied on every engine update. This article covers how to define a designated trigger area using sensor properties, track bodies entering and leaving the zone, and rapidly decelerate both linear and angular velocities to simulate thick, viscous fluid resistance.

1. Define the Quicksand Area as a Sensor

Matter.js bodies normally bounce or block other rigid bodies. To allow objects to enter the quicksand zone while still detecting their presence, set the body's isSensor property to true and mark it as isStatic.

const quicksandZone = Matter.Bodies.rectangle(400, 300, 250, 150, {
  isStatic: true,
  isSensor: true,
  render: {
    fillStyle: '#c2b280',
    opacity: 0.6
  }
});

Matter.Composite.add(engine.world, quicksandZone);

2. Track Entities Inside the Zone

Maintain a set or array of dynamic bodies currently inside the quicksand volume by listening to the engine's collision events.

const submergedBodies = new Set();

Matter.Events.on(engine, 'collisionStart', (event) => {
  event.pairs.forEach((pair) => {
    if (pair.bodyA === quicksandZone && !pair.bodyB.isStatic) {
      submergedBodies.add(pair.bodyB);
    } else if (pair.bodyB === quicksandZone && !pair.bodyA.isStatic) {
      submergedBodies.add(pair.bodyA);
    }
  });
});

Matter.Events.on(engine, 'collisionEnd', (event) => {
  event.pairs.forEach((pair) => {
    if (pair.bodyA === quicksandZone) {
      submergedBodies.delete(pair.bodyB);
    } else if (pair.bodyB === quicksandZone) {
      submergedBodies.delete(pair.bodyA);
    }
  });
});

3. Apply Linear and Angular Velocity Damping

To create a thick, sticky effect, modify the velocity components of all trapped bodies directly inside the beforeUpdate event. Multiplying velocities by a fraction below 1.0 reduces speed rapidly without destabilizing the physics engine.

const LINEAR_DAMPING = 0.85;   // Lower values mean thicker mud (range: 0.0 - 1.0)
const ANGULAR_DAMPING = 0.70;  // Rapidly suppresses rotation
const SINK_GRAVITY = 0.2;      // Optional: slow downward pull

Matter.Events.on(engine, 'beforeUpdate', () => {
  submergedBodies.forEach((body) => {
    // Damp linear velocity directly
    Matter.Body.setVelocity(body, {
      x: body.velocity.x * LINEAR_DAMPING,
      y: (body.velocity.y * LINEAR_DAMPING) + SINK_GRAVITY
    });

    // Damp angular velocity directly
    Matter.Body.setAngularVelocity(body, body.angularVelocity * ANGULAR_DAMPING);
  });
});

4. Alternative: Area Query Approach

If dynamic objects are spawned or destroyed while inside the quicksand, collision listeners may occasionally miss cleanup events. You can replace the event listener approach with Matter.Query.region inside the update step for a more robust implementation:

Matter.Events.on(engine, 'beforeUpdate', () => {
  const overlappingBodies = Matter.Query.region(
    Matter.Composite.allBodies(engine.world),
    quicksandZone.bounds
  );

  overlappingBodies.forEach((body) => {
    if (body === quicksandZone || body.isStatic) return;

    Matter.Body.setVelocity(body, {
      x: body.velocity.x * 0.85,
      y: body.velocity.y * 0.85 + 0.15
    });

    Matter.Body.setAngularVelocity(body, body.angularVelocity * 0.75);
  });
});

Using this approach guarantees that any body currently overlapping the quicksand boundary has its movement heavily suppressed immediately. Adjust the damping constants toward 0.0 for concrete-like entrapment or closer to 0.95 for light liquid drag.