How to Import Saved Matter.js State

Restoring a physics simulation to a previous state in Matter.js requires saving the key properties of your bodies to a serializable format like JSON and reconstructing them inside the physics world. Because Matter.js instances contain internal functions and circular references, native serialization methods like JSON.stringify(engine) fail. This article demonstrates how to structure, parse, and import serialized body states back into a Matter.js engine.

Why Manual State Deserialization is Required

The Matter.Engine and Matter.Composite objects maintain complex references, cache structures, and event listeners that cannot be serialized directly. To restore a simulation, you must reconstruct the bodies from a snapshot containing essential geometric and physical values: position, angle, velocity, angular velocity, and custom dimensions or collision options.

Structure of a Saved State

A reliable saved state consists of a clean array of plain objects containing only the required simulation parameters:

[
  {
    "type": "rectangle",
    "x": 400,
    "y": 200,
    "width": 80,
    "height": 80,
    "angle": 0.45,
    "velocity": { "x": 1.2, "y": -0.5 },
    "angularVelocity": 0.02,
    "isStatic": false
  }
]

Implementing the Import Function

To import and apply this data, first clear the existing simulation bodies using Composite.clear(). Then, iterate through the saved data to recreate the bodies with Matter.Bodies, apply their physical states, and add them back to the world.

import Matter from 'matter-js';

const { Composite, Bodies, Body } = Matter;

function importSavedState(engine, savedData) {
  // Clear existing bodies and constraints from the world
  Composite.clear(engine.world, false);

  const newBodies = savedData.map((data) => {
    let body;

    // Reconstruct the geometry based on type
    if (data.type === 'rectangle') {
      body = Bodies.rectangle(data.x, data.y, data.width, data.height, {
        isStatic: data.isStatic
      });
    } else if (data.type === 'circle') {
      body = Bodies.circle(data.x, data.y, data.radius, {
        isStatic: data.isStatic
      });
    }

    // Apply rotation and dynamic physics properties
    if (body) {
      Body.setAngle(body, data.angle || 0);
      Body.setVelocity(body, data.velocity || { x: 0, y: 0 });
      Body.setAngularVelocity(body, data.angularVelocity || 0);
    }

    return body;
  }).filter(Boolean);

  // Add the reconstructed bodies to the physics engine
  Composite.add(engine.world, newBodies);
}

Restoring Constraints and Joints

If your state includes constraints (such as pins, springs, or ropes), save unique identifiers (id) for each body during the export step. During the import process:

  1. Map each recreated body to its original id.
  2. Iterate through a saved constraints array.
  3. Use Matter.Constraint.create() with bodyA and bodyB assigned via the mapped identifiers.
  4. Add the resulting constraints to engine.world using Composite.add().

Handling Collision Groups and Masks

To ensure physical behaviors remain identical after importing, include collisionFilter data (group, category, and mask) in your export payload. When reconstructing the bodies, pass these options into the body creation parameters so that collision rules remain intact.