Press "Enter" to skip to content

Saving Data in Unity 3D (Serialization for Beginners)

With games we almost always need to save some data: scores, inventory, progress, preferences, etc. There are many ways to do this in Unity. In this article I’ll take you through a few different ways to save data and hopefully encourage some good habits when structuring your applications for saved data.

There are two main levels of persistency for data:

  • Session Data (data doesn’t necessarily need to be remembered the next time you load up the application): This is usually done via static variables and is used for only values that matter for the current session. Most commonly this is for health, score, lives, active power-ups, etc.
  • Application Data (data that must be remembered if the application is closed): This is done by saving data to a file or possibly a database on a server.

What often becomes confusing with Unity is that there are instances of classes that are disposed when a new scene is loaded. So if you have a GameObject with a PlayerData script attached to it, then when you load a new scene it with another GameObject with a PlayerData script, it is not the same instance of the first script. All of its values are set back to whatever was assigned in the inspector or script by default. So we need to make use of data persistence techniques.

When I design Unity applications I try to stick to the following rules:

  • Is it an application preference (music volume, mute, skip tutorial messages)? If yes, then save using Unity’s PlayerPrefs class.
  • Is it a value that must be available when we reload the application (player progress, inventory items, IAP)? If yes, then save to a file using some serialization method (binary, JSON, XML, etc).
  • Otherwise it is likely only a variable that needs to be known throughout this session and can simply be stored in a static variable (Session Data).

Let’s take a look at these various methods and see how to implement them. I won’t be explaining the use of static variables to store session data because it is literally just having some class with public static variables that you set from other classes. The main disadvantage of this is that the use of these variables can be spread throughout your code, so try to use these sparingly.

Part I – PlayerPrefs

As inferred by the name of the class, these are the Player Preferences (not the person playing the game, but the Unity Player Application). As I mentioned earlier this should really be for storing things like music volume, should the app be muted, should we skip tutorial messages, quality settings (though if you use the application launcher then this is already managed by the Unity player). Let’s examine a simple use-case of this: muting all sounds and setting the master volume.

Scene Setup

To start, let’s setup our scene. The scene consists of a UI canvas with a panel, and that panel contains a slider for our volume setting and a toggle for our mute setting. I’ve also put in a button that reloads the scene and an AudioSource so that we can test the persistence of the settings. The AudioSource just plays a clip on awake and is set to looping. We can hear it restart when we reload the scene, but the volume or mute setting is automatically reapplied. Here’s a screenshot of all the important parts of the scene:

playerpresf_example_scene
(click for full size)

You may notice on the Volume Slider and the Mute Toggle that there are script components. We’ll get to those momentarily. First let’s talk about the class that handles the saving, loading, and applying of PlayerPrefs, the PlayerPrefsHandler .

PlayerPrefsHandler.cs

This class is responsible for saving, loading, and applying PlayerPrefs. The PlayerPrefs class is part of the Unity API. It has a bunch of handy static methods that can be used to save and restore data (integers, floats, and strings). Each preference is stored with a key, the key is a string that is used to look up a value. For example, for the mute setting I’ll be using a key, “mute” and for volume I’ll be using “volume”. The keys and their values actually get stored in either a file or in the Windows Registry (you can view them with regedit.exe, the Windows Registry Editor). So, they’re pretty accessible to your users and easy to modify. Therefore, they aren’t a good idea to use for sensitive data like scores, player stats, xp, etc. But they’re perfect for storing things like mute and volume settings. Unity also stores some data here like window dimensions and graphics quality level. I typically have a class like the PlayerPrefsHandler that deals with saving, loading, and applying all preferences. Even though this class is meant to handle all of this, oftentimes we’ll find ourselves accessing the preferences via other classes. You should try to avoid this, but in case you have to do it then it’s very important to keep all of the prefs keys in a single script and make them constant. This way you can use Intellisense to fill in the key’s variable name for you instead of trying (and failing) to remember the actual string. It also makes changing the key string easy because you only have to do it in one place. The code for this class is below and it is heavily commented so that you know what’s happening every step of the way:

You may notice that this class does not inherit from MonoBehaviour. There’s really no reason to, and although this class is responsible for handling the player prefs it is actually loaded and ran from a different class which is a MonoBehaviour class. This class is called DataService.

DataService.cs

