Save System

The save system can save the current state of the inventory, and other component, to disk. This state can then be restored by loading the file that it was saved to.

The Ultimate Inventory System Save System is meant to be either extended or used within an existing save system.

Save System Manager

The Save System Manager is responsible for saving the data to the disk. Serialized data can be saved by registering it with the SaverBase component. When the manager is saving it will loop through the SaverBase components and retrieve the data that needs to be saved. It will then serialize that data to disk.

At the time of loading, the Save System Manager will go through all registered Savers and load the data. In most cases the Saver component will register itself to the Save System Manager during its Start function.
In the case the Save System Manager loads the data before Saver was registered, the Saver can fetch the loaded save data during its start function by having the “Load On Start” field set to ON.

By default the save folder path is defined by the Unity property “Application.persistentDataPath“, learn more here. Right-Clicking the Save System Manager will show an option to print the save folder location for your computer in the console.

Saver Components

Any components that contain data that should be saved will inherit the SaverBase class. The Inventory Saver component is an example which contains data that should be saved. The Inventory Saver will serialize and deserialize the items that are within of the Inventory. Your Inventory content will be saved/loaded by the Save System Manager if the Inventory Saver component is added to the same GameObject as the Inventory.

The Savers available out of the box are:

  • InventorySystemManagerItemSaver : The main saver component. It is used to save the Item states (attribute values, names, etc…). All savers which save Items only save the item IDs. They notify this component to save the actual Item by serializing it once even if it is used in multiple places. Without this saver the “InventorySaver” and other savers which save Item IDs won’t work (except for Immutable & Common Items)
  • InventorySaver : Saves the content of an Inventory component by saving the Item IDs and amounts in each Item Collection.
  • CurrencyOwnerSaver : Saves the content of a Currency Owner.
  • InventoryGridSaver: Saves the positions of your Items within the Inventory grid.
  • GameObjectSaver: A generic save component that can be used to save position, rotation, scale and active/inactive states of the components.
  • ItemShapeGridDataSaver: Saves the Item Shape Grid Data to keep track the positions of the items within the shape grid.

Some Savers are specific to integrations, for example Ultimate Character Controller (UCC) is incompatible with the standard Inventory Saver:

  • InventoryBridgeSaver : Saves the content of an Inventory which is bound to a UCC character.

Save Data

Save Data is the object that is saved to disk. This contains the serialized data of the inventory state as well as metadata such as the time that it was created. Since the save system is very extensible it may save anything which can be serialized by our serializer.

Save Meta Data & Save Meta Data Creator

To prevent all the saves files to be loaded in memory at all times to preview the information about the save within the UI (for example the time the file was saved), we split the save files in two. The Save Meta Data just includes some context about the save file you wish to actually load.

The Save Meta Data and Save Meta Data Creator can be extended to add custom information such as the last level loaded, time played, player progress, etc… The save meta data is accessible in the Save Menu UI to customize the Save Views.

Here is an example of a basic Save Meta Data and Save Meta Data Creator

/// <summary>
/// A basic save meta data creator.
/// </summary>
[CreateAssetMenu(fileName = "BasicSaveMetaDataCreator", menuName = "Opsive/Save System/Save Meta Data Creator.")]
public class BasicSaveMetaDataCreator : SaveMetaDataCreator
{
    /// <summary>
    /// Create the save meta data.
    /// </summary>
    /// <param name="saveSystemManager">The save system manager.</param>
    /// <param name="saveDataInfo">The save data info linked to that meta data.</param>
    /// <returns>The save meta data.</returns>
    public override SaveMetaData CreateMetaData(SaveSystemManager saveSystemManager, SaveDataInfo saveDataInfo)
    {
        return new BasicSaveMetaData(saveSystemManager, saveDataInfo);
    }
    /// <summary>
    /// Create an empty save meta data.
    /// </summary>
    /// <returns></returns>
    public override SaveMetaData CreateEmpty()
    {
        return new BasicSaveMetaData();
    }
}
/// <summary>
/// The save meta data which can be serialized.
/// </summary>
[Serializable]
public class BasicSaveMetaData : SaveMetaData
{
    [Tooltip("The date and time in ticks.")]
    [SerializeField] protected long m_DateTimeTicks;
    public long DateTimeTicks => m_DateTimeTicks;
    /// <summary>
    /// Default constructor.
    /// </summary>
    public BasicSaveMetaData() : base()
    {
        m_DateTimeTicks = new DateTime().Ticks;
    }
    /// <summary>
    /// Overloaded constructor.
    /// </summary>
    /// <param name="saveSystemManager">The save system manager.</param>
    /// <param name="saveDataInfo">The save data info linked to that meta data.</param>
    public BasicSaveMetaData(SaveSystemManager saveSystemManager, SaveDataInfo saveDataInfo) : base(saveSystemManager, saveDataInfo)
    {
        m_DateTimeTicks = DateTime.Now.Ticks;
    }
    /// <summary>
    /// Set the date of the save data.
    /// </summary>
    /// <param name="newDateTime">The new date.</param>
    public void SetDateTime(DateTime newDateTime)
    {
        m_DateTimeTicks = newDateTime.Ticks;
    }
}

