Serialization – Saving Objects as Files

Creating the DataSaveSystem

Now that we have a class that can be serialized we can write a class that handles saving. This DataSaveSystem class is a static class (for global accessibility) and will handle serialization and deserialization. This is going to be a big class, so if you want to take a a look at the whole class before I go into a step by step explanation you can do that by
[expand title=”clicking here.”]

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;

public static class DataSaveSystem
{
    public static bool SaveData<T>(T dataCotainer, string fileName) where T : DataCotainer
    {

        using (var stream = new FileStream(GetDataPath(fileName), FileMode.Create))
        {
            try
            {
                var formatter = new BinaryFormatter();
                formatter.Serialize(stream, dataCotainer);
            }
            catch (Exception exception)
            {
                Debug.LogError(exception.Message);
                return false;
            }
        }

        return true;
    }

    public static T LoadData<T>(string fileName) where T : DataCotainer
    {
        if (!DoesDataContainerExist(fileName))
        {
            return null;
        }

        using (var stream = new FileStream(GetDataPath(fileName), FileMode.Open))
        {
            try
            {
                var formatter = new BinaryFormatter();
                return formatter.Deserialize(stream) as T;
            }
            catch (Exception exception)
            {
                Debug.LogError(exception.Message);
                return null;
            }
        }
    }

    public static bool DeleteDataContainer(string fileName)
    {
        try
        {
            File.Delete(GetDataPath(fileName));
        }
        catch (Exception exception)
        {
            Debug.LogError(exception.Message);
            return false;
        }

        return true;
    }

    public static bool DoesDataContainerExist(string fileName)
    {
        return File.Exists(GetDataPath(fileName));
    }

    private static string GetDataPath(string fileName)
    {
        return Path.Combine(Application.persistentDataPath, fileName + ".dat");
    }
}

[/expand]
The first thing to consider is where to save our data. Luckily Unity’s Application.persistentDataPath provides us with a safe place to store our files. Implementing a method to get a full file location would look like this:

using System.IO;
using UnityEngine;

public static class DataSaveSystem
{
    private static string GetDataPath(string fileName)
    {
        return Path.Combine(Application.persistentDataPath, fileName + ".dat");
    }
}

The System.IO namespace contains functionality to interact with files. Using it’s Path.Combine method we can create a complete file location from Unity’s persistentDatapPath, a fileName and the file extension “.dat”.
Now that we know where to save a file we can create a method to actually save a DataContainer to a file.

public static bool SaveData<T>(T dataCotainer, string fileName) where T : DataCotainer
    {

        using (var stream = new FileStream(GetDataPath(fileName), FileMode.Create))
        {
            try
            {
                var formatter = new BinaryFormatter();
                formatter.Serialize(stream, dataCotainer);
            }
            catch (Exception exception)
            {
                Debug.LogError(exception.Message);
                return false;
            }
        }

        return true;
    }

This static SaveData method takes a derived class of DataContainer (e.g. PlayerDataContainer) and the fileName of the file that container should be saved as. It returns wehter or not the save action was successful. Error messages from serializing or writing are catched and logged.
Now that we can save data we also need to load it, but before blindly trying to load a file we should check if it actually exists.

public static bool DoesDataContainerExist(string fileName)
    {
        return File.Exists(GetDataPath(fileName));
    }

This method returns true if a file with a given fileName exists. With this we are going to make sure the file we are trying to load does actually exist.

public static T LoadData<T>(string fileName) where T : DataCotainer
    {
        if (!DoesDataContainerExist(fileName))
        {
            return null;
        }

        using (var stream = new FileStream(GetDataPath(fileName), FileMode.Open))
        {
            try
            {
                var formatter = new BinaryFormatter();
                return formatter.Deserialize(stream) as T;
            }
            catch (Exception exception)
            {
                Debug.LogError(exception.Message);
                return null;
            }
        }
    }

This method takes a fileName and a generic parameter T that inherits from DataContainer and defines what type the saved class is and what will be returned as. Like in the saving method errors from trying to deserialize the file are catched and logged. Lastly wee need a method to delete a file:

public static bool DeleteDataContainer(string fileName)
    {
        try
        {
            File.Delete(GetDataPath(fileName));
        }
        catch (Exception exception)
        {
            Debug.LogError(exception.Message);
            return false;
        }

        return true;
    }

Here we try to delete the file with a given fileName and return wehter or not that was successful.
Now we have all we need for a fully functioning save system. With it we can save, load and delete our own custom classes.
If you want the see the full DataSaveSystem as well as my script to test it
[expand title=”click here.”]

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;

public static class DataSaveSystem
{
    public static bool SaveData<T>(T dataCotainer, string fileName) where T : DataCotainer
    {

        using (var stream = new FileStream(GetDataPath(fileName), FileMode.Create))
        {
            try
            {
                var formatter = new BinaryFormatter();
                formatter.Serialize(stream, dataCotainer);
                Debug.Log(GetDataPath(fileName));
            }
            catch (Exception exception)
            {
                Debug.LogError(exception.Message);
                return false;
            }
        }

        return true;
    }

    public static T LoadData<T>(string fileName) where T : DataCotainer
    {
        if (!DoesDataContainerExist(fileName))
        {
            return null;
        }

        using (var stream = new FileStream(GetDataPath(fileName), FileMode.Open))
        {
            try
            {
                var formatter = new BinaryFormatter();
                return formatter.Deserialize(stream) as T;
            }
            catch (Exception exception)
            {
                Debug.LogError(exception.Message);
                return null;
            }
        }
    }

    public static bool DeleteDataContainer(string fileName)
    {
        try
        {
            File.Delete(GetDataPath(fileName));
        }
        catch (Exception exception)
        {
            Debug.LogError(exception.Message);
            return false;
        }

        return true;
    }

    public static bool DoesDataContainerExist(string fileName)
    {
        return File.Exists(GetDataPath(fileName));
    }

    private static string GetDataPath(string fileName)
    {
        return Path.Combine(Application.persistentDataPath, fileName + ".dat");
    }
}
using UnityEngine;

public class InputToSaveActionParser : MonoBehaviour
{
    private const string DataContainerName = "PlayerDataCotainer";

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.F5))
        {

            SavePlayerDataContainer();
        }

        else if (Input.GetKeyDown(KeyCode.F9))
        {
            LoadPlayerDataContainer();
        }

        else if (Input.GetKeyDown(KeyCode.F12))
        {
            DataSaveSystem.DeleteDataContainer(DataContainerName);
        }
    }

    private void SavePlayerDataContainer()
    {
        var playerDataCotainer = new PlayerDataCotainer
        {
            PlayerName = "Kevin",
            Score = 666666,
            Secret = Random.Range(0, 1000).ToString()
        };
        DataSaveSystem.SaveData(playerDataCotainer, DataContainerName);
    }

    private void LoadPlayerDataContainer()
    {

        var playerDataCotainer = DataSaveSystem.LoadData<PlayerDataCotainer>(DataContainerName);
        if (playerDataCotainer == null) return;

        Debug.Log(playerDataCotainer.PlayerName);
        Debug.Log(playerDataCotainer.Score);
        Debug.Log(playerDataCotainer.Secret);
    }
}

[/expand]