Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

A Damsel In Need of Saving (It's Me)

Author: Jonah McConnell


Don't we all need to be saved now and again? We don't? Oh, that's just me... ok but we all want to be able to save our progress in games (roguelikes, stay in your corner, I'm watching you). Saving is an integral part of the majority of games these days. In one way or another, as you play a game it keeps track of your progress. With something so universal, it's only natural there'd be so many ways to go about it, especially in an engine as large as Unreal. That said, I can't say I expected them to be quite so divergent, or as poorly documented.


This meant that beyond a few blog-space tutorials, I was left to my own means. This meant a lot of looking at code like the code above in the Engine's source. After stepping through and debugging for a couple of hours, I understood Unreal's process for saving a binary file. However, getting to that point was a winding road, to say the least. My attempts at implementing a save system started with using the UPROPERTY specifier "SaveGame". However, despite searching for a few hours, I could not find anything concrete regarding using the specifier, aside from its general purpose.  What I found did not seem to line up since it involved setting that on any variables intended to be serialized, but the USaveGame child class was also supposed to define what it saved and set them manually in code. These two things did not seem to line up since from what I was reading, the whole purpose of using the "SaveGame" specifier was to avoid writing manual code. I opted for something I knew I could test (or so I thought as you'll later see), by serializing things manually into a binary formatter.

This had a lot more precise control over it. Mostly for readability but also in part because it was already written out, I left the specifier on all variables intended to be serialized. However, the approach I ended up using used an overload of a function from the generic FArchive class to allow for serializing and deserializing data. This class was fascinating to interact with because it already had serialization implemented for the most basic types, so I had to work my way down the chain of types I wanted to serialize and implement the operator for that specific type. Seeing the seemingly unending list of overloads for the operator was certainly daunting, but it also helped me understand just how expansive Unreal is as a piece of software. This implementation shockingly worked without much need to tweak things. The main issue I encountered beyond figuring out how to implement it, was simply finding where the file was saved to. This was what led to my previously mentioned foraging through the Unreal codebase via debugger. It also is the reason I alluded to that I couldn't test things as easily as I'd like. Because the binary was... well... binary, I couldn't verify the contents without attempting to load them back into the game. Once I finally verified where the file was saved (which sidenote, is entirely attributed to the wonderful speed of modern storage), I was able to try loading everything back into the game.

This led to tackling a lot of timing issues revolving around the player being ready, and access to Game Manager, so while it felt like a lot of time spent just to load at the right time, I felt it was more than justified since it solves many foreseeable issues in the future. But, luckily, I had little to fear once I was able to try to load the binary file. Because of the way the FArchive is architected, the operator works for both the FArchive implementation for saving to and from binary without any added work. Because everything works by passing a reference to one of the pre-defined types to the archive at its core, the archive can define all the work to convert to and from binary.  Because of this, so long as the order for serializing and deserializing is the same, the saving and loading go off without a hitch. The beauty of the design comes in that the operator you define is used both times. Therefore,  as long as you use the same order at the topmost level where you begin your interactions with the FArchive, by design the system simply works. This reinvigorated my appreciation for well-architected code, encouraging me to push myself to continue to focus on the architecture of my code throughout the project.

Support this post

Did you like this post? Tell us

Leave a comment

Log in with your itch.io account to leave a comment.