Loading And Saving

Abstract

Problem: How should game developers approach implementing load and save systems?

Approach: Tim Cain provides a high-level overview of save system design, covering the types of save systems, UI decisions, serialization strategies, and versioning β€” without writing a single line of code.

Findings: Save system design is primarily about upfront decisions: what type of saving to support, how to serialize data, and how to handle versioning. These architectural choices constrain everything that follows.

Key insight: The hardest part of load/save isn't the code β€” it's the cascade of design decisions you must make before writing any code, and how each choice constrains or simplifies every system in your game.

Source: https://www.youtube.com/watch?v=S_gE99yeK6c

Types of Save Systems

Tim outlines a spectrum of save system types, ordered from hardest to easiest to implement:

Save Anywhere, Anytime

The most player-friendly but hardest to implement. Every system in the game must be able to serialize its state β€” including animation mid-frames, particle effects, physics states, and AI decision-making. Third-party engines (e.g., physics) may not expose enough state to read or set, making true "save anywhere" impossible in practice.

Save in Some Places / At Some Times

Common restrictions include:

  • No saving during combat β€” avoids serializing combat AI, moment-to-moment decision states, and active animations
  • Save only in town β€” further restricts state complexity by eliminating combat, dungeon, and dynamic world state
  • Save only when resting β€” guarantees no active dialogue, spellcasting, falling, or particle effects, allowing many systems to completely ignore load/save

Checkpoints

Common on consoles. The game automatically saves at predefined spots (physical locations, end of story acts). This is the easiest to implement because you define exactly when and where saves happen, and most systems never need to know about save/load at all. Only story-relevant variables need to carry over.

Autosaves and Quick Saves

Autosaves are checkpoint-like saves triggered automatically (e.g., before a boss fight, on map transitions) to prevent the player from ending up in a bad state. Quick saves let the player save with a single button press β€” no UI, no questions. They typically use a rolling slot system (e.g., three slots that cycle), since there's no UI to ask which slot to use.

UI Decisions

Before writing save code, several UI questions need answers:

  • How many save slots? Unlimited (until storage runs out) or a fixed number? Different slot limits can apply to manual saves vs. autosaves/quick saves
  • Save game naming: Do players type custom names ("just entered the Dungeon of Doom") or are names auto-generated from location, sublocation, and timestamp?
  • Save game images: Historically too expensive (the screenshot was often bigger than the save file), but in the modern era, attaching a screenshot is easy and helps players identify saves β€” especially when multiple saves share similar auto-generated names

The Save Code Itself

Tim's approach: every class/module gets a load method and a save method. Each is passed a file pointer, writes out or reads in its own data, and returns.

File Pointer Positioning

Two strategies with different tradeoffs:

  • Trust the pointer position β€” each method assumes the file pointer is already at the right spot. Fastest approach, but if any module reads the wrong amount of data, the error propagates to every subsequent load. These bugs are notoriously hard to debug because they manifest in modules that aren't the source of the problem.
  • Self-repositioning β€” each method finds its own section in the file. Eliminates propagating errors and makes it easy to add new sections, but is always slower due to extra seeking/indexing.

Raw Data vs. String-Based (JSON)

  • Raw binary data (e.g., fwrite/fread in C): Fastest and smallest save files. But requires hand-serializing everything including pointer reconstruction and memory allocation during load. A lot of manual work.
  • String-based / JSON: Human-readable, much easier to write code for. Sections can be labeled with string headers (e.g., [combat_code]), making it trivial to find the right section. But produces larger, slower save files.

Versioning

Versioning is the problem of loading save files across different versions of the game:

  • Can version 1.1 read version 1.0 saves?
  • Can a save made with DLC installed be loaded without the DLC?
  • Can saves from modded games be loaded without the mod?

String-based formats handle versioning much more gracefully β€” unrecognized sections can be skipped with sensible defaults (e.g., a DLC item simply disappears from inventory). Raw binary formats can support versioning too, but it's harder.

The UI also needs to handle version mismatches: should incompatible saves be grayed out, display an error message, or attempt a best-effort load of the base game data?

Summary

Tim's framework for thinking about load/save:

  1. Decide the save system type β€” save anywhere, restricted saving, or checkpoints
  2. Make UI decisions β€” slots, naming, images, version display
  3. Choose your data format β€” raw binary (fast, small, hard) vs. string-based (slow, large, easy)
  4. Plan for versioning β€” patches, DLC, mods

The entire topic is complex enough that Tim deliberately stays at the architectural level, directing viewers to look up "serialization" tutorials for their specific language and engine.

References