Savegames & New Versions

What is the best way to allow for changing a [SaveGame object](http://(since dictionaries are flexible structures that can hold anything)) in the future? I would like my users to be able to load their old savegames even after I push out an update that changes the savegame format.

In my business-logic life of Python scripting, I would do this by using a dictionary (since Python dictionaries are flexible structures that can hold pretty much anything, and they can be serialized to disk):


saved_stuff = {
    "id" : 1,
    "name" : "Bob"
}

And then future versions of my business logic could easily load the structure (regardless of what version it was) and have logic that would look for anything missing in case what was loaded was an old version. For example, if I added a “answers” member that stored a list of booleans, my parsing pseudocode would be:


saved_stuff = load_savegame()
if not saved_stuff.has_key("answers"):
    # Must be an old version of the savegame.  Give it the default answers
    saved_stuff"answers"] = [True, False, False, True]

(and saved_stuff would end up looking like)


{
    "id" : 1,
    "name" : "Bob",
    "answers" : [True, False, False, True]
}

And voila! Choosing that initial structure allowed me to simply update the loading logic whenever I updated the savegame format, and old savegame info is never lost, and I have forever-backwards-compatibility.

**Is there anything like that for UE4 SaveGames?

**If not, then what is the best (or alternately, most common) way that people deal with it? (Other than just nuking all SaveGames)

I suppose one strategy would be to create an entirely new SaveGame object each time the format needs to change, and then write a separate set of loading stuff that tries loading from the oldest format to the newest until it hits one that works, and make graphs translating from older objects to newer objects. I’m just hoping there’s something more elegant than that.

1 Like

Looks like you accidentally posted this in the wrong place!

Because…the Blueprint Visual Scripting forum isn’t the place to discuss strategies for dealing with SaveGame blueprints?

What is the right place that I should post?

Oh, I am so sorry. I saw code and thought you had entered the wrong section accidentally.

No worries. For a minute there I thought I had missed some “Advice” section or something!

Instead of saving directly to the save game blueprint, use a proxy such as a data-table to save game information. Then just save and load from the information that’s in the data table.

I was under the impression that data-tables were imported in the editor, and then used read-only at runtime. Are you saying there’s a way to modify and save data-tables at runtime? If so, could you tip me off on how to do that? I’m happy to read the docs…but I can’t find anything about that in the docs.

I’ll post a few basic nodes once i get off work tomorrow.

Was looking a bit more into it, and you’re right, data tables can only be read during run time (hopefully this changes, data tables really need love). Instead of using data tables, structs would be the way to go, since those can be read and written to. When dealing with data tables, a structure is the row header template. However, if you create a Structure, add it in the blueprint as an array, that’s basically a roundabout data table that can be read and written to, thus, saved.

Yes, so back to my original question. How do you structure SaveGames so that if you change them, you don’t lose information saved with an older version? Am I correct in assuming that simply changing a struct will render existing files unloadable?

Depends on how you’re changing them. There wouldn’t be a one answer fits all.

In your example, the original struct would have 2 items: id, name; the replacement struct would have 3 items: id, name, answers.

This should be pretty straight forward I would think since if they update, the current save game would just have null values when getting the struct elements. I would include a saved game version checker as the first value as well as in the name of the struct:

Struct_SavedInfo01
version
id
name

Struct_SavedInfo02
version
id
name
answers

Then it’s just a matter of attaching pins to where you need to, and instead of re-saving Struct_SavedInfo01, save 02 on top of it.

I may not be 100% correct here, but I remember digging into this a lot a while back.

The way the “save game” system works is that it takes your variable name (as a string), the data type, and the data value in that variable and then it serializes these data attributes into a binary file.

When you deserialize a save game file, it tries to match variable names and data types to whatever objects your deserializing into. If a variable name was not found, it is skipped. The handy thing about this is that you don’t break deserialization offsets…
I may be totally wrong on all of this, it’s been a few months.

Anyways, assuming I’m correct, if you have an old save game struct with two variables:



struct MyData
{
   int32 Health;
   FString Name;
};


Now you have made an update to your game and your struct looks like this:



struct MyData
{
   int32 Health;
   FString Name;
   float Energy;
};


If you deserialize the old binary data into the new version of the struct, it’ll only be able to find valid data for “Health” and “Name”. Likewise, if your friend is running a newer version of the game and sends you a save game file from his version, deserializing will be able to populate the old struct values and the new ones will be skipped.

Again, I may be totally wrong. Try an experiment to see if it’s true.

One strategy you could employ is to encode a game version value into the first four bytes of the save game file (as an int32). Before you try to load a file, you could read this value to see if the save game is compatible with the current version of the game. Every release where you modify the binary file structure, increment this value. I think UE4 does something similar with their assets in “\Engine\Source\Runtime\Core\Public\UObject\ObjectVersion.h” using a giant enum.