How to Implement Character Jumping in Matter.js
This article explains how to build a reliable jumping mechanic for a 2D character using the Matter.js physics engine. Implementing a jump requires configuring a rigid body with locked rotation, detecting when the character is touching the ground to prevent infinite jumping, and applying an upward impulse or velocity change upon receiving user input. Below, you will find the direct technical steps and code required to handle jumping and ground detection cleanly.
1. Set Up the Player Body
A platformer character typically requires its rotation to be locked
so it does not tip over when moving across surfaces. Create a
rectangular or chamfered body and set inertia to
Infinity to prevent rotation.
const player = Matter.Bodies.rectangle(100, 100, 40, 60, {
inertia: Infinity, // Prevents the player from tipping over
friction: 0.05,
frictionAir: 0.01,
restitution: 0 // Prevents unwanted bouncing
});
Matter.Composite.add(engine.world, player);2. Implement Reliable Ground Detection
The most common issue with jumping in physics engines is mid-air jumping. The most robust way to prevent this in Matter.js is by using a dedicated sensor attached to the bottom of the player, or by monitoring collision pairs.
Using a compound body with a "foot" sensor provides precise ground state checking:
const mainBody = Matter.Bodies.rectangle(100, 100, 40, 56);
const groundSensor = Matter.Bodies.rectangle(100, 128, 36, 6, {
isSensor: true, // Passes through objects without physical collision
label: 'groundSensor'
});
const playerCompound = Matter.Body.create({
parts: [mainBody, groundSensor],
inertia: Infinity,
friction: 0.05
});
Matter.Composite.add(engine.world, playerCompound);Track whether the sensor is touching a solid surface using collision events:
let isGrounded = false;
Matter.Events.on(engine, 'collisionStart', (event) => {
event.pairs.forEach((pair) => {
if (pair.bodyA === groundSensor || pair.bodyB === groundSensor) {
isGrounded = true;
}
});
});
Matter.Events.on(engine, 'collisionEnd', (event) => {
event.pairs.forEach((pair) => {
if (pair.bodyA === groundSensor || pair.bodyB === groundSensor) {
isGrounded = false;
}
});
});3. Apply the Jump Velocity
When the jump input is triggered (such as pressing the Spacebar),
verify that isGrounded is true, then apply an
upward velocity or force. Overriding vertical velocity directly via
Matter.Body.setVelocity provides consistent, snappy jump
heights regardless of existing downward momentum.
const JUMP_FORCE = -12; // Adjust based on your world's gravity settings
function jump() {
if (isGrounded) {
Matter.Body.setVelocity(playerCompound, {
x: playerCompound.velocity.x,
y: JUMP_FORCE
});
isGrounded = false; // Immediate reset to prevent multi-frame triggers
}
}
window.addEventListener('keydown', (e) => {
if (e.code === 'Space' || e.code === 'ArrowUp') {
jump();
}
});4. Optional Enhancements
- Variable Jump Height: To make the jump respond to
how long the button is held, cut the upward velocity in half
(
player.velocity.y *= 0.5) on thekeyupevent if the player is still moving upward. - Coyote Time: Allow a grace period (e.g., 100
milliseconds) after
isGroundedturnsfalsebefore disabling the ability to jump, making platforming controls feel significantly more responsive.