Skip to content

Saving & Loading

SaveManager persists named save slots as versioned envelopes. Each save records the schema version it was written at, so a shipped game can change its save shape later and still read old saves — on load, older data is migrated forward.

Create one manager for your game (pick a current schema version), then read and write slots by name:

import { SaveManager } from 'esengine';
const saves = new SaveManager({ version: 1 });
saves.save('slot1', { level: 3, score: 1200, hp: 80 });
const data = saves.load<{ level: number; score: number; hp: number }>('slot1');
// -> { level: 3, score: 1200, hp: 80 } (or null if the slot is empty)
saves.has('slot1'); // true
saves.savedAt('slot1'); // epoch ms the slot was written, or null
saves.remove('slot1'); // delete the slot
  • save(slot, data) writes data stamped with the current version and time.
  • load(slot) returns the slot’s data (migrated forward), or null when the slot is empty or unreadable.
  • has / remove / savedAt query, delete, and timestamp a slot.

When your save shape changes, bump version and add a migration for each step. migrations[n] upgrades a save written at version n to n + 1; on load, every step from the save’s version up to the current one runs in order:

const saves = new SaveManager({
version: 2,
migrations: {
// v1 stored a flat score; v2 nests it under `stats`.
1: (old) => {
const s = old as { level: number; score: number };
return { level: s.level, stats: { score: s.score, deaths: 0 } };
},
},
});
// A v1 save on disk is upgraded to the v2 shape transparently on load.
const data = saves.load('slot1');

Migration is forward-only: loading a save that is newer than the current version throws (no downgrade), and a missing step in the chain throws too — so a gap is caught immediately instead of silently corrupting data.

For simple flags and preferences that don’t need versioning, use the Storage API directly — a per-platform key/value store (browser localStorage, WeChat storage, …) with typed helpers:

import { Storage } from 'esengine';
Storage.setBoolean('muted', true);
Storage.setNumber('volume', 0.8);
Storage.setJSON('keybinds', { jump: 'Space', fire: 'KeyJ' });
Storage.getBoolean('muted', false); // true
Storage.getNumber('volume', 1); // 0.8
Storage.has('keybinds'); // true

Storage also has getString / setString, remove(key), and clear(). SaveManager is built on top of it, so both persist to the same per-platform backend.

  • Scenes — loading and switching the scenes a save points at.
  • Assets — why bulk data belongs in assets, not in a save slot.