Core

Persistence in AAK is built as simple as possible for the consuming components. Therefore we will start exploring persistence from this side and work our way towards the persistence management.

Most built in components of AAK simply reference a PersisterBase to keep their own persistence logic as slim as possible. That PersisterBase has methods for checking if a value has ever been set, retrieving it, setting it and clearing it out. For example the ObjectAction just calls Set(true) when it is triggered and whenever it is started in the future(scene/game reload) it calls Get() to check if it has been triggered already.

Persisters can come in different forms but there is one called ManualPersister that is meant specifically for the case above where some other component needs to save its state. The persister has a field for a key which is needed to identify its data and one for the area. A persister is convenient to keep persistence logic out of game logic but not necessarily needed, the PickupAction for example just deals with persistence itself without using a separate persister.

Another kind of persister is the DestructionPersister which simply sets a bool when it gets destroyed and destroys its GameObject if that bool has been set in the past. This means you can just add this persister to any destructible object to persist the destruction without any other scripting needed.

A PersistenceArea is a ScriptableObject found in Adventure/PersistenceArea that groups together data that will be saved together. This can be useful to separate smaller data that is saved frequently from larger data that is saved less frequently. The PersistenceArea also has the IsGlobal flag which is meant for data that is saved independent of save slot(settings for example).

The PersistenceContainer is the central singleton manager for persistence that is needed in every scene that uses any kind of persistence. This is where all the persistence data is managed and eventually sent to the saver that actually saves it to a storage medium. As you might have guessed from the above ObjectAction example state persistence in AAK is pushed rather than pulled. This means that state is not collected when a save game is created, instead state is sent to the container whenever it changes and the container decides when it actually saves the state. The container can be set to AutoSave so that is saves at the end of the frame whenever anything has changed, otherwise a Save() has to be called by some outside component like a save button.

In the inspector for PersistenceContainer you can find some useful helper buttons that can be used to deleted data when debugging or to check the keys of the persisters in the scene for duplicates. It can also generate random GUID keys for any persisters with an empty key field. So if you have objects that do not necessarily need a readable key just leave the key in their prefab empty and generate them all before you use the persistence.

As mentioned above the container does not save to disk itself, instead it references a PersistenceSaverBase for that purpose. This is done so that the ‘device-facing’ part of persistence is easily replaceable as I anticipate it might have to be customized depending on the built platform and other factors.

Souls

State in AdventureSouls is split into a couple different areas. SoulsSystemPersistence is a global area independent of the save slot that is used for settings like the sound effects volume but also to save a short info structure for every save index that is displayed in the title screen. SoulsPlayerPersistence is just used by the player character, the player gets its own area because it is saved quite frequently. The SoulsPermanentPersistence is used to save permanent world state like collected items or pulled levers. Lastly SoulsTemporaryPersistence is where any state resides that is discarded whenever the player changes the level or sits at a bonfire. It contains the state of the enemies and destructible environment objects.

The PersistenceContainer is part of the SoulsSetup prefab which contains all the necessary setup to run the game. Basically if you create a new scene and add the setup as well as a plane for the player to stand on you can start the game and it’ll work. If you check the container on the setups in the debugging scenes like Scenes/Debugging/SoulsDebuggingGeneral you may notice some overrides. For one the key is overridden so that data saved for that debug scene does not override data from the actual game scenes. Also the saver has been removed for most of them, this completely disables persistence because for most debug scenes a complete reset is more useful. If you want to debug the persistence logic you have to reassign or revert that saver.

Hero

Persistence in the hero demo is a bit more straightforward than in souls. There is only one PersistenceArea for game data called HeroGamePersistence which saves all the data per save slot. The second area called HeroSettingsPersistence is only used for global settings like sound effects volume or visual quality.

Movement is not persisted, the current scene is set in the ‘Scene’ variable and the desired entry point is saved in ‘Parameter’. The game is saved automatically in scene transitions(HeroExit) which also write the current scene or when the game is ended from the menu. When the game is loaded the scene set in the variable is opened.

Whether, for example a heart shard has been picked up or a cracked wall bombed is persisted using a DestructionPersister.