Ammo.js Physics to Pixels Scaling Ratio for 2D Games
Integrating the 3D physics engine ammo.js into a 2D game requires scaling physics units to screen pixels to ensure realistic movement and stable collision detection. This article explains the ideal scale ratio for mapping ammo.js meters to pixels, why proper scaling is critical for the physics simulation, and how to implement this conversion in your rendering loop.
The Ideal Scale Ratio: 30 to 100 Pixels per Meter
The ideal scale ratio for mapping ammo.js physics units to pixel coordinates in a 2D game is 30 to 100 pixels per meter (PPM). Within this range, 50 PPM or 100 PPM are the most common standards used by developers.
Choosing a ratio within this range ensures that your game objects map to the optimal size limits of the physics engine while remaining easily viewable on modern screen resolutions.
Why Unit Scaling is Critical in Ammo.js
Ammo.js is a direct port of the Bullet physics engine, which is hardcoded to work with the Metric system (MKS: meters, kilograms, seconds). The engine’s collision detection algorithms, gravity calculations, and rigid body constraints are optimized for objects sized between 0.05 units (5 centimeters) and 10.0 units (10 meters).
If you map your units 1:1 (where 1 pixel = 1 meter): * A standard 64x64 pixel sprite would be simulated as a massive 64-meter structure. * Gravity (9.8 m/s²) would feel incredibly slow and floaty, as objects would take a long time to fall across a 1080-pixel-high screen. * Fast-moving objects would suffer from “tunneling” (passing through solid walls) because their pixel velocities would translate to supersonic speeds in the physics simulation.
By applying a Scaling Factor (e.g., 50 PPM), a 100-pixel wide sprite is represented in the physics world as a highly stable 2-meter wide box.
How to Implement the Scale Ratio
To use this scale ratio, you must convert positions and dimensions when passing data between your game’s rendering engine (like Three.js, PixiJS, or Phaser) and the ammo.js physics world.
Define your scale factor globally:
const PPM = 50; // Pixels Per Meter1. Converting Pixels to Physics Units (Meters)
When creating rigid bodies in ammo.js, divide your pixel dimensions and starting coordinates by your PPM factor:
// A sprite that is 100px wide and 50px high
let spriteWidth = 100;
let spriteHeight = 50;
// Convert to ammo.js meters
let physicsWidth = spriteWidth / PPM; // 2 meters
let physicsHeight = spriteHeight / PPM; // 1 meter
// Define the Ammo box shape using half-extents
let colShape = new Ammo.btBoxShape(new Ammo.btVector3(physicsWidth / 2, physicsHeight / 2, 0.5));2. Converting Physics Units to Pixels
During your game’s update loop, retrieve the position and rotation from the ammo.js motion state, multiply the coordinates by your PPM factor, and apply them to your visual sprites:
// Get position from ammo.js
let transform = new Ammo.btTransform();
motionState.getWorldTransform(transform);
let origin = transform.getOrigin();
// Convert physics meters back to screen pixels for rendering
let screenX = origin.x() * PPM;
let screenY = origin.y() * PPM;
// Update your 2D sprite position
sprite.position.set(screenX, screenY);Configuring Gravity
When using the ideal scale ratio, you can use realistic earth gravity in your ammo.js world setup. Set your gravity vector using standard metric values:
let collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
let dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration);
let overlappingPairCache = new Ammo.btDbvtBroadphase();
let solver = new Ammo.btSequentialImpulseConstraintSolver();
let physicsWorld = new Ammo.btDiscreteDynamicsWorld(dispatcher, overlappingPairCache, solver, collisionConfiguration);
// Set gravity to 9.8 m/s^2 downward on the Y-axis
physicsWorld.setGravity(new Ammo.btVector3(0, -9.8, 0));By keeping the physics world configuration in true meters, your objects will fall, bounce, and collide with natural, realistic behavior while rendering perfectly to your screen’s pixel grid.