Create Pressure Plates and Doors in Matter.js

This guide explains how to construct an interactive pressure plate trigger system in Matter.js that opens and closes a mechanical door. By configuring a sensor body to detect collisions without physically impeding objects, listening to collision events, and programmatically translating the door's position, you can build responsive, physics-driven environmental puzzles and mechanics for web-based games.

1. Define the Physics Bodies

To build this mechanism, you need three primary physics elements: a standard dynamic body (such as a player or crate), a static sensor to act as the pressure plate, and a static body for the door.

Setting isSensor: true on the pressure plate allows other bodies to pass through it while still triggering collision events.

const { Engine, Render, Runner, Bodies, Composite, Events, Body } = Matter;

const engine = Engine.create();
const world = engine.world;

// 1. Dynamic Actor (e.g., Player or Crate)
const player = Bodies.rectangle(400, 100, 40, 40, { 
    label: 'player',
    restitution: 0.2 
});

// 2. Pressure Plate (Sensor Body)
const pressurePlate = Bodies.rectangle(400, 580, 100, 10, {
    isStatic: true,
    isSensor: true,
    label: 'pressurePlate',
    render: { fillStyle: '#e74c3c' }
});

// 3. Mechanical Door
const door = Bodies.rectangle(600, 450, 20, 150, {
    isStatic: true,
    label: 'door',
    render: { fillStyle: '#7f8c8d' }
});

Composite.add(world, [player, pressurePlate, door]);

2. Track Collision State

Matter.js dispatches collisionStart and collisionEnd events whenever pairs of bodies begin or cease touching. To reliably manage multi-object interactions (such as two crates on one plate), maintain an active contact counter rather than a simple boolean flag.

let activeContacts = 0;
let isDoorOpen = false;

Events.on(engine, 'collisionStart', (event) => {
    const pairs = event.pairs;
    for (let i = 0; i < pairs.length; i++) {
        const { bodyA, bodyB } = pairs[i];
        if (bodyA === pressurePlate || bodyB === pressurePlate) {
            activeContacts++;
            isDoorOpen = true;
        }
    }
});

Events.on(engine, 'collisionEnd', (event) => {
    const pairs = event.pairs;
    for (let i = 0; i < pairs.length; i++) {
        const { bodyA, bodyB } = pairs[i];
        if (bodyA === pressurePlate || bodyB === pressurePlate) {
            activeContacts = Math.max(0, activeContacts - 1);
            if (activeContacts === 0) {
                isDoorOpen = false;
            }
        }
    }
});

3. Animate the Door Movement

Modifying the position of a static body directly can cause overlapping objects to behave erratically. Instead, interpolate the door's coordinates inside the beforeUpdate event hook. This ensures the physics engine recalculates collision bounds and velocities smoothly before running collision resolution.

const closedY = 450;
const openY = 300; // Target height when retracted upward
const moveSpeed = 4;

Events.on(engine, 'beforeUpdate', () => {
    const currentY = door.position.y;
    
    if (isDoorOpen && currentY > openY) {
        // Raise door
        const nextY = Math.max(openY, currentY - moveSpeed);
        Body.setPosition(door, { x: door.position.x, y: nextY });
    } else if (!isDoorOpen && currentY < closedY) {
        // Lower door
        const nextY = Math.min(closedY, currentY + moveSpeed);
        Body.setPosition(door, { x: door.position.x, y: nextY });
    }
});

4. Implementation Best Practices