如何将Matter.js的engine.world导出为可加载的JSON格式
Matter.js 世界对象序列化存储方案
直接对engine.world调用JSON.stringify失败是必然的:Matter.js的世界、刚体、约束对象之间存在大量双向循环引用(比如刚体持有所属世界的引用,世界同时持有刚体列表的引用),同时对象上还挂载了大量运行时生成的临时计算属性、方法,原生序列化方法无法处理这类结构。
可落地的实现方案有三类,按需选择即可:
方案1:使用Matter.js官方内置序列化模块
Matter.js本身提供了专门处理持久化的Matter.Serialize模块,自动过滤循环引用、剔除运行时临时属性,只保留可还原的核心数据,是接入成本最低的实现方式:
// 导出世界 const serializableWorld = Matter.Serialize(engine.world) // 此时可安全转JSON,存入localStorage/后端/本地文件 const worldStr = JSON.stringify(serializableWorld) // 导入世界 const savedWorld = JSON.parse(worldStr) // 清空当前世界现有内容 Matter.World.clear(engine.world, false) // 批量还原添加内容 Matter.World.add(engine.world, savedWorld.bodies) Matter.World.add(engine.world, savedWorld.constraints) Matter.World.add(engine.world, savedWorld.composites)
注意:如果你的刚体、约束上挂载了自定义业务属性,需要在调用Matter.Serialize时传入第二个参数,声明需要额外保留的自定义字段名,否则自定义属性会被默认过滤。
方案2:自定义循环引用处理逻辑
如果需要自主控制序列化逻辑,可以给JSON.stringify传入自定义replacer,配合WeakSet过滤已遍历的引用,避免循环报错:
const seen = new WeakSet() const worldStr = JSON.stringify(engine.world, (key, value) => { // 跳过已遍历的对象,切断循环引用 if (typeof value === 'object' && value !== null) { if (seen.has(value)) return seen.add(value) } // 跳过函数、Matter内部运行时临时属性 if (typeof value === 'function') return if (key.startsWith('_')) return return value })
这种方式灵活度高,但需要自己维护过滤规则,且导入时需要手动将还原后的对象重新关联Matter的对应原型,否则无法正常参与物理计算。
方案3:自定义精简世界存储格式(自定义世界场景最推荐)
做用户自定义世界功能时,最稳定的方案是不直接序列化Matter的原生对象,而是自己定义一套精简的存储结构,只存构建世界必需的核心参数:
- 全局配置:重力参数、世界边界
- 刚体列表:每个刚体的类型(矩形/圆形/多边形)、位置、角度、尺寸、质量、摩擦系数、弹性系数、自定义业务属性
- 约束/复合体列表:关联的刚体id、约束参数、结构配置
导出时遍历engine.world.bodies、engine.world.constraints提取上述字段存为JSON,导入时遍历配置,调用Matter.Bodies.rectangle()、Matter.Constraint.create()等官方API重新生成物体加入世界即可。
这种方案完全规避了Matter内部结构的兼容性问题,存储体积极小,哪怕后续升级Matter版本,也不会出现旧存档无法读取的问题,是目前自定义关卡、用户自创世界类功能的主流实现方式。
不推荐使用
structuredClone处理:该方法虽然支持循环引用,但无法序列化对象上的方法,且还原后的对象丢失了Matter的原型链,直接加入世界会导致物理计算异常,需要额外做原型还原,成本远高于上述方案。
内容的提问来源于stack exchange,提问作者Four Minecraft
相关产品推荐
相关产品推荐

