How to Use Planck.js Friction Joints for Top-Down Physics

Building a top-down 2D physics environment requires simulating friction that acts on an object’s entire surface area, unlike side-scrolling games where gravity pushes objects onto a platform. In planck.js (a JavaScript port of Box2D), this is efficiently achieved using a FrictionJoint. This article provides a quick guide on how to configure rigid bodies, bind them with friction joints to a static ground body, and tune maximum force and torque to create realistic top-down movement and sliding mechanics.

Setting Up the Ground and Moving Bodies

In a top-down perspective, gravity is typically set to zero because the camera looks down on the playing field. Because of this, bodies will float infinitely when forces are applied unless simulated surface friction is introduced.

To set up the structure:

// Initialize a world with zero gravity
const world = planck.World({
  gravity: planck.Vec2(0, 0)
});

// Create a static ground body at the origin
const ground = world.createBody();

Configuring the Friction Joint

A FrictionJoint in planck.js works by connecting a dynamic body to the static ground body. It actively resists both linear translation (sliding) and angular rotation (spinning) to simulate the drag of a surface.

When creating the joint, you must define the local or world anchors and then set the maximum friction constraints:

// Create a dynamic box body
const box = world.createBody({
  type: 'dynamic',
  position: planck.Vec2(0, 0)
});
box.createFixture(planck.Box(1.0, 1.0));

// Bind the box to the ground using a FrictionJoint
const frictionJoint = world.createJoint(planck.FrictionJoint({
  bodyA: ground,
  bodyB: box,
  localAnchorA: planck.Vec2(0, 0),
  localAnchorB: planck.Vec2(0, 0)
}));

Tuning Mass, Force, and Torque

Once the joint is established, it will not apply any friction until you define its limits. You must explicitly set maxForce (resistance to moving) and maxTorque (resistance to spinning).

// Define the friction limits based on your object's mass
const objectMass = box.getMass();
const gravitySimulation = 9.8; // Simulated gravity constant
const frictionCoefficient = 0.5; // Surface grip factor

// Calculate and apply maximum force and torque limits
frictionJoint.setMaxForce(objectMass * gravitySimulation * frictionCoefficient);
frictionJoint.setMaxTorque(objectMass * frictionCoefficient * 0.5);

By linking every dynamic body to the static ground with individual FrictionJoint components, you create a modular architecture. This structure handles complex, multi-object top-down physics smoothly without manually calculating and subtracting velocity vectors inside your game loop.