DEV Community

Cover image for DevBlog - How to Save Data in Unity
JustLeviDev
JustLeviDev

Posted on

DevBlog - How to Save Data in Unity

Introduction

Hello there, nice to see you.
My name is Levi and I'm a Unity-Games-Programmer/Developer.
Today I'd like to talk a little bit about how I save and load data in my projects.

Disclaimer

The games I usually work on are Singleplayer games built in Unity for Windows using the language C# and JsonUtility from System.IO.
I'll mention a small addition to use the code that should encrypt it, but I can't guarantee that it'll work for different engines using different languages.
With all of that said:

Let's begin!


The Core

The main part of the System is a script called SaveSystem (I know, very authentic).
This class includes the 4 most important functions:
Save, Load, Clear and Exists.


Save

The save function takes in a generic T and a filename and uses JsonUtility.ToJson in order to convert the generic into a string in the json format.

public void SaveClass<T>(T infoToSave, string fileName)
{
    string path = $"{Application.persistentDataPath}\\{fileName}.json";

    string json = JsonUtility.ToJson(infoToSave, true);
    File.WriteAllText(path, json);
}
Enter fullscreen mode Exit fullscreen mode

While this approach works for my use cases, it has one minor downside, as this could lead to the player being able to easily change the file and give themselves more items, health or anything else they might want to have in their inventory, whenever they load the game again.
Since it's supposed to be used for singleplayer games that isn't really a problem, but if you want to use this code for any multiplayer game that gets hosted on the players computer, you might want to encrypt your json before calling File.WriteAllText.

This approach uses Application.persistentDataPath as a save location, but you could just change the first line like followed to use whichever location you want to place your save files.

public void SaveClass<T>(T infoToSave, string fileName, string path)
{
    string json = JsonUtility.ToJson(infoToSave, true);
    File.WriteAllText($"{path}\\{fileName}.json", json);
}
Enter fullscreen mode Exit fullscreen mode
Those changes can be added to every function following, but for the sake of keeping it simple I won't mention it again.

Load

The load function just takes in a filename, but has a generic T as the return type.

public T LoadClass<T>(string fileName)
{
    string path = $"{Application.persistentDataPath}\\{fileName}.json";

    string json = File.ReadAllText(path);
    T tempClass = JsonUtility.FromJson<T>(json);

    return tempClass;
    }
Enter fullscreen mode Exit fullscreen mode

And while loading a class or struct is how most data gets saved, I've added another version that allows the loading of Scriptable Objects, since then you can't use FromJson, but you have to use FromJsonOverwrite instead.

public T LoadScriptableObject<T>(string fileName) where T : ScriptableObject, new()
{
    string path = $"{Application.persistentDataPath}\\{fileName}.json";
    //this can be removed, but ensures that it doesn't throw an exception if the file doesn't exist
    if (!File.Exists(path))
        return default(T);

    string json = File.ReadAllText(path);

    T tempClass = ScriptableObject.CreateInstance<T>();
    JsonUtility.FromJsonOverwrite(json, tempClass);

    return tempClass;
}
Enter fullscreen mode Exit fullscreen mode

A problem of those methods is that they will not work if the path doesn't exist, which is why there is another method called Exists.

Exists

Exists just checks whether the specified file actually exists or not and should be used inside an if-statement before calling Load.

public bool Exists(string fileName)
{
string path = $"{Application.persistentDataPath}\{fileName}.json";

return File.Exists(path);
Enter fullscreen mode Exit fullscreen mode

}

Clear

Last but definitely not least we have Clear.
The sole purpose of Clear is to delete a specified file if it exists.

public void ClearFile(string fileName)
{
    string path = $"{Application.persistentDataPath}\\{fileName}.json";

    if (fileName != "" && File.Exists(path))
        File.Delete(path);
    }
Enter fullscreen mode Exit fullscreen mode

Calls

Just using the SaveSystem as is could lead to some human errors occuring, which is why I added a SaveManager as a wrapper around SaveSystem.
This class implements a singleton-pattern and gets placed on an otherwise empty GameObject in Unity to be called from everywhere at all times.
It also has an instance of the SaveSystem (I called it "save") which gets instantiated in Awake.
The main purpose of this script is to store the filenames, which I did using a struct that consists of only an enum and a string.

[Serializable]
public struct SaveFileNames
{
    public ESaveType type;
    public string fileName;
}
Enter fullscreen mode Exit fullscreen mode

At runtime during Awake the array of SaveFileNames will be used to fill a Dictionary that takes the enum ESaveType as input and gives out the filename via string.

Dictionary<ESaveType, string> GetFileName;
Enter fullscreen mode Exit fullscreen mode

Save

This Save-function basically just uses the given enum to get the filename from and uses the generic T again for taking in any type of data to save.

public void Save<T>(ESaveType type, T info)
{
    save.SaveClass<T>(info, GetFileName[type]);
}
Enter fullscreen mode Exit fullscreen mode

Load/Exists/Clear

The only difference between the Save-function and the other 3 functions is that they only require the enum as parameter and have the same return type as their counter-parts in the SaveSystem.

public T Load<T>(ESaveType type)
{
    string fileName = GetFileName[type];
    //saving the string allows for additional changes
    return Load<T>(fileName);
}

public T LoadScriptable<T>(ESaveType type) where T : ScriptableObject, new()
{
    string fileName = GetFileName[type];
    return LoadScriptable<T>(fileName);
}

public bool Exists(ESaveType type)
{
    return save.Exists(GetFileName[type]);
}

public void Clear(ESaveType type)
{
    save.ClearFile(GetFileName[type]);
}
Enter fullscreen mode Exit fullscreen mode

Summary

After adding the SaveSystem and SaveManager into any new Unity project I can call the save-, load-, exists- and clear-functions given by the SaveManager, allowing saves to be made using just a single line.


Outro

Thank you for reading my first DevBlog that was a little bit more educational than the others.
Hopefully you could learn something along the way :)
Regarding my next Blog, I'm not quite sure what it will be about. Maybe about the polishing on my project "The Farm", maybe about a new project I've started working on, or maybe just another insight on a system I've made that allows me to develop faster and easier.
But regardless of what the next topic will be, thank you again for reading this.
If you want to see more of what I'm doing check out my socials and drop me a message.
Thanks for your time and I'll see you in my next Blog, have a good night.

Top comments (0)