Game Saves & Progress

Save slots with extensible sections for per-playthrough state, and a separate global progress aggregate for what should survive a deleted save.

What the system is for

Most Unity save systems conflate two different questions: "what happened in this playthrough" and "what has this player ever unlocked." The result is save-slot menus that break the moment a player deletes a slot and loses achievements that should have survived, or completion counters bundled into the same save file and reset along with it.

Serenity answers both questions with two aggregates instead of one. GameSave holds per-playthrough state across a configurable number of slots, each an extensible document your game defines. GameProgress holds everything that is global and slot-independent — completion counts, unlocked extras — so deleting a save never erases what the player has earned.

The Unity problem

A save system built ad hoc tends to serialize "everything" into one blob per slot, including data that has nothing to do with that specific playthrough. Deleting a slot then deletes progress that should be permanent, and every new save-slot feature — inventory, milestones, completion percent — means editing a hand-rolled serializer instead of registering a new piece of data.

Slot menus that need to list label, timestamp and completion percent usually have to deserialize the entire save file just to read three fields, which gets slower as saves grow. And without atomic writes, a save interrupted mid-write or a stale in-memory cache after a restart can silently drop the player's most recent progress.

How Serenity approaches it

GameSave gives every slot a SaveDocument built from typed sections. Games extend it by implementing ISaveSection — SectionKey, Serialize and Deserialize — and Serenity ships InventorySection, MilestonesSection and CompletionSection out of the box. Completion percent and playtime are promoted to queryable columns, so a save-slot menu lists label, timestamp and completion without deserializing the full document. Every write is a per-slot atomic latest-wins upsert with cold-cache tick reconciliation, so a save is never silently dropped after an app restart.

GameProgress is a separate, single-row aggregate exposed through IGameProgressService — IncrementCompletionCount, UnlockExtra, IsExtraUnlocked, GetUnlockedExtras and a custom JSON payload for anything else your game needs to track globally. It persists independently of any save slot, so deleting a slot never touches it. Checkpoint quicksaves travel inside the active slot through CheckpointsSection, enabled by default, with Checkpoint's public API unchanged.

How it fits into Serenity

Both aggregates persist through the same pluggable structured backend the Leaderboard introduced — PlayerPrefs, LocalFile JSON, embedded SQLite, HTTP or a direct Postgres, MySQL or MongoDB connection — configured through UnityGameSaveSettings and UnityGameProgressSettings. Choosing a cloud backend for saves costs zero extra application code; it is a backend configuration change.

GameSave publishes a full signal surface — GameSavedSignal, GameLoadedSignal, SaveSlotDeletedSignal — plus no-code trigger pairs like RequestSaveGameSignal/SaveGameAction, RequestLoadGameSignal/LoadGameAction and ShowSaveSlotMenuSignal/ShowSaveSlotMenuAction, so menus and UI can request a save or load without a line of C#. Two authoring wizards, Create Game Save Settings and Create Game Progress Settings, configure slots, sections and backend together.

Practical workflow

  1. Implement ISaveSection for any custom data your game needs to save per playthrough, alongside the built-in InventorySection, MilestonesSection and CompletionSection.
  2. Configure the number of save slots and the persistence backend on UnityGameSaveSettings, or run Create Game Save Settings to do it through a wizard.
  3. Request a save or load through RequestSaveGameSignal or RequestLoadGameSignal — no direct service call required from UI code.
  4. Build a save-slot menu from the queryable label, timestamp and completion columns without deserializing full documents.
  5. Configure IGameProgressService through UnityGameProgressSettings or Create Game Progress Settings to track completion counts and unlocked extras independently of any slot.
  6. Enable RouteCheckpointsIntoActiveSlot so Checkpoint quicksaves travel with the active save slot automatically.

What you get

  • GameSave aggregate with configurable SlotCount and an extensible SaveDocument per slot
  • ISaveSection extension point with built-in InventorySection, MilestonesSection and CompletionSection
  • Completion percent and playtime promoted to queryable columns for save-slot menus
  • Per-slot atomic latest-wins upsert with cold-cache tick reconciliation
  • GameProgress aggregate with IGameProgressService — IncrementCompletionCount, UnlockExtra, IsExtraUnlocked, GetUnlockedExtras
  • CheckpointsSection routing Checkpoint quicksaves into the active save slot, enabled by default
  • Full signal surface plus no-code trigger pairs for save, load and slot-menu requests
  • Shared pluggable persistence backend with the Leaderboard — cloud saves without extra code

When to use this

  • Games that need multiple save slots with inventory, milestones or completion data without hand-rolling a serializer.
  • Projects that need global unlocks or completion counters to survive save-slot deletion.
  • Save-slot menus that need to list label, timestamp and completion instantly, without deserializing every save.
  • Teams that already use Checkpoint and Persistence and want quicksaves and cloud storage without extra wiring.

Related systems

Use Serenity when you want per-playthrough saves and permanent player progress to be two clearly separated concerns, both backed by storage you can swap without touching game code.

Back to the home page