Save Data Info

The save data info is a struct with:

  • The save slot index
  • The save Meta Data
  • The Save Data

Save Menu

The save menu uses the Save System Manager to display the saves available to load/save. Check the UI documentation to learn more about the Save Menu.

Save System Manager API

Save Load and delete save files
// Save in file 0
SaveSystemManager.Save(0);

// Load in file 0
SaveSystemManager.Load(0);

// Delete save at file 0
SaveSystemManager.DeleteSave(0);
Get the save data
// Get the current state of the cached save data
var saveData = SaveSystemManager.GetCurrentSaveDataInfo();

// Get the cached serialized data of a specific Saver component
var saveData = SaveSystemManager.TryGetSaveData("The Saver Key", out var serializedData);

Nest our Save System in a Third Party Save System

In a lot of cases you may use a custom or third party save system for your game as the main save system. But you may still wish to use our save system to save and load items without needing to create additional save files.

In the example below we assume you have an interface that has a function for Saving called “RecordData” and a function for loading call “ApplyData”. The actual functions, parameters and return types will depend on your save system implementation.

Here is an example on how this can be achived:

public class NestedInventorySaver : ISave
{
    public int saveSlot = 0;
    public object RecordData()
    {
        // Save the game without saving to disk by specifying the "false" parameter
        SaveSystemManager.Save(saveSlot, false);
        var saveDataInfo = SaveSystemManager.GetCurrentSaveDataInfo();
        
        var saveData = saveDataInfo.Data;
        
        // Serialize the save Data with a custom serializer perhaps?
        return saveData;
    }
    public void ApplyData(object data)
    {
        if (data == null) return;
        // Deserialize the save data with a custom serializer perhaps?
        var saveData = data as SaveData;
        
        // Load the save data in a specific slot, by specifying the save data we don't need to read from disk.
        SaveSystemManager.Load(saveSlot, saveData);
    }
    
}

Custom Saver Component

To create a custom Saver component inherit the SaverBase class and override the Serialize and Deserialize methods.

/// <summary>
/// An example of a Saver component.
/// </summary>
public class ExampleSaver : SaverBase
{
    /// <summary>
    /// The examples save data.
    /// </summary>
    [System.Serializable]
    public struct ExampleSaveData
    {
        // Add you serializable data here.
        // To save items or currency use IDAmountSaveData.
        // public IDAmountSaveData[] ExampleAmounts;
    }
    /// <summary>
    /// Serialize the save data.
    /// </summary>
    /// <returns>The serialized data.</returns>
    public override Serialization SerializeSaveData()
    {
        // Get the item you want to save
        // var saveAmounts = GetComponent<Example>().Amounts;
        var saveData = new ExampleSaveData() {
            //Set the save Data Value
            //ExampleAmounts = saveAmounts
        };
        // Serialize the save data using our custom Serialization.
        return Serialization.Serialize(saveData);
    }
    /// <summary>
    /// Deserialize and load the save data.
    /// </summary>
    /// <param name="serializedSaveData">The serialized save data.</param>
    public override void DeserializeAndLoadSaveData(Serialization serializedSaveData)
    {
        // Get the save data, Make sure the type matches the type saved
        var savedData = serializedSaveData.DeserializeFields(MemberVisibility.All) as ExampleSaveData?;
        if (savedData.HasValue == false) {
            return;
        }
        var exampleSaveData = savedData.Value;
        //Apply the loaded save data on the object
        // GetComponent<Example>().Amounts = exampleSaveData.ExampleAmounts;
    }
}