What scale factor to use for pixels to meters in planck.js?
When developing 2D physics games using planck.js (a JavaScript port of Box2D), translating screen pixels to physics meters requires a recommended scale factor of 30 pixels per meter. Because planck.js is optimized to handle objects between 0.1 and 10 meters in size, mapping pixels directly 1:1 will cause simulation slowdowns, floating artifacts, and unrealistic gravity. This article covers why this specific scaling factor matters, how to implement it efficiently in your rendering loop, and best practices for managing physics-to-screen conversions.
Why planck.js Requires MKS Scaling
Like its predecessor Box2D, planck.js uses the MKS (meters, kilograms, seconds) unit system. The internal algorithms and floating-point math of the engine are tuned for real-world macroscopic objects—think of a crate that is 1 meter wide, or a character that is 1.8 meters tall.
If you pass raw pixel values (e.g., a sprite that is 300 pixels wide) directly into a planck.js rigid body without scaling, the engine treats it as a 300-meter-wide mega-structure. Gravity will appear to move in slow motion, and collisions will behave sluggishly because the engine is computing physics for objects the size of skyscrapers.
Implementing the 30x Scale Factor
The standard industry recommendation for Box2D-based engines is a
scale factor of 30 (often represented as
SCALE = 30 or PPM = 30 for Pixels Per
Meter).
To seamlessly bridge your physics world and your rendering canvas, you must apply this scale factor during two critical phases:
- Creating Physics Bodies (Pixels to Meters): Divide your visual pixel dimensions by 30 before passing them to planck.js shapes.
- Rendering Graphics (Meters to Pixels): Multiply the position and dimensions returned by planck.js by 30 before drawing them to the screen.
Code Implementation Example
The following approach isolates the conversion logic into a clean, reusable system for your game loop.
// Define the scaling constant
const PPM = 30;
// 1. Creating a physics body from pixel dimensions
function createBox(pixelX, pixelY, pixelWidth, pixelHeight) {
const body = world.createBody({
type: 'dynamic',
position: planck.Vec2(pixelX / PPM, pixelY / PPM)
});
// Box2D/Planck uses half-widths and half-heights for extents
const halfWidth = (pixelWidth / 2) / PPM;
const halfHeight = (pixelHeight / 2) / PPM;
body.createFixture(planck.Box(halfWidth, halfHeight), {
density: 1.0,
friction: 0.3
});
return body;
}
// 2. Updating the visual sprite position in the render loop
function updateSprite(sprite, physicsBody) {
const position = physicsBody.getPosition();
// Convert meters back to pixels for screen rendering
sprite.x = position.x * PPM;
sprite.y = position.y * PPM;
// Rotation remains in radians, no conversion needed for scaling
sprite.rotation = physicsBody.getAngle();
}Flexibility with Tuning Your Scale
While 30 PPM is the baseline standard, the exact number can be adjusted based on your target resolution and design needs. Common alternatives include 32 PPM (ideal for matching up perfectly with \(32 \times 32\) pixel tilemaps) or 50 PPM (for clean decimal math).
The core rule is to pick a scale factor that keeps your primary moving physics objects roughly between 0.1 and 10 meters in the eyes of the simulation engine. Static background scenery can safely exceed these limits, but active game characters and projectiles should always fit comfortably within this performance window.