Save Load

What can I do with Save/Load addon?

  • Save/load the state of the database to/from byte array during runtime on players devices
  • Define which table and which fields you want to be saved/loaded using merge settings (it won't work if you skip this step)
  • Create C# class for fine-grained row-level control.
  • You can save the whole database without Save/Load addon (click)

Should I use additional save/load solutions besides this addon?

It's perfectly fine to use BGDatabase as the only solution for saving/loading, but it's a safer option to use a professional Save/Load solution and save database content as a part of all saved data (as a byte array field), even if you do not have any other data to save at the moment.

Restrictions

Data inside horizontal partitions, created with partitioning addon can not be saved/loaded properly

How can I enable it?

Choose Addons->Save Load, click "Enable", mark tables/fields for saving/loading and save the database.

Examples

The following projects use SaveLoad addon to save/load runtime data:

  1. Basic 3D example
  2. Basic 2D example
  3. Inventory example
  4. Localization example (localization addon is required)

How it works?

SaveLoad addon can create multiple, configurable snapshots of database data, and it can restore database state from these snapshots. Snapshots are constructed from the data, you marked as changeable, using Merge settings, described below. (Why it works like this?) Please, make sure you marked all data you want to be saved.

[Important] Loading with SaveLoad addon results in whole database to be reloaded, so if you use SaveLoad addon do not store references to tables/fields/entities, because they will be obsolete after loading.

Why it works like this?

Here is the quick explanation and main reasons why it works the way it works and why it's much better than simply saving the whole database.

  1. we want to be able to change database between public game releases
  2. we want to make sure the old saves, which are made with old version of database will remain compatible with new database
  3. As a result of loading old save, we want to keep all saved data, and we want to keep the changes we made to default database (shipped with a game)

Here is the detailed illustration about what we want to achieve:

Another diagram:

Read more about:
  1. How to change your database and keep it compatible with old save files - read the section below
  2. How to implement row-level control over merging - read Row-level controller section below.

How to use it?


//to save
byte[] bytes = BGRepo.I.Addons.Get<BGAddonSaveLoad>().Save();

//to load
BGRepo.I.Addons.Get<BGAddonSaveLoad>().Load(bytes);

Once you obtained bytes array using Save method, you need to write it to some persistent storage (file or cloud drive) See this minimalistic example for more details. There are also custom units/actions for Visual Scripting tools are available (see the full list at the bottom of this page)

Is it possible to save/load the whole database?


// !!!! This is not recommended !!!!
//to save
byte[] bytes = BGRepo.I.Save();

//to load
BGRepo.I.Load(bytes);

The above call does not support Partition and localization addons currently

Using Merge settings

Using Merge settings you can choose which data to save/load. You want as fewer data to be saved as possible. You do not need to include data, which is "static" and can not be changed during play session.

There are only 2 rules for using merge settings and condition for choosing one or another is whether rows can be added or removed at runtime.

Rule #1 Rule #2
Condition Rows can NOT be added/deleted at runtime Rows can be added/deleted at runtime
How to apply Turn on "Update matching". Optionally turn "Chose fields to update" on and mark the fields you want to be saved. (Tip: do not mark the fields which can not be changed at runtime) Turn on "Add missing", "Remove Orphaned", "Update matching".
Example We use our Basic 3D project as an example
Rows from Player table can not be added or removed, so we do not turn on "Add missing" and "Remove Orphaned". There is no need to save a name field, so we do not include it into settings Rows from Collectable table can be added and removed, so we turn on "Add missing", "Remove Orphaned", "Update matching"
Screenshot for example

How to change your database and keep it compatible with old save files?

  1. Do not include static data (the data, which can not be changed during game session) to save/load merge settings
  2. While developing new, public version of your game - you can change anything in your static data with one simple exception: you can not delete rows which may be referenced from saved data. Well, actually you can, but in this case you need to make sure your code should handle null relation values properly.(and they will be null if you remove the rows)
  3. As for the data, which is saved within database snapshot (save file),
    • if you mark the field for saving using merge settings ("Update Matching") it will be loaded if the target row still exists in the default database
    • if you mark the rows for saving ("Add missing" and "Remove Orphaned") the whole table will be updated with values from the saved file during loading, if this table (meta) still exists in the default database.
    • if you want fine-grained, row-level control over loading - you can do it with C# controller class (read the next section for more details).

Here is an example from our basic 3D example project:

Row-level control over loading

Merge settings give you control over which tables/fields you want to save. But what about particular rows? What if you want to update/add/delete only those rows which comply to some condition?

This can be done with C# controller class, which can cancel update operation before it actually takes place. Currently, there is no any merging on saving data, only on loading (but we reserved "merging on saving" option for future releases)

Here is a quick example of custom controller class. This controller is made for our basic 3D example project.

The main objective of this example controller is to prevent deleting Collectable rows for added scenes (scenes which was added between public game releases)

Here is the code
using BansheeGz.BGDatabase;

    // Warning!!! this controller is implemented without code generation
    //and may contains ugly boilerplate code
 public class MyCustomSaveLoadController : BGMergeSettingsEntity.IMergeReceiver,BGMergeSettingsEntity.IRemoveOrphanedReceiver
 {
     private bool loading;

     public bool OnBeforeMerge(BGRepo from, BGRepo to)
     {
         //here we want to know, if it's loading or saving?
         //... Note (currently there is no merging on saving, but it can be implemented in future releases)
         loading = to == BGRepo.I;
         return false;
     }

     public void OnAfterMerge(BGRepo from, BGRepo to)
     {
         //no need to write anything here
     }

     //this callback is invoked before row removal. Return true to cancel this operation
     public bool OnBeforeDelete(BGEntity toEntity)
     {
         if (loading)
         {
             if ("Collectable".Equals(toEntity.Meta.Name))
             {
                 //before deleting Collectable row, we want to make sure- it's not connected to newly added scene
                 var scene = toEntity.Get<BGEntity>("Scene");
                 //if scene's version more than 0 - cancel removing by returning true
                 return scene != null && scene.Get<int>("version") > 0;
             }
         }

         return false;
     }
 }

To see this controller in action:

  • Download default 3D example project here
  • Run the scene (Assets\BGDatabaseBasic3DExample\Scenes\BGExample1.unity), collect some objects and save the game
  • Let's assume this was made by a real player with our game version 1, and now we want to develop version 1.1, and we want to add a new scene. We could add a new scene without any controller and without any issue, but we want to populate this scene with Collectable rows, and we do not want them to be deleted while loading save file. Without this controller, all Collectable rows will be overriden with the data from the save file, cause in SaveLoad merge settings we marked this table for "addingMissing" and "removingOrphaned" (which basically mean the whole table will be restored from save file)
  • At this point we start to develop game version 1.1. We will omit real scene creating, cause all we want - is to make sure our save file will be loaded properly, and it will not erase our newly added collectables for new scene
  • Open BGDatabase window, choose Configuration->Scene and add new int field, call it "version"
  • Then choose Database->Scene and add a new scene
  • For newly created scene assign "version" field to 1
  • Choose "Collectable" field for new scene and add some Collectables
  • Now choose Addons->SaveLoad, and assign "Controller Type" field to MyCustomSaveLoadController (this is a class name of our controller)
  • Save the database and run BGExample1.unity scene again
  • Load the game, choose Database->Scene and make sure all saved Collectables remain intact, just like newly added Collectables for our new scene. Also, you can run the same example without controller assigned to see the difference
Read more about:
  1. row-level controller on export/import page

Row-level control on saving

To filter out rows while saving the data, your C# controller should implement ISaveLoadAddonSavedEntityFilter interface. Method bool OnSaveEntity(BGEntity entity); will be called for every row, and it should return true if you want to skip provided row.

BeforeSave event

You can declare you MonoBehavior script to be a receiver of BeforeSave event, so it could update the database before saving (code from the example project)
//this class implement BGAddonSaveLoad.BeforeSaveReciever interface,
//so OnBeforeSave method will be called before saving
public class BGPlayer : BGM_Player, BGAddonSaveLoad.BeforeSaveReciever
{
    ...
    //this method is called before saving
    void BGAddonSaveLoad.BeforeSaveReciever.OnBeforeSave()
    {
        //save current position , rotation and scene to the database
        m_position = transform.position;
        m_rotation = transform.rotation;
        m_scene = BGE_Scene.FindEntity(scene => string.Equals(scene.Name, SceneManager.GetActiveScene().name));
    }
}
Note, callback method is called on all loaded objects, including inactive game objects and prefabs. Use the following code (at the beginning of your callback function) to filter out inactive game objects and prefabs.
        // use the following line to filter out components, attached to inactive GameObjects
        if (!gameObject.activeInHierarchy) return;

        // use the following line to filter out prefabs
        if (!gameObject.scene.IsValid()) return;

Additional configs

Starting with BGDatabase version 1.8.0, additional optional configs can be created for SaveLoad addon to save/load different data, for example game settings. Here is how to use it:

  1. Create an additional config with name Settings and include game settings to this config
  2. To save current settings data use this code
        byte[] settingsSave = BGRepo.I.Addons.Get<BGAddonSaveLoad>().Save(new BGSaveLoadAddonSaveContext("Settings"));
  3. To load game settings on your application startup, use this code
        // ReloadDatabase parameter, set to false, prevents database reloading
        BGRepo.I.Addons.Get<BGAddonSaveLoad>().Load(new BGSaveLoadAddonLoadContext(new BGSaveLoadAddonLoadContext.LoadRequest("Settings", settingsData)){ReloadDatabase = false});
  4. To load saved game using default config, use the following code. Preserve request is required to not lost settings data during main database reloading.
                BGRepo.I.Addons.Get<BGAddonSaveLoad>().Load(
                new BGSaveLoadAddonLoadContext(new BGSaveLoadAddonLoadContext.LoadRequest(BGAddonSaveLoad.DefaultSettingsName, gameSessionData))
                {
                    //the data for "Settings" config will be saved before database is reloaded and loaded after database reloading
                    PreserveRequests = new List<BGSaveLoadAddonLoadContext.PreserveRequest> { new BGSaveLoadAddonLoadContext.PreserveRequest("Settings") }
                }
            );

Example project can be downloaded here (ignore the errors on startup and import BGDatabase asset after opening the project). Run Assets\Scenes\SampleScene.unity scene and keep in mind that Settings config saves Settings.value field only.

Additional downloads

  1. Custom actions for Playmaker to support SaveLoad addon
  2. Custom units for Bolt to support SaveLoad addon
  3. Custom nodes for Flow Canvas
  4. Node Canvas integration package has custom actions to use with SaveLoad addon
  5. Behavior Designer integration package has custom actions to use with SaveLoad addon
  6. UNode integration package has custom nodes to use with SaveLoad addon
  7. Game Creator integration package has custom actions to use with SaveLoad addon