Unity

How to Save & Load your Game in Unity?

Building a Persistence System

This blog is a quick guide on how I built a save & load mechanism for my upcoming game, Slime Hunter.

There are several ways of building a persistence system in Unity. The most simplest solution provided by Unity for this is to use PlayerPrefs. PlayerPrefs can be used to store string, float, and integer values in a local registry, without encryption. This information persists between game sessions and hence can be used to save some small preference data. For example, you can store the settings adjusted by player, like volume, graphics settings, etc.

To build a full fledged Save & Load mechanism, you need to build a Persistence System.

Pre-requisites

Basic understanding of Unity and a knack for learning :)

The Idea

I was building this Persistence System for a 3D open-world action RPG game called Slime Hunter. Like any other game, this game also consists of a lot of different Components. Each component contains different type of data. A singleton "PersistenceManager" can not possibly know how to save each and every component. So, the idea was to build the Persistent System such that each Component can save & load information relevant to it by itself.

When it is time to Save:

When it is time to Load:

IPersistent Interface Class

Each component in Unity inherits from MonoBehaviour to get the component properties. I created an interface class called IPersistent and used it to create a class called PersistentObject.

The interface class simply ensures that each PersistentObject class defines what to save.

Code: (ThatDNS) Github: Persistence/IPersistent.cs 

public interface IPersistent
{
    // Serialize the relevant data and return it
    public byte[] GetSaveData();


    // Parse the serialized data and load it into object properties
    public void LoadSaveData(byte[] data);
}

PersistentObject Abstract Class

PersistentObject inherits from MonoBehaviour and IPersistent. Any component can become a PersistentObject.

Features of PersistentObject:

Code: (ThatDNS) Github: Persistence/PersistentObject.cs 

public abstract class PersistentObject : MonoBehaviour, IPersistent

{

    [SerializeField] string uuid;  // Don't forget to set this in the Inspector!

    protected virtual void Awake()

    {

        if (string.IsNullOrEmpty(uuid))

        {

            uuid = Guid.NewGuid().ToString();

        }

        PersistenceManager.Instance.RegisterPersistent(this);

    }


    protected virtual void OnDestroy()

    {

        PersistenceManager.Instance.UnregisterPersistent(this);

    }

    public abstract byte[] GetSaveData();

    public abstract void LoadSaveData(byte[] data);

}

PersistenceManager

PersistenceManager is a simple Singleton class which contains a list of PersistentObject.

It contains 2 important methods:-

SaveGame

This function ends up asking each PersistentObject to save itself. I'm saving the files in binary format to protect these files. You can go ahead and encrypt them but I don't think that is necessary. Using my approach, your game can easily save hundreds of small save files. For someone trying to hack your game, finding the relevant saved file among these can be a nightmare!

public void SaveGame()

{

    foreach (PersistentObject obj in persistentGameObjects)

    {

        byte[] data = obj.GetSaveData();

        string path = Path.Combine(Application.persistentDataPath, SAVE_FOLDER, $"{obj.GetUUID()}.dat");

        File.WriteAllBytes(path, data);

    }

}

LoadGame

This function uses each PersistentObject's UUID to determine its saved file. It loads the file and passes the contents to the PersistentObject. Rest all responsibility of actually loading the data falls upon the relevant PersistentObject.

(Note that this is not "actually" a PersistentObject object. It is a derived class of PersistentObject.)

public void LoadGame()

{

    foreach (PersistentObject obj in persistentGameObjects)

    {

        string path = Path.Combine(Application.persistentDataPath, SAVE_FOLDER, $"{obj.GetUUID()}.dat");

        if (File.Exists(path))

        {

            byte[] data = File.ReadAllBytes(path);

            obj.LoadSaveData(data);

        }

    }

}

Code: (ThatDNS) Github: PersistenceManager.cs 

You can call SaveGame() with a Coroutine to auto-save your game at regular intervals.

This system currently allows only 1 saved game in a PC, but this can be easily extended to allow multiple saved games. Just add another subfolder after the "SAVE_FOLDER" for each new saved game.

And, that is it! Now you have a pretty robust save and load mechanism. So go ahead and build something awesome! 😁