How to Create One-Way Platforms in Matter.js
This article explains how to build one-way (jump-through) platforms using the Matter.js 2D physics engine. You will learn the mechanics behind selective collision handling, how to monitor object positions and velocities relative to the platform, and how to use collision filters and engine events to allow characters to jump from below while landing securely on top.
The Core Concept
In standard 2D platformers, a one-way platform is permeable when a character approaches it from underneath or the sides, but solid when the character lands on it from above.
Because Matter.js is a rigid-body physics engine, it resolves collisions automatically. To create a one-way effect, you must dynamically toggle the interaction between the player and the platform before the physics engine calculates collision resolution for the current frame.
The most reliable approach is checking the player's position and
vertical velocity inside the beforeUpdate event and
adjusting collision masks accordingly.
Setting Up Collision Categories
Using collision categories allows the platform to ignore collisions with specific bodies without affecting other objects in the world. Define bitmasks for your entities:
const CATEGORY_PLAYER = 0x0001;
const CATEGORY_SOLID = 0x0002;
const CATEGORY_PLATFORM = 0x0004;
// Player setup
const player = Matter.Bodies.rectangle(400, 500, 40, 60, {
collisionFilter: {
category: CATEGORY_PLAYER,
mask: CATEGORY_SOLID | CATEGORY_PLATFORM // Collides with solids and platforms by default
}
});
// Platform setup
const platform = Matter.Bodies.rectangle(400, 300, 200, 20, {
isStatic: true,
collisionFilter: {
category: CATEGORY_PLATFORM,
mask: CATEGORY_PLAYER
}
});Dynamic Collision Detection Logic
Listen to the engine's beforeUpdate event to evaluate
whether the player is above the platform and falling downward. If the
player is jumping upward or located below the platform surface, disable
collision between the two.
Matter.Events.on(engine, 'beforeUpdate', () => {
const playerHalfHeight = 30;
const platformHalfHeight = 10;
const playerBottom = player.position.y + playerHalfHeight;
const platformTop = platform.position.y - platformHalfHeight;
// A small threshold prevents jitter when standing on the surface
const threshold = 5;
// Condition 1: Player is moving downward or standing still
const isMovingDown = player.velocity.y >= 0;
// Condition 2: Player's feet are above or at the platform's surface
const isAbovePlatform = playerBottom <= platformTop + threshold;
if (isMovingDown && isAbovePlatform) {
// Enable collision
player.collisionFilter.mask = CATEGORY_SOLID | CATEGORY_PLATFORM;
} else {
// Disable collision with the one-way platform
player.collisionFilter.mask = CATEGORY_SOLID;
}
});Allowing the Player to Drop Down
To let a player drop down through the platform on demand (such as pressing the Down arrow along with a jump key), introduce an override flag:
let dropThrough = false;
// Example key listener
window.addEventListener('keydown', (e) => {
if (e.key === 'ArrowDown') {
dropThrough = true;
// Reset the flag after a brief period so the player can land on platforms below
setTimeout(() => {
dropThrough = false;
}, 250);
}
});Update your beforeUpdate condition to include the
flag:
if (isMovingDown && isAbovePlatform && !dropThrough) {
player.collisionFilter.mask = CATEGORY_SOLID | CATEGORY_PLATFORM;
} else {
player.collisionFilter.mask = CATEGORY_SOLID;
}Handling Multiple Platforms
If your game has multiple one-way platforms, manage collision toggling on the platform bodies rather than modifying the player's mask:
const oneWayPlatforms = [platform1, platform2, platform3];
Matter.Events.on(engine, 'beforeUpdate', () => {
const playerHalfHeight = 30;
const playerBottom = player.position.y + playerHalfHeight;
const isMovingDown = player.velocity.y >= 0;
oneWayPlatforms.forEach(plat => {
const platformTop = plat.position.y - (plat.bounds.max.y - plat.bounds.min.y) / 2;
const isAbove = playerBottom <= platformTop + 5;
if (isMovingDown && isAbove && !dropThrough) {
plat.collisionFilter.mask = CATEGORY_PLAYER;
} else {
plat.collisionFilter.mask = 0; // Ignore player
}
});
});Modifying the individual platform masks ensures that a player falling through one platform does not phase through an unrelated platform beneath it.