The basic SaveGame tutorial takes five minutes and works perfectly — in the tutorial. Then you add a second save slot, or ship an update that changes your data, or notice the game freezes the frame it writes. Suddenly the five-minute version isn't enough. This post is about the parts the tutorials skip, the ones that decide whether you rewrite your save system halfway through the project.
The five-minute version (and where it ends)
Create a Blueprint child of SaveGame. Add variables for everything you want to persist. Save Game to Slot (the object, a slot name, a user index). To load: Load Game from Slot → Cast To your class → read the variables. That's the entire tutorial, and it genuinely works.
It ends the moment you want a second slot, an update that adds a field, or a save that doesn't stutter. Let's handle each.
Async, so the save doesn't hitch
Save Game to Slot is synchronous — it stalls the game thread while it writes to disk. On a tiny save you won't notice. On a big one, or a slow drive, it's a visible freeze — and some console certifications outright reject hitches during a save.
Use Async Save Game to Slot instead. It writes on a background thread and fires a Completed pin when done — the same latent-node idea from async loading. Keep the synchronous version only for tiny saves while paused or in a menu, where a few milliseconds of stall is invisible anyway.
Multiple slots without re-reading everything
Most games need several slots and a load screen that lists them: "Slot 1 — Level 3, 2h 14m, yesterday". The mistake is loading every full save file to build that list — slow, and you're deserialising data you won't even show.
The pattern: keep a separate, tiny metadata SaveGame holding just the display info — name, level, playtime, timestamp, maybe a thumbnail. The load screen reads only that. The full save loads only when the player actually picks a slot. Small file for the list, big file on demand.
Version your saves from day one
This is the one beginners most regret skipping. Add an integer SaveVersion field to your SaveGame now, before you ship anything. When a future update adds or renames fields, you check the version on load and migrate old saves instead of breaking them.
Without it, your first content patch invalidates every player's save. With it, you write a small "if version < 2, fill the new field with a default" and everyone keeps their progress. One integer now saves a support nightmare later.
Save data, not objects
You can't save a hard reference to a spawned actor or a placed level object and expect it back — the pointer means nothing next session. Save identifiers instead: a row name, an ID, a soft path, a transform. On load, re-find or re-spawn from those. (If hard vs soft references is still fuzzy, see soft references.)
And save only what you can't regenerate. Derived values, full world state, default settings — skip them. Save the delta: what the player changed, not the entire game world.
Plugin tip
The default SaveGame gives you one verb — write an object to a slot. Slot management, profiles, versioning, safe disk I/O: you build all of that yourself, every project.
FoxEasySave ships exactly that layer — slot- and profile-based saves, versioning, disk handling — as Blueprint nodes, so you keep your design and skip the plumbing. Optional, but it's the difference between a weekend and an afternoon.Common pitfalls
- Synchronous save on a big file. Visible hitch, possible cert failure. Use
Async Save Game to Slot. - No version field. Your first update breaks every existing save. Add
SaveVersionbefore launch. - Saving hard object references. They don't survive the session. Save IDs / soft paths and re-resolve on load.
- Loading the full save just to show the slot list. Keep slim metadata separate.
- Not handling "no save yet".
Load Game from SlotreturnsNoneon a fresh install. Null-check and start a new game. - Saving everything. Bloated files mean slow saves and slow loads. Save changes, not the world.
How to verify it's working
- After a save, check the file actually appeared: it lands in
Saved/SaveGames/in your project (and the platform's user folder in a packaged build). Print Stringthe result bool of the save, and theIs Validof the loaded object. Silent failures here are common — a wrong slot name loads nothing and says nothing.- Test the upgrade path on purpose: make a save, bump
SaveVersion, load it. If it crashes, your migration is the thing to fix before players hit it.
The 30-second recap
- Basic:
SaveGamechild +Save Game to Slot/Load Game from Slot+ cast. - Use
Async Save Game to Slotso writing never freezes the frame. - Keep slim per-slot metadata so the load screen doesn't read full saves.
- Add a version number before you ship — it's how old saves survive updates.
- Save IDs and deltas, never hard object references or regenerable data.
Next Sunday, votes permitting: DataTable vs DataAsset vs Primary DataAsset — three ways to store your game's data, and the real reasons to pick each one.
— Marco
Comments
Leave a comment