How to Object Pool planck.js Bodies?
Object pooling in planck.js—a 2D JavaScript physics engine based on Box2D—is a crucial optimization technique for maintaining a stable frame rate in physics-heavy games or simulations. Constantly creating and destroying rigid bodies triggers frequent garbage collection spikes, leading to noticeable frame drops. By reusing a fixed set of bodies instead of destroying them, you can keep memory allocation flat. This article covers the essential steps to implement an efficient object pool for planck.js bodies, including spawning, recycling, and properly resetting a body’s state.
Why Pool Physics Bodies?
In standard JavaScript development, creating a new object is
relatively cheap. However, in a physics engine like planck.js, a
World.createBody() call does heavy lifting behind the
scenes. It allocates memory for the body, its transform states, velocity
vectors, and attached fixtures.
If you are building a game like a bullet hell or an endless runner where hundreds of projectiles or obstacles appear and disappear every second, standard destruction will quickly bottleneck your application.
The Garbage Collection Trap: When you call
world.destroyBody(body), you don’t instantly free memory; you just remove the reference. The JavaScript Garbage Collector (GC) will eventually run to clean up those objects, causing micro-stutters during gameplay.
Step 1: Initializing the Object Pool
An object pool is essentially an array or a list that holds pre-allocated objects. When you initialize your game scene, you should create the maximum number of bodies you expect to need at any given time.
class BodyPool {
constructor(world, poolSize) {
this.world = world;
this.inactiveBodies = [];
// Pre-allocate bodies
for (let i = 0; i < poolSize; i++) {
// Create a generic dynamic body at world origin
const body = this.world.createBody({
type: 'dynamic',
position: pl.Vec2(0, 0)
});
// Attach a default fixture (e.g., a small circle)
body.createFixture(pl.Circle(0.5), {
density: 1.0,
friction: 0.3
});
// Deactivate it immediately so it doesn't interact with the world
body.setActive(false);
this.inactiveBodies.push(body);
}
}
}Step 2: Spawning and Activating a Body
When you need a new object in your game, instead of calling
world.createBody(), you request one from your pool. You
must activate the body and set its new position and velocity before it
rejoins the physics simulation.
get(position, linearVelocity) {
if (this.inactiveBodies.length === 0) {
// Optional: Expand the pool dynamically if it runs out
return null;
}
const body = this.inactiveBodies.pop();
// Teleport the body to the starting position
body.setPosition(position);
body.setLinearVelocity(linearVelocity);
body.setAngularVelocity(0);
// Wake up the body and make it active
body.setActive(true);
body.setAwake(true);
return body;
}Step 3: Properly Resetting and Recycling Bodies
The most critical part of object pooling in planck.js is cleaning the slate when a body goes off-screen or gets destroyed. If you don’t completely reset the physical properties, the reused body will carry over residual forces, velocities, and contact states, causing unpredictable physics glitches.
To safely recycle a body back into the pool, use the following checklist:
- Set Active to False: This removes the body from the collision detection phase and stops it from interacting with other shapes.
- Zero Out Velocities: Wipe both linear and angular velocities so the object starts completely still next time.
- Clear Forces: Clear any accumulated forces and torques that haven’t been integrated yet.
- Reset Mass Properties: If your pool reuses bodies
with varying fixtures, ensure you call
body.resetMassData()if shapes change.
recycle(body) {
// Deactivate physics interactions
body.setActive(false);
// Wipe out physical momentum
body.setLinearVelocity(pl.Vec2(0, 0));
body.setAngularVelocity(0);
// Clear any pending force accumulations
body.setForce(pl.Vec2(0, 0));
body.setTorque(0);
// Return to the pool array
this.inactiveBodies.push(body);
}Handling Contacts and Collisions During Pooling
When you deactivate a body using body.setActive(false),
planck.js automatically destroys any active contacts touching that body.
However, if you are caching data inside the user data property
(body.setUserData()), make sure to clear or update that
reference when recycling. If your game relies on contact listeners,
always check body.isActive() within your
beginContact loops to ensure you are not processing logic
for a body that was just returned to the pool during the same physics
step.