Search Unity

Complex Save System example

Discussion in 'Scripting' started by RobAnthem, May 25, 2020.

  1. RobAnthem

    RobAnthem

    Joined:
    Dec 3, 2016
    Posts:
    90
    This save system uses the idea of a savable interface and a save management system. The idea is to simplify saving on a large scale, but still allow any amount of data to be saved.

    The interface is simple.

    Code (CSharp):
    1. public interface ISaveable
    2. {
    3.     Dictionary<string, object> OnSave();
    4.     void OnLoad(Dictionary<string, object> data);
    5.     string UniqueID { get; set; }
    6. }
    Just implements a Save, and Load function and requires a Unique ID.

    Now to implement this we would do something like this, as for an example a door object.

    Code (CSharp):
    1. public class DoorObject : MonoBehaviour, ISaveable
    2. {
    3.     public bool isDoorOpen;
    4.     public bool isLocked;
    5.     public string m_UniqueID;
    6.  
    7.     public string UniqueID { get { return m_UniqueID; } set { m_UniqueID = value; } }
    8.  
    9.     public void OnLoad(Dictionary<string, object> data)
    10.     {
    11.         isDoorOpen = (bool)data[nameof(isDoorOpen)];
    12.         isLocked = (bool)data[nameof(isLocked)];
    13.     }
    14.  
    15.     public Dictionary<string, object> OnSave()
    16.     {
    17.         Dictionary<string, object> data = new Dictionary<string, object>();
    18.         data.Add(nameof(isDoorOpen), isDoorOpen);
    19.         data.Add(nameof(isLocked), isLocked);
    20.         return data;
    21.     }
    22. }
    23.  
    You may note that this not actually a door script, but t represents what the data side of a door script might look like.

    Now to implement the save and load system, it's relatively simple yet might seem complicated at first. We simply use the UniqueID to get save and load the specific objects.

    Code (CSharp):
    1. public class SaveSystem : MonoBehaviour
    2. {
    3.     /// <summary>
    4.     /// Base file name for save flle
    5.     /// </summary>
    6.     public string saveFile = "savedData"; // needs an identifier if you want multiple save slots.
    7.     /// <summary>
    8.     /// The objects that wll be saved.
    9.     /// </summary>
    10.     public List<GameObject> saveObjects;
    11.     /// <summary>
    12.     /// Function to locate any objects that can be saved.
    13.     /// </summary>
    14.     public void FindSaveObjects()
    15.     {
    16.         // We use a gameobject list because you can't find Interfaces and the system will lose references to them.
    17.         saveObjects = new List<GameObject>();
    18.         // FInd al l objects, even in a large scene this may only take a second or 2.
    19.         GameObject[] gos = GameObject.FindObjectsOfType<GameObject>();
    20.         // Iterate the objects.
    21.         foreach (GameObject go in gos)
    22.         {
    23.             // Look for savables
    24.             if (go.GetComponent<ISaveable>() != null)
    25.             {
    26.                 // If found, add it to our list
    27.                 saveObjects.Add(go);
    28.             }
    29.         }
    30.     }
    31.     /// <summary>
    32.     /// Save all current saveable scene data.
    33.     /// </summary>
    34.     public void SaveData()
    35.     {
    36.         //Check if initialized
    37.         if (saveObjects == null || saveObjects.Count == 0)
    38.             FindSaveObjects();
    39.         // Create our data object
    40.         Dictionary<string, Dictionary<string, object>> allData = new Dictionary<string, Dictionary<string, object>>();
    41.         // Collect all the data.
    42.         foreach (GameObject go in saveObjects)
    43.         {
    44.             ISaveable isave = go.GetComponent<ISaveable>();
    45.             allData.Add(isave.UniqueID, isave.OnSave());
    46.         }
    47.         //Save the data.
    48.         SaveManager.Instance.SaveData(allData, UnityEngine.SceneManagement.SceneManager.GetActiveScene().name + saveFile);
    49.     }
    50.     /// <summary>
    51.     /// Load the data stucture from the file system.
    52.     /// </summary>
    53.     public void LoadData()
    54.     {
    55.         //Check if we have initialized
    56.         if (saveObjects == null || saveObjects.Count == 0)
    57.             FindSaveObjects();
    58.         //Get our data
    59.         Dictionary<string, Dictionary<string, object>> allData = SaveManager.Instance.LoadData<Dictionary<string, Dictionary<string, object>>>(UnityEngine.SceneManagement.SceneManager.GetActiveScene().name + saveFile);
    60.         if (allData == null)
    61.         {
    62.             Debug.LogWarning("Save File NOT FOUND");
    63.             return;
    64.         }
    65.         //Iterate and load onto our objects
    66.         foreach (GameObject go in saveObjects)
    67.         {
    68.             ISaveable isave = go.GetComponent<ISaveable>();
    69.             isave.OnLoad(allData[isave.UniqueID]);
    70.         }
    71.     }
    72. }
    And incase anyone wants it, here's the actual save file system I wrote. Very simple and powerfufl.


    Code (CSharp):
    1.  
    2. using System.Runtime.Serialization.Formatters.Binary;
    3. using System.IO;
    4. using UnityEngine;
    5.  
    6. public class SaveManager
    7. {
    8.     private const string fileExtension = ".bin";
    9.     public static SaveManager Instance
    10.     {
    11.         get
    12.         {
    13.             if (instance == null)
    14.             {
    15.                 instance = new SaveManager();
    16.             }
    17.             return instance;
    18.         }
    19.     }
    20.     private static SaveManager instance;
    21.     private BinaryFormatter formatter;
    22.     public void SaveData(object obj, string path)
    23.     {
    24.         formatter = new BinaryFormatter();
    25.         FileStream fileStream = new FileStream(Application.persistentDataPath + "/"+ path + fileExtension, FileMode.Create, FileAccess.Write);
    26.         formatter.Serialize(fileStream, obj);
    27.         fileStream.Close();
    28.     }
    29.     public T LoadData<T>(string path)
    30.     {
    31.         object obj = null;
    32.         formatter = new BinaryFormatter();
    33.         if (File.Exists(Application.persistentDataPath + "/" + path + fileExtension))
    34.         {
    35.             FileStream fileStream = new FileStream(Application.persistentDataPath + "/" + path + fileExtension, FileMode.Open, FileAccess.Read);
    36.             obj = formatter.Deserialize(fileStream);
    37.             fileStream.Close();
    38.         }
    39.         return (T)obj;
    40.     }
    41.     public bool FileExists(string path)
    42.     {
    43.         return File.Exists(Application.persistentDataPath + "/" + path + fileExtension);
    44.     }
    45.     public bool DeleteData(string path)
    46.     {
    47.         if (File.Exists(Application.persistentDataPath + "/" + path + fileExtension))
    48.         {
    49.             File.Delete(Application.persistentDataPath + "/" + path + fileExtension);
    50.  
    51.             return true;
    52.         }
    53.         return false;
    54.     }
    55. }
    56.  
     
    Last edited: Nov 13, 2021
    Colossal_Games likes this.