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:

  1. Identify the squish state via contact pairs.
  2. Temporarily set the elevator's collisionFilter.mask = 0 or change elevator.isSensor = true.
  3. Allow the platform to phase through the player harmlessly until full separation occurs, then restore solid collision.