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
- Use Static Sensor Anchors: Keep the pressure plate
isStatic: true. If the trigger needs to visually depress, lower its visual offset in a custom renderer or lower a secondary non-sensor plate using prismatic constraints. - Collision Filtering: Assign distinct
collisionFiltercategories to your triggers if only specific objects (such as heavy boxes, but not projectiles) should activate the plate. - Swept Movement Handling: When the mechanical door
closes downward, any dynamic body underneath will naturally be pushed
down by the solver, provided
moveSpeedremains moderate and position updates occur inside thebeforeUpdatelifecycle.