Sep 25, 2024
Savegame Assets for Development and Debugging
Unreal Engine comes with a savegame system out of the box, where your savegames inherit from the USaveGame class. If you want to make the most out of this system and provide an easy way to test gameplay and debug issues, creating savegame assets is a great way to go about it.
Why Use Savegame Assets?
Savegame assets can help overcome two types of challenges:
- Consistently recreating a specific state of the game to test features and corner cases
- Debugging errors reported by players
When games become complex, it can get really difficult to test features that are only available past a certain point. Maybe they’re unlocked after a quest is completed or players need to have acquired certain skills or items. In short: the game needs to be in a certain state in order to test a certain feature meaningfully. It would be great if we could just prepare a savegame that represents that state and load it up whenever we need to.
Another thing that every game develper will eventually have to deal with are errors reported by players. Being able to reproduce an error is necessary to fix it. Ideally, players would be able to send a savegame that triggers the bug along with their report so we can inspect it and load it up to reproduce the error on our side.
This is what we’re going to build.
What’s an Asset?
Assets are the building blocks of every Unreal Engine game. They’re imported or created in the engine and are stored in the content directory. Think textures, materials, animations, audio files etc. They contain data and are reused between all objects that use them.
What’s a Data Asset?
As a game developer, you will probably want to create your own asset types at some point. In Unreal Engine you do that by inheriting from the UDataAsset class, or in our case the UPrimaryDataAsset class. Here is a minimal code example of what that would look like:
|
|
You would then create an asset of that type in the editor’s content browser by opening the import menu, navigating to Miscellaneous → Data Asset, and selecting the type from the list. Open the new asset and this is what you’ll see:
Creating Savegame Assets
Let’s build a minimal savegame class and a matching data asset.
|
|
|
|
The savegame keeps track of the player’s name and a list of unlocked levels, which we store as array of numerical ids for simplicity’s sake. We also store the savegame’s name and a timestamp for when it was saved.
Pay attention to the UPROPERTY
declarations in the asset class. They need to include at least
EditDefaultsOnly
in order to be able to edit them directly from the content browser. It’s somewhat
inconvenient that we have to duplicate these properties and keep them in sync between the two
classes, but you’ll get used to it and it’s not something that needs to change terribly often during
development.
The last step is to create a savegame asset and store some data in it:
Converting Asset to Savegame
Now, the asset itself is not the savegame, so we need a way to transfer the data when the game starts. The best place to do that depends on your particular setup, so let’s just use the UGameInstance class for now.
The idea is this: the game instance has a property that stores a reference to a savegame asset. When the game starts, either as a shipped build or PIE, that asset is used to create the default savegame: the one that’s available right from the beginning, the state of the game when starting a new playthrough. Here’s a simple implementation:
|
|
|
|
Remember, this is only necessary for the initial savegame. It will be overwritten whenever you load a savegame from a previous checkpoint. It represents the state of the game when players start a new playthrough. It should also go without saying that the examples above are intentionally kept simple to illustrate a point. You will have to put this logic elsewhere depending on your specific use case. We do that too.
What we have so far is the ability to load a savegame that we have constructed to represent a particular state the game can be in. We can test the game in that state, find bugs and inspect edge cases. And we can start doing that the moment we hit the Play button in the editor.
Let’s say we want to use this with a savegame that a playtester sent us, along with a description of how to reproduce a specific bug. We can load their savegame without a problem, but what if we want to look at the actual data?
Converting Savegame to Asset
In order to turn a savegame file into a savegame asset, we’re going to write an editor module that does the job.
First, some boilerplate. We need to set up the editor module folder and register it in the build
system. If you’re not familiar with the process, you can read up on it in the
official docs.
Once the module is set up, we’re going to build a UMG widget that allows us to select from a list of
savegames and convert the selected savegame to an asset, stored somewhere in the content browser.
Let’s call the class USaveGameInspector
. This is a minimal implementation:
|
|
These are the important parts. First, there’s a function to find all savegames. For now, we’re just looking in the editor’s default savegame location. You can easily change that, but it’s usually good enough for most cases. The rest is pretty straightforward: read the file name and modification date and put it all in a list.
|
|
And here is the implementation for the function that converts the savegame to an asset:
|
|
We create the asset object and write the savegame property values to the asset’s. The only tricky
part is saving the asset in a way that turns it into a file you can select in the content browser.
That’s what the whole UPackage
stuff does.
You might notice some similarities betwen this code and the function in the game instance class that writes the asset’s values to the savegame. That means we now have a complete roundtrip of the data.
Building the Editor Utility Widget
The last step is creating the editor widget that we will use to call all this functionality.
In the editor, create a new utility widget and select our widget class as the parent. Next, populate the widget tree by creating three widgets inside it:
- a combo box widget that is going to contain all the savegames that were found,
- a button for rescanning the savegame folder to find new savegames,
- a button that calls the conversion code.
There are some other widgets in the screenshot that are used for grouping and layout. We’re not going into these here, but you’re free to go wild with whatever layout you prefer, of course. Hook up the functions to the buttons and you’re done.
The Final Result
Let’s see all of this in action with Days of Defiance, an upcoming game that’s being developed at Robo Poets. In the video below, we set a character’s starting location in the savegame asset. We move around a little, save the game, and load the saved game into a savegame asset where we can inspect the character’s saved position.