A game with no memory is a game the player abandons after the first session. Today we give your game the ability to remember progress between sessions.
In previous lessons we worked with data structures that live in memory while the game runs: positions, velocities, animation states. The moment you close the program, all of that disappears. A save system takes that state and writes it to disk so it can be rebuilt exactly the same way next time.
The main risk isn't writing the file — that part is easy. The risk is compatibility: what happens when you update your game and add a new field to the data structure, and now you have old save files that don't match the new format. We'll design the system with this in mind from the start.
The most direct way to save data in C++ is to write a structure's memory straight to a file with `fstream` in binary mode. It's fast and compact, but fragile against structural changes if not handled carefully.
Notice the `version` field at the start of the structure. You should always include a version number in your save formats — it's what lets you, in the future, detect an old file and migrate it instead of misreading it and corrupting the game state.
Writing an entire `struct` with `write()` works as long as the structure has no pointers or dynamic containers (`std::string`, `std::vector`). If your `SaveData` included a `std::vector<Item> inventory`, writing the raw memory would write the vector's internal pointer, not its data — and loading the file in another run would leave that pointer invalid. This is exactly the kind of bug worth asking your AI assistant to review before trusting a save system: "Is this structure safe to serialize with a raw `write()`, or does it contain something pointing to dynamic memory?"
For data that needs to be human-readable, hand-editable, or versioned with more flexibility (like variable-length inventory lists), a JSON-style text format is more robust. You could use a library like `nlohmann/json`, but to understand the concept from first principles we'll write a minimal manual serializer.
This manual serializer is useful for understanding the problem, but for a real project I'd recommend using a proven library like `nlohmann/json` instead of maintaining your own parser — writing a correct JSON parser (with quote escaping, negative numbers, nesting) is more work than it looks. This is a perfect case for asking your AI assistant to wire the library into your `CMakeLists.txt` and show you the equivalent using `nlohmann::json` instead of the manual serializer.
Never blindly trust a save file, even one your own game generated — it could be corrupted by an abrupt shutdown, a full disk, or manual editing by the player.
If your game closes (or crashes) right while it's writing the save file, you can end up with a half-written file. The standard technique is to write to a temporary file first and, only if the write succeeds, replace the original file.
`std::rename` is an atomic operation on most filesystems: either the new file completely replaces the old one, or nothing happens — you never end up with a corrupt intermediate state.
With persistent saving handled, in the next lesson we'll organize the game's overall flow (main menu, gameplay, pause, end screen) with a state machine, which is the structure that will let you load a saved game from a menu cleanly.
Carlos Montiel is an enterprise AI solutions architect. He implements LLMs, Agents, RAG, and orchestrators for companies across Guatemala and Latin America. Reach out for a consultation.
Contact Carlos Montiel