How to Make One-Way Platforms in Matter.js

This guide explains how to implement one-way semi-solid platforms in the Matter.js 2D physics engine. Matter.js does not include a native one-way collision feature, but you can easily achieve the classic "jump-through" mechanic by monitoring body positions and velocities, then dynamically adjusting collision properties—such as collision filters or sensor flags—before each physics update.

The Core Concept

A one-way platform must satisfy two conditions before enabling collision against a player:

  1. Direction of Movement: The player must be moving downward (velocity.y >= 0). If the player is jumping upward, collisions must be disabled so they can pass through.
  2. Relative Position: The bottom of the player's bounding box must be above (or aligned with) the platform's top surface. If the player is still inside or beneath the platform, collision must remain off to avoid pushing the player downward violently.

Method 1: Toggling the Collision Mask

The most reliable approach modifies the collision filter mask during the beforeUpdate engine event. By setting the platform's mask, you tell Matter.js whether it should register collisions with the player on the upcoming tick.

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

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

// Define collision categories
const CATEGORY_PLAYER = 0x0001;
const CATEGORY_PLATFORM = 0x0002;

// Create the player
const player = Bodies.rectangle(400, 500, 40, 60, {
    collisionFilter: {
        category: CATEGORY_PLAYER
    }
});

// Create the one-way platform
const platform = Bodies.rectangle(400, 300, 200, 20, {
    isStatic: true,
    collisionFilter: {
        category: CATEGORY_PLATFORM,
        mask: 0 // Start with collisions disabled
    }
});

Composite.add(world, [player, platform]);

// Handle one-way logic before every engine update
Events.on(engine, 'beforeUpdate', () => {
    const playerHalfHeight = 30;
    const platformHalfHeight = 10;

    const playerBottom = player.position.y + playerHalfHeight;
    const platformTop = platform.position.y - platformHalfHeight;

    // Buffer threshold to prevent clipping issues when falling fast
    const threshold = 5;

    // Enable collision only if falling and above the platform surface
    if (player.velocity.y >= 0 && playerBottom <= platformTop + threshold) {
        platform.collisionFilter.mask = CATEGORY_PLAYER;
    } else {
        platform.collisionFilter.mask = 0;
    }
});

Method 2: Dynamic Sensor Toggling for Multiple Platforms

If your game has multiple platforms and players, assigning an individual property to each platform helps scale the logic. Setting isSensor: true disables rigid collision while still firing collision events, which is useful if you want to track when an entity passes through a zone.

const platforms = [platform1, platform2, platform3];

Events.on(engine, 'beforeUpdate', () => {
    const playerHalfHeight = 30;
    const playerBottom = player.position.y + playerHalfHeight;

    platforms.forEach((plat) => {
        const platformTop = plat.position.y - (plat.bounds.max.y - plat.bounds.min.y) / 2;

        // Make solid only if player is above platform and falling
        if (player.velocity.y >= 0 && playerBottom <= platformTop + 5) {
            plat.isSensor = false;
        } else {
            plat.isSensor = true;
        }
    });
});

Implementing "Drop-Down" Controls

To allow players to drop down through a semi-solid platform when pressing the Down key:

  1. Maintain an input flag (e.g., isDroppingDown).
  2. If isDroppingDown is active, temporarily force platform.collisionFilter.mask = 0 or set platform.isSensor = true.
  3. Keep the platform disabled for a short duration (such as 250 milliseconds) or until the player has completely cleared the platform's bounding box.