DevBlog - How to Save Data in Unity
Tags: tutorial, unity3d, csharp, coding
Published: 2023-07-06 21:20:09

#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.

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

    string json = JsonUtility.ToJson(infoToSave, true);
    File.WriteAllText(path, json);
}
```

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.

```c#
public void SaveClass<T>(T infoToSave, string fileName, string path)
{
    string json = JsonUtility.ToJson(infoToSave, true);
    File.WriteAllText($"{path}\\{fileName}.json", json);
}
```

######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.

```c#
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;
    }
```

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.

```c#
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;
}
```

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);
}

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

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

    if (fileName != "" && File.Exists(path))
        File.Delete(path);
    }
```

---
#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.

```c#
[Serializable]
public struct SaveFileNames
{
    public ESaveType type;
    public string fileName;
}
```

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.

```c#
Dictionary<ESaveType, string> GetFileName;
```

##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.

```c#
public void Save<T>(ESaveType type, T info)
{
    save.SaveClass<T>(info, GetFileName[type]);
}
```

##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.

```c#
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]);
}
```

---
#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](https://linktr.ee/justlevidev) and drop me a message.
Thanks for your time and I'll see you in my next Blog, have a good night.