Save & load menu
This example shows a tiny save/load UI:
- save current game state into
localStorage - list saves
- load a save by id
It uses Button for UI and game.saves for persistence.
Define a save payload
type GameState = {
schemaVersion: 1;
level: number;
hp: number;
};
Save button
import { Button, Vector } from "sliver-engine";
const saveButton = new Button(
"save",
new Vector(40, 40),
new Vector(160, 50),
"Save",
"#2da44e",
"white",
() => {
const state: GameState = { schemaVersion: 1, level: 2, hp: 10 };
const id = game.saves.create(state, { label: `L${state.level}` });
console.log("Saved:", id);
}
);
Load menu (list + load)
This is intentionally simple: it logs entries and loads the most recent one.
const entries = game.saves.list();
console.log("Saves:", entries);
const mostRecent = entries[0];
if (mostRecent) {
const save = game.saves.read<GameState>(mostRecent.id);
if (save) {
console.log("Loaded data:", save.data);
// Apply it to your game (player stats, current level, etc.)
}
}
Tip: for a real menu, render one Button per save entry and call read(entry.id) in its click handler.
Interactive example
This sandbox includes a tiny save/load panel:
- Save stores current level/hp
- Load latest restores the newest entry
- Clear saves removes all entries in this demo namespace
Edit SaveButtons.ts to tweak save/load behavior.
import { Button, type GameObject, Vector } from "sliver-engine"; import { PlayerState } from "./PlayerState"; import type { GameState } from "./types"; const STATUS_CHANNEL = "save:status"; const LEFT_X = 24; const RIGHT_X = 198; const START_Y = 40; const BUTTON_WIDTH = 150; const BUTTON_HEIGHT = 40; const ROW_STEP = 14; const trimId = (id: string): string => id.slice(0, 8); export const createSaveButtons = (player: PlayerState): GameObject[] => { const saveButton = new Button( "save", new Vector(LEFT_X, START_Y), new Vector(BUTTON_WIDTH, BUTTON_HEIGHT), "Save", "#2da44e", "white", (btn) => { const ctx = btn.getContext(); if (!ctx) return; const state = player.toState(); const id = ctx.getGame().saves.create(state, { label: "L" + state.level + " HP" + state.hp, }); btn.sendMessage(STATUS_CHANNEL, "Saved " + trimId(id)); }, ); const loadLatestButton = new Button( "load-latest", new Vector(LEFT_X, START_Y + BUTTON_HEIGHT + ROW_STEP), new Vector(BUTTON_WIDTH, BUTTON_HEIGHT), "Load latest", "#1d4ed8", "white", (btn) => { const ctx = btn.getContext(); if (!ctx) return; const entries = ctx.getGame().saves.list(); const latest = entries[0]; if (!latest) { btn.sendMessage(STATUS_CHANNEL, "No saves found"); return; } const save = ctx.getGame().saves.read<GameState>(latest.id); if (!save) { btn.sendMessage(STATUS_CHANNEL, "Failed to read save"); return; } player.applyState(save.data); btn.sendMessage(STATUS_CHANNEL, "Loaded " + trimId(latest.id)); }, ); const clearButton = new Button( "clear-saves", new Vector(LEFT_X, START_Y + (BUTTON_HEIGHT + ROW_STEP) * 2), new Vector(BUTTON_WIDTH, BUTTON_HEIGHT), "Clear saves", "#7f1d1d", "white", (btn) => { const ctx = btn.getContext(); if (!ctx) return; ctx.getGame().saves.clear(); btn.sendMessage(STATUS_CHANNEL, "Cleared all saves"); }, ); const damageButton = new Button( "damage", new Vector(RIGHT_X, START_Y), new Vector(BUTTON_WIDTH, BUTTON_HEIGHT), "Damage -1", "#b91c1c", "white", (btn) => { player.damage(1); btn.sendMessage(STATUS_CHANNEL, "HP changed to " + player.hp); }, ); const healButton = new Button( "heal", new Vector(RIGHT_X, START_Y + BUTTON_HEIGHT + ROW_STEP), new Vector(BUTTON_WIDTH, BUTTON_HEIGHT), "Heal +1", "#15803d", "white", (btn) => { player.heal(1); btn.sendMessage(STATUS_CHANNEL, "HP changed to " + player.hp); }, ); const levelButton = new Button( "level-up", new Vector(RIGHT_X, START_Y + (BUTTON_HEIGHT + ROW_STEP) * 2), new Vector(BUTTON_WIDTH, BUTTON_HEIGHT), "Level +1", "#7c3aed", "white", (btn) => { player.levelUp(); btn.sendMessage(STATUS_CHANNEL, "Level changed to " + player.level); }, ); return [ saveButton, loadLatestButton, clearButton, damageButton, healButton, levelButton, ]; };