How to Move Characters with Planck.js Forces?

Using forces for character movement in planck.js (a 2D JavaScript physics engine based on Box2D) allows for realistic, momentum-based physics interactions, but it requires careful tuning to prevent characters from feeling “floaty” or sliding uncontrollably. This article covers how to apply forces for horizontal movement, implement instant stopping using linear damping, and manage jumping mechanics while maintaining precise control. By balancing forces with the right body properties, you can achieve responsive controls without sacrificing the benefits of a robust physics engine.

Setting Up the Character Body

Before applying forces, the character’s physics body must be configured correctly. By default, physics bodies rotate when forces are applied off-center or during collisions. For a standard platformer or top-down character, you must lock the rotation.

Applying Horizontal Forces

To move a character, you should apply a force to the center of mass using body.applyForceToCenter(). This adds momentum gradually, which creates a natural acceleration curve.

// Example: Moving right
const moveForce = vec2(10.0, 0.0);
characterBody.applyForceToCenter(moveForce, true);

Because forces accumulate over time, simply stopping the input won’t stop the character immediately. The character will slide due to inertia.

Eliminating the “Floaty” Feeling

To make controls feel snappy and responsive rather than slippery, you need to manage how velocity decreases when input ceases.

1. Adjusting Linear Damping

Linear damping simulates fluid resistance (like air drag). Increasing the character’s linearDamping will cause them to slow down quickly when you stop applying force.

characterBody.setLinearDamping(5.0); // Higher values mean faster stopping

2. Capping Maximum Velocity

Forces can accelerate a body infinitely if left unchecked. You must manually clamp the character’s linear velocity in your update loop to enforce a maximum speed.

const velocity = characterBody.getLinearVelocity();
const maxSpeed = 8.0;

if (Math.abs(velocity.x) > maxSpeed) {
    characterBody.setLinearVelocity(vec2(Math.sign(velocity.x) * maxSpeed, velocity.y));
}

3. Implementing Hard Stops

If you want the character to stop instantly when no directional keys are pressed, you can directly override the horizontal velocity while preserving gravity’s effect on the vertical axis.

if (!isMovingInputPressed) {
    const currentVelocity = characterBody.getLinearVelocity();
    characterBody.setLinearVelocity(vec2(0.0, currentVelocity.y));
}

Handling Jumping Mechanics

Jumping with forces can be unpredictable because applyForce takes time to build momentum. For a crisp, instantaneous jump, using a linear impulse is often preferred over a force, as it changes velocity immediately.

// Applying an instantaneous upward impulse for a jump
const jumpImpulse = vec2(0.0, 15.0);
characterBody.applyLinearImpulse(jumpImpulse, characterBody.getWorldCenter(), true);

To make the fall feel weightless or heavy, you can adjust the gravityScale on the character body dynamically—increasing it when the character is falling to create a satisfying, punchy jump curve.