The DataService class is what is called a Singleton. A Singleton is basically a single instance of a class. Only one instance should ever exist. The Instance is accessed statically (i.e. DataService.Instance ) instead of by creating an instance and passing that reference around. They tend to make lie a lot easier and are used often in games. This Singleton is also derived from MonoBehaviour so it can be attached to a game object which makes it available to other game objects in the Hierarchy so we can assign onClick events and the like. Finally, the game object it is attached to is marked DontDestroyOnLoad , so that Unity will keep it when other scenes are loaded. Beware though! Singletons have gotten a bad name because they are often abused and referenced too frequently throughout code. When this happens the code isn’t extensible and modifications can be more time consuming later on. Singletons are also not easy to extend. You’ll want to use the conventional “Instance” member to access the subclass, but you that won’t give you access to any members of the subclass, just the parent class (not very helpful). So use them sparingly.

The code below is heavily commented as usual, but here’s a quick breakdown of what’s happening: Anytime the Instance is accessed the class will ensure that the Instance exists (it’s self-loading or lazy loading). When the MonoBehaviour Awake method is called we ensure that it is the one and only singleton in our scene. This allows us some leniency when we forget that we attached the singleton to an object in a scene (sometimes done during testing). If another instance of this class exists then it is destroyed. The Instance, though, is set as DontDestroyOnLoad, this makes it so that the game object it is attached to doesn’t get destroyed when a new scene is loaded and ensures we’re working with that initial and only instance.

In our Awake method is where we also create an instance of the PlayerPrefsHandler class and restore/apply the preferences that were saved. We also do this whenever another scene is loaded so that the preferences are applied to the new objects in the new scene (like muting the AudioListener).

Note that this class is wrapped in a namespace (EX1). This is because I’m using the same class name in the second part of this tutorial and I don’t want them to conflict. Our next step will be to set up some UI elements to control our settings and then test it out.

UI Elements

Most often, the user will be making changes to settings like mute and volume via UI elements. In this tutorial we’re using a UI Toggle to control muting and a UI Slider to control volume. I’ve created a script for each of these controls that gets attached directly to the game object that has the correct UI component (i.e. one for toggle, one for slider). There’s no other setup needed in the inspector as these scripts automatically handle everything for us. I’ve commented the MuteToggleHandler class liberally, but not the VolumeSliderHandler since they only differ in one minor aspect. Both have a Start method that gets the appropriate component from the game object we’re attached to, then is uses the DataService.Instance  to get access to an instance of the PlayerPrefs class and set the appropriate setting, and finally we set up a callback, or listener, to the UI control’s onValueChanged delegate. In the onValueChanged.AddListener  method I’m inputting a parameter that is actually a method. It’s a special type of method called a lambda. This is used often in JavaScript and they come in really handy. Check out this link for more info. Simply attach the MuteToggleHandler class to the UI Toggle for muting and attach the VolumeSliderHandler to the UI Slider. The scripts do the rest!

 

 

As you can see from the VolumeSliderHandler class, these two scripts are quite short and simple. As the values of the controls are changed the player prefs are saved. Sweet!

You can test this out now if you like. I’d suggest adding an AudioSource in your scene so you can hear the effect of the changes and reload the scene to test out that the saving of the settings is persistent between runs of the application as well as levels. In my project I set up a button that reloads the current level, that part is optional, but it’s easy to do and is good practice.

ClearPlayerPrefs.cs

There’s one last part to all of this, the ClearPlayerPrefs class. I use this script in every project I have. It’s super simple, but really handy when testing things out. It just clears the PlayerPrefs file/registry entries. Perfect for when you mess things up 🙂 . This script is an Editor script and must go into a folder named “Editor”. This tells Unity that it shouldn’t be included in the build and that it is only modifying the Unity Editor. It actually adds a menu item to the Edit menu. Super handy.

That’s really it for handling player preferences. There are a few classes involved, but with this sort of structure you should rarely go wrong. The only issue here is that PlayerPrefs is not really a good place to store data that you want to prevent players from modifying, like save files.

Part II – Data Serialization

We often save our data to files for a variety of factors. PlayerPrefs is a widely know location and easy to tinker with. In the case of Windows machines, you have limits on how much data can be stored in a registry entry. Also, during development it is often easier for the devs to examine a file with an easy-to-read format (like JSON or XML) then we turn on encryption before we release the game. So PlayerPrefs just doesn’t tend to cut it.

In the example here I will be using JSON (pronounced JAY-son) serialization to store save data. I won’t be diving into any encryption methods as they’re beyond the scope of this tutorial (and there are tons of tutorials out there on the subject). JSON is a highly popular format. It’s relatively easy for a human to read, it is frequently used for data transfer over the internet, and Unity’s API now contains a utility to (de)serialize JSON strings.

Serialization takes the public variables in a class and writes them to a format like JSON. For example, if you had a class like this:

It’s JSON representation would be the variable name followed by it’s value like so:

