How to clone or serialize a Planck.js world state?
Planck.js does not feature built-in, native methods for deep cloning
or serializing a world state out of the box. Because the underlying
physics engine relies heavily on complex, interconnected object graphs
and circular references—such as doubly linked lists linking bodies,
fixtures, joints, and contacts—standard JavaScript serialization methods
like JSON.stringify() or structuredClone()
will instantly fail. To successfully save, network, or replicate a
planck.World instance, developers must manually extract the
raw definition states of active physical components and reconstruct them
into a fresh world instance.
Why Standard JavaScript Cloning Fails
Planck.js is a direct JavaScript/TypeScript rewrite of the C++ Box2D engine. It inherits an architecture that prioritizes performance and memory management over modern JavaScript data immutability.
- Circular References: Every body keeps track of its fixtures, and every fixture links directly back to its parent body. Attempting to run native serialization throws immediate type and range errors.
- Prototype Methods and Internal State: Components like vectors, shapes, and broad-phase collision trees contain internal mathematical state variables and methods that cannot be natively converted to plain text data.
- Dispatched User Data: Real-world implementations
utilize the
userDataproperty to attach physics bodies to game-engine visual meshes, introducing complex, un-serializable references into the physics pipeline.
The Strategy for Custom Serialization
To successfully export a snapshot of a Planck.js world, you must execute a structural extraction loop. This process isolates the essential physical properties needed to recreate the objects from scratch.
A standard serialization script iterates through the components of the world using the engine’s built-in linked list traversal getters:
const serializedData = { bodies: [], joints: [] };
// Traversing the body linked list
for (let body = world.getBodyList(); body; body = body.getNext()) {
const bodyState = {
type: body.getType(),
position: body.getPosition().clone(),
angle: body.getAngle(),
linearVelocity: body.getLinearVelocity().clone(),
angularVelocity: body.getAngularVelocity(),
fixtures: []
};
// Traversing fixtures within the current body
for (let fixture = body.getFixtureList(); fixture; fixture = fixture.getNext()) {
const shape = fixture.getShape();
bodyState.fixtures.push({
type: shape.getType(),
radius: shape.m_radius, // Extract specific shape details depending on type
density: fixture.getDensity(),
friction: fixture.getFriction(),
restitution: fixture.getRestitution()
});
}
serializedData.bodies.push(bodyState);
}This structural object contains only primitive values, arrays, and
flat structures, rendering it entirely safe for
JSON.stringify() serialization or distribution across a
network protocol.
Rehydrating and Deep Cloning the World State
Whether your goal is to recreate a deep copy of the world state for
determinism checks, rollback networking, or a save-state feature, the
rehydration process requires manual setup. You cannot inject raw state
directly back into a living engine instance; you must generate a clean
planck.World and reconstruct the elements sequentially.
- Initialize a New World Instance: Create a new world using the original gravity configuration and sleeping settings.
- Recreate Bodies and Fixtures: Run a loop over your
serialized JSON data array. For each item, invoke
world.createBody(bodyDef)using the saved position, type, and angle properties. - Reapply Dynamic Velocities: After a body is
instantiated, explicitly apply its linear and angular velocities via
body.setLinearVelocity()andbody.setAngularVelocity(). - Reconstruct Fixtures and Shapes: Iterate through
the shape definitions stored for that body, instantiate the correct
geometry subclass (e.g.,
planck.Box,planck.Circle), and append it usingbody.createFixture(shape, density). - Re-link Joint Hierarchies: If your simulation includes joints, save the identifiers or indices of connected bodies during serialization, locate those specific bodies in the new world, and pass them into the appropriate joint definition factory.