Building Non-Crushing Moving Platforms in Matter.js
In Matter.js, moving kinematic or static platforms vertically can easily trap dynamic player bodies against static ceilings or floors, causing erratic physics tunneling, violent ejection, or body deformation. This article provides a direct guide on how to implement vertically moving elevators that carry dynamic player bodies smoothly without crushing them. By combining synchronized velocity manipulation, pre-step collision detection, and elevator safety halts, you can achieve reliable platforming mechanics.
Why Crushing Occurs
When an elevator body moves into a dynamic player pinned against an
immovable ceiling, the Matter.js iterative constraint solver cannot
satisfy both non-penetration constraints simultaneously. If the platform
is moved purely via Body.setPosition(), it behaves
infinitely massed and teleports into the player's collision hull,
generating massive separation forces that fling the player through
geometry or distort the simulation.
Step 1: Use Kinematic Velocity Synchronization
Never update an elevator's position solely with coordinates. Matter.js requires linear velocity to accurately calculate friction and collision response for bodies resting on top of a moving surface.
Set both the velocity and position on each tick:
const elevatorSpeed = 2; // Pixels per step
let direction = -1; // -1 for up, 1 for down
Matter.Events.on(engine, 'beforeUpdate', () => {
// Set appropriate linear velocity
Matter.Body.setVelocity(elevator, { x: 0, y: direction * elevatorSpeed });
// Explicitly update position to match the velocity
Matter.Body.setPosition(elevator, {
x: elevator.position.x,
y: elevator.position.y + direction * elevatorSpeed
});
});Step 2: Implement Squish Detection
To prevent crushing, monitor whether the player is compressed between the moving platform and an immovable obstacle (such as a ceiling or another static body). When compression occurs, the elevator must halt, reverse, or yield.
Use collision pairs during the beforeUpdate or
collisionActive events to check if the player is touching
both the elevator and an overhead barrier:
Matter.Events.on(engine, 'collisionActive', (event) => {
const pairs = event.pairs;
let touchingElevator = false;
let touchingCeiling = false;
for (let i = 0; i < pairs.length; i++) {
const { bodyA, bodyB } = pairs[i];
const bodies = [bodyA, bodyB];
if (bodies.includes(player) && bodies.includes(elevator)) {
touchingElevator = true;
}
if (bodies.includes(player) && bodies.some(b => b.isStatic && b !== elevator)) {
// Confirm the static body is located above the player
const otherBody = bodies.find(b => b !== player);
if (otherBody.position.y < player.position.y) {
touchingCeiling = true;
}
}
}
// Safety intervention when trapped moving upward
if (touchingElevator && touchingCeiling && direction < 0) {
// Option A: Reverse platform direction immediately
direction = 1;
// Option B: Temporarily halt the platform
Matter.Body.setVelocity(elevator, { x: 0, y: 0 });
}
});Step 3: Implement Raycasting or Collision Queries
For smoother motion, detect imminent pinches before contacts generate
high-pressure penetration artifacts. Use Matter.Query.ray
or Matter.Query.region slightly above the player's top
edge:
function isHeadroomBlocked(player, detectionDistance = 4) {
const rayStart = {
x: player.position.x,
y: player.bounds.min.y
};
const rayEnd = {
x: player.position.x,
y: player.bounds.min.y - detectionDistance
};
const collisions = Matter.Query.ray(worldBodies, rayStart, rayEnd);
return collisions.some(collision => collision.body.isStatic && collision.body !== elevator);
}If isHeadroomBlocked(player) returns true
and the elevator is pushing the player upward, pause the elevator until
the player steps off or sufficient clearance is restored.
Step 4: Employ One-Way Platform Logic
Alternatively, configure the elevator as a one-way platform using collision filters or collision active callbacks. If a player is trapped between a solid ceiling and an ascending elevator:
- Identify the squish state via contact pairs.
- Temporarily set the elevator's
collisionFilter.mask = 0or changeelevator.isSensor = true. - Allow the platform to phase through the player harmlessly until full separation occurs, then restore solid collision.