Game Save Systems - Implementing Persistent Data in Unity

A practical guide to implementing persistent data and save systems in Unity. Serialization, file storage, multiple slots, and patterns that scale from prototypes to shipped games.

Programming/Technical Mar 14, 2026

Game Save Systems - Implementing Persistent Data in Unity

A practical guide to implementing persistent data and save systems in Unity. Serialization, file storage, multiple slots, and patterns that scale from prototypes to shipped games.

By GamineAI Team

What Counts as Persistent Data?

Persistent data is any information that must survive between play sessions. Typical examples:

  • Progress – Level, checkpoint, quest state, inventory
  • Settings – Volume, controls, language, accessibility options
  • Unlocks – Characters, levels, cosmetics
  • Analytics or meta – Play time, session count (if stored locally)

Anything that lives only in scene objects or runtime variables is lost when the game closes. As soon as you want “continue where I left off,” you need a plan for what to save, where to store it, and when to read and write it.

Storage Options in Unity

Unity does not force one save format. You choose based on size, security, and platform.

PlayerPrefs

  • Key-value store; ideal for a small number of settings (volume, key bindings).
  • No built-in structure for complex state; avoid using it as your main save format for progress.
  • Persists per machine; location varies by OS.

JSON (or other text) to file

  • Human-readable, easy to debug and patch.
  • Use Application.persistentDataPath so path is valid on all platforms.
  • Good default for most indie games: one file per save slot or one global settings file.

Binary serialization

  • Smaller files and slightly harder for players to edit.
  • In Unity you often use a binary formatter or a library; be aware that .NET binary serialization can be fragile across Unity versions if you change type names or structure.

Cloud / backend

  • For cross-device sync or anti-cheat you add a server or a service (e.g. PlayFab, custom API). The local layer (what to serialize, when to save) stays the same; you only change where the bytes are sent.

For a first full save system, JSON under Application.persistentDataPath is a solid choice: simple to implement, debuggable, and easy to extend later.

Designing a Save Data Model

Keep the shape of your save data in one place. A single model (e.g. a C# class or struct) should describe everything you persist.

  • One type – e.g. GameSaveData with fields for level index, player position, inventory IDs, settings, etc.
  • Serializable – Use [Serializable] and only types that Unity’s JsonUtility (or your chosen serializer) can handle. Avoid storing references to scene objects; store IDs or simple values.
  • Versioned – Add a saveVersion or schemaVersion field so you can detect old saves and migrate them later.

Example skeleton:

[Serializable]
public class GameSaveData
{
    public int saveVersion = 1;
    public int levelIndex;
    public float[] playerPosition; // x, y, z or x, y for 2D
    public List<string> inventoryItemIds;
    public SettingsData settings;
}

[Serializable]
public class SettingsData
{
    public float masterVolume;
    public float musicVolume;
    public int qualityLevel;
}

Your game systems read and write this model; a dedicated manager handles loading from and saving to disk.

Centralizing Read and Write - Save Manager

A single point of responsibility for persistence keeps the rest of the game simple.

  • Save – Take the current in-memory save model, serialize it (e.g. to JSON), write to a path derived from Application.persistentDataPath and maybe a slot index or name.
  • Load – Read the file, deserialize into your save type, run optional migration if saveVersion is old, then return the model. The rest of the game applies that data to the world (load level, set position, apply settings).
  • Slots – Use different filenames per slot (e.g. save_0.json, save_1.json) or a folder per slot. The same manager can list existing slots (by checking which files exist) and support “New game” by writing a default or empty save.

This matches the pattern in our simple save/load guide: one data model, one manager, and game logic that pushes/pulls from the manager instead of touching files directly.

When to Save and Load

Load

  • Typically once at startup or when the player selects “Continue” or a slot: read the file, apply data to the game (load scene, set state), then run the game.

Save

  • On explicit “Save” action, at checkpoints, on quit, or at the end of a level. Avoid saving every frame; batch changes and write at clear moments to reduce I/O and avoid file corruption if the game crashes.

Settings

  • Often a separate file or key (e.g. settings.json or PlayerPrefs). Load at startup and apply to Audio Mixer, quality settings, etc.; save when the player changes options.

Multiple Save Slots

To support several save files:

  • Naming – e.g. save_0.json, save_1.json, or slot_1, slot_2.
  • Metadata – Store a small summary (level name, play time, last saved time) in the file or in a sidecar file so you can show a slot list without loading full save data.
  • Current slot – The active slot can be stored in PlayerPrefs or in a small “current_slot” file so the next launch knows which slot to load by default.

Same data model and same SaveManager; only the path changes per slot.

Migration and Versioning

When you add a field or change the meaning of an old one, use the saveVersion (or schema version) inside the file:

  • On load, if version is older than current, run a migration step: e.g. copy old fields into a new structure, set defaults for new fields, then resave with the new version.
  • Without this, old saves can break or lose data after an update. Versioning is especially important once you ship.

Security and Integrity (Optional)

For many single-player indie games, plain JSON is enough. If you want to discourage casual editing or detect corruption:

  • Checksum – Store a hash of the serialized payload; on load, recompute and compare. If it fails, treat as corrupted and fall back to default or ask the player to pick another slot.
  • Obfuscation – Encode or lightly encrypt the string before writing. Adds minimal security but can reduce accidental edits. Avoid relying on it for real anti-cheat; that usually needs server authority.

Platform Notes

  • Path – Always use Application.persistentDataPath for save files so you get a valid, writable directory on Windows, Mac, Linux, iOS, Android, and consoles.
  • Size – Keep single save files reasonably small (e.g. under a few MB) so they load quickly and do not run into platform limits.
  • Cloud – Consoles and some stores offer cloud save APIs. You still define the same save model; the difference is where the file is stored and how it is synced.

Summary

Implementing persistent data in Unity comes down to: (1) defining a single, versioned save data model, (2) using a central SaveManager to read and write it (e.g. JSON under Application.persistentDataPath), (3) saving at clear moments and loading at startup or when the player chooses a slot, and (4) optionally supporting multiple slots and migration for future updates. Start simple with one slot and one JSON file; extend to multiple slots and versioning as your game grows. For a minimal implementation, use our step-by-step save and load guide; for state management patterns across scenes, see Game State Management - Patterns and Best Practices.

Found this useful? Bookmark it for when you add save slots or migrate to a new schema.