The awesome thing about this is that it can be read right back into the class’s variables. Very cool (I wish I had understood this when making my first game!). Just about every programming language has a library that will serialize and deserialize JSON. So you could send this data up to your server and read it with PHP, or send it to a Java app and read it there with relative ease. It is used very commonly in REST APIs which are everywhere (Twitter, Facebook, Google APIs, etc)! So learn how to deal with JSON objects. You’ll find them everywhere.

SaveData.cs

save-iconWe begin with our class that will store the player’s progress and stats. This is the class that we will serialize and write to file. It will store the number of coins, health, lives, power ups, and the last level (scene) the player was in. It is responsible for being able to save an instance of itself to a file via the WriteToFile()  method. It has a static method that can create a new instance of the SaveData class from a file (or a default instance if the file doesn’t exist). It also has a method to check if an instance has only the default values (i.e. nothing has been written to it yet). And finally, a ToString()  method so that we can get a human-friendly string representation of the instance when we need it.

For saving and loading the file I am using the simplest methods available: File.WriteAllText() and File.ReadAllText() . These are part of the System.IO  namespace and just blankly read the entire contents of a file to a string or write a single string to a file. To (de)serialize the class to a JSON object I’m making use of Unity’s built-in JsonUtility class.

The code is quite short and the majority of the code is dealing with default values for the class. I’ve commented it up for you to get a better understanding.

Next up we’ll integrate this into our DataService class.

DataService.cs (EX2)

In the second part of our DataService class we’ll want to make it responsible for handling the loading and saving of our player data files. Also, it’s a bit boring to have a single save data file. Most games allow us to have multiple save file slots, so we’ll set that up too.

The new code (added on to the original DataService class) starts in the OnLevelWasLoaded  method where the comments start. Take a look a the code for a moment then read on for some further explanation.

The first additions we have are in the OnLevelWasLoaded  method. This method is called every time a scene is loaded. It ensures that we have a SaveData object loaded into memory and it then ensures it is written to file. This way when a new scene is loaded the SaveData is automatically saved to a file. Oftentimes we may also want to WriteSaveData  when the player reaches a checkpoint, level end, or some other meaningful time in our games.

The LoadSaveData  method has an optional parameter (profile number). If we don’t use this parameter then the method will look for the first existing save data file and load it. If we do use the parameter then we will load the data from file if the file exists. If the file doesn’t exist we simply create a new SaveData  object. In all cases we set the currentlyLoadedProfileNumber  so that we can reference it later when saving the SaveData  object to file.

Next up are some constants (and a getter) that will help us construct the full path and file name of the save data file. That full path is constructed via the GetSaveDataFilePath  method which expects a parameter indicating the profile number to load. This method simply ensures the directory exists and returns that constructed file path and file name.

Finally, we have the WriteSaveData  method. This first attempts to ensure that we have a valid profileNumber  and will write the file to disk.

Since DataService  is a singleton we can call any of these methods without creating a new instance of the class or using something like GameObject.FindObjectOfType . All we have to do is type something like this: DataService.Instance.WriteSaveData()  and we’re good to go.

When you save the data it will go into a directory called “saves” that’s in your Asset folder (you might need to refresh the project window for it to show up). You should examine the contents of those files as changes happen. Since we’re saving them with a txt extension, Unity will show the contents in the inspector window (you may need to refresh the Project window). The contents should look like this:

Next up we’re going to add a class to handle loading of the profiles from buttons.

SaveSlotButtonHandler.cs

We’ll use LoadSaveData on some buttons to load a profile and start a level with this next bit of code. First, simply add 3 buttons to your scene. Then add this class to your project:

The code is commented heavily and there’s really nothing to explain outside of the comments other than what was explained above. The next thing we’ll want to do is set up some way to test our SaveData.

TestSaveData.cs

imagesThis class’s sole purpose is to just test out our save data file. It will allow us to view the currently loaded SaveData object instance and press some keyboard keys to give the player coins, give/take health, give/take lives, give/take power ups, and load the next level. You should also examine the contents of the savedata{n}.txt files to see that when they change on disk (hint: When is WriteToFile running during the application?).

After you’ve added all of this code to your project, test it out by following the on-screen instructions provided by TestSaveData.cs. Examine the changes to the save files, get a feel for when and how the data is loaded. And most of all, enjoy the magic of (de)serialization!

Next Steps

xml-jsonNow that you’ve had an introduction on saving data to file with JSON, take a look at some other serialization methods like .NET’s BinaryWriter or XmlSerializer. Neither is as neat and easy to use as a Unity’s Json Utility, but they have their merits. BinaryWriter will be harder for end-users to tinker with, it is the most compact, and the easiest to encrypt, but it is the most difficult to transfer to other applications. XML is probably the easiest for a human to read, easy to transfer between applications, but it is the least compact and most complex to write code for. JSON is a good middle-of the road serialization method. It’s not too hard for a human to read, it’s fairly compact, it’s not too difficult to encrypt, and it is easily read by other applications. I’d also suggest exploring some JSON readers such as JSON Reader for Notepad++. Spend some time playing around with each of these methods to get a feel for them.

You can get the full project with source from this repository on Github:

https://github.com/Naphier/Unity-Data_Management-Examples/releases/tag/v1.0

If you’re interested in diving deeper and need some one-on-one help, give me a shout! I’m a Unity Certified Developer and I give private lessons in a variety of programming topics.

As always, thanks for reading and don’t forget to subscribe to my email list to hear about new blog posts!

5 Comments

  1. BenWang54852
    BenWang54852 2017/03/08

    Hello Sean:
    Thanks for sharing your article, it explains this topic very well and clear, help me a lot. i’m following it now. I got a little problem when i got to the DataService.cs part, within the Awake() function ,
    private void Awake()
    {
    ////this line is needed
    SceneManager.sceneLoaded += OnLevelWasLoaded;
    }
    }

    void OnLevelWasLoaded()
    {
    prefs.RestorePreference();
    }

    if i followed you here exactly the same, i will got error, it said “No overload for OnLevelWasLoaded matches delegate UnityAction “, so i have to add two parameters to OnLevelWasLoaded function as signature to make it work. it became:

    void OnLevelWasLoaded(Scene scene, LoadSceneMode mode)
    {
    prefs.RestorePreference();
    }

    I’m not sure this happened it is because my unity is ver5.5 and i want to know if those two extra parameters will cause problem in the future? thanks for your work again.

    • napland_wp
      napland_wp 2017/03/08

      Excellent catch, thank you! The article will be updated. I think I was trying to make it “universal” and forgot about the required parameters on that callback. Good catch! And no, those parameters should be OK in future versions of Unity (unless they decided to change) and they do not need to be used.

      • BenWang54852
        BenWang54852 2017/03/08

        Thank you for your answer, have a nice day!

  2. BenWang54852
    BenWang54852 2017/03/12

    Hello Sean:
    I just got to the final of your tutorial, it is a excellent tutorial, i learnt a lot useful stuff from it. i followed your guild and everything work fine, except one part: the powerups part in save file. after i wrote the data to the file , i found that the data of save file is :
    {
    “conis”: 0,
    “health”: 100,
    “lives”: 3,
    “powerUps”: [], // the powerups part is missing!
    “lastLevel”: “Level1”
    }

    then i run the test file, the outcome is :
    {
    “conis”: 20,
    “health”: 115,
    “lives”: 11,
    “powerUps”: [], //powerup part still didn’t work.
    “lastLevel”: “Level1”
    }

    that means everything works just fine except the powerUps part. then i examined the SaveData.cs, the part:
    public override string ToString()
    {
    string[] powerUpsStrings = new string[powerUps.Count];
    for (int i = 0; i < powerUps.Count; i++)
    {
    powerUpsStrings[i] = powerUps[i].ToString();
    }

    return string.Format(
    "coins: {0}\nhealth: {1}\nlives: {2}\npowerUps: {3}\nlastLevel: {4}",
    conis,
    health,
    lives,
    "[" + string.Join(",", powerUpsStrings) + "]",
    lastLevel
    );
    }
    everything looks fine for me. i even copied the whole script from your article, but the problem is still there. that is a weird problem, at my point of view. do you have any idea about this problem? thank you for your time.

  3. Sean Mann
    Sean Mann 2017/03/12

    Hi Ben,
    It looks like either your powerUps list is not getting serialized or there is nothing in it when it gets serialized. Did you write out the TestSaveData class? Did you actually add powerUps via the GUI input (F or D key)? Do the powerUps show in the OnGUI display? Can you manually add powerups to the JSON file and are they read in correctly? As you can see none of this really has to do with ToString which just provides a nicely formatted way to debug the class. If your ToString method is showing an empty array for powerups then there’s nothing in the array (i.e. there’s nothing been added). If it’s still not storing after all of those checks then it may be an issue with Unity’s JSON Utllity which I was just using yesterday and tends to not do nested types well, but it should be doing this list of powerups just fine. If it does end up being JSON Utility then check out part 2 of this article: Twitter REST API, it uses NewtonSoft’s JSON utility which is a lot better.

Leave a Reply

Your email address will not be published. Required fields are marked *