12.7.2019 - Lauri "murgo" Härsilä
Hi, I’m Lauri and I am a code warlock working on Iron Danger. Lately I’ve been working on one huge feature that was missing from the game: the in-game saving system. We already had a campaign progression saving system that kept track of what level you were on, what character rewards were selected etc. but as our levels grew longer we felt we’ll need the ability to quit mid-level and continue where you left off.
Saving the full state of the game is notoriously difficult to do well. We pondered if we could do something like save points to make the system simpler, but our level design is pretty non-linear for most longer levels where the saving is actually needed, so save points are not a good fit for us. Most of the difficulty about saving the game comes from serializing the state of the game to disk, and especially deserializing that data back into the game. We have hundreds of different kinds of objects in the scene at one time, all with some internal state and building that state from a file with all the references between the objects can be painful.
Snapshotting logic to the rescue!
For our time rewind mechanics most of our components have a feature we call snapshot recording. There probably will be another blog post about the time manipulation stuff, but in short all rewindable components record their rewindable variables (like position, direction, animation state etc.) each frame into snapshots, and when rewinding time the state of those components is read from the snapshots. Thankfully we can use this code for our saving, too. When saving the game, last snapshots from all objects is copied to more serializer-friendly data structures, most of which can be directly serialized to file using the pretty awesome Odin serializer. When loading the game, snapshots are loaded and deserialized from the file and fed into the objects, so the rewinding logic can try to set the internal object states based on these snapshots. Not all needed data is recorded on these snapshots but for maybe 80%+ of the cases the logic was already there.
References between scene objects are saved as a textual identifier that is assigned on every object on save/load time that currently look like this: “/0.LevelWorld/0.Terrain/0.TerrainProps/0.PlayerCharacters (1)/0.Kipuna_Injured”. It’s basically just a path to the object in scene hierarchy with the number differing similarly named sibling objects from each other. If referenced object is not found in the scene, it is spawned on load using extra spawn metadata that is saved along with other data. Prefabs, components and other stuff have similar referencing scheme. This allows us to edit the levels without breaking all links between level data and saved objects.
Now about hundred commits later the in-game save system kind of works! There still are some bugs and issues that remain to be resolved like save versioning and change control so we can better edit the level after launch without breaking the saves, but I’m sure we’ll figure them out.