1. Code
  2. Coding Fundamentals
  3. Game Development

How to Save and Load Your Players' Progress in Unity

Scroll to top
Final product imageFinal product imageFinal product image
What You'll Be Creating

In this tutorial, you'll learn how to implement a simple system to create and manage savegames for your Unity games. We will build the framework for a Final Fantasy-like main menu that enables players to create new, unique save files, and to load existing ones. The principles demonstrated will allow you to extend them to whatever needs your game has. 

By the end of the tutorial, you will have learned how to:

  • save and load game data within Unity3D using serialization
  • use static variables to persist data across scene changes

Note: This approach to saving and loading game data works on all platforms except for the Web Player. For information on saving game data in the Web Player, take a look at the official docs on Unity Web Player and browser communication.

Let's Get Serial

The first thing we're going to do is to create some code that allows us to serialize our game data—that is, convert it to a format that can be saved and later restored. For this, let's create a C# script and call it SaveLoad. This script will handle all the saving and loading functionality. 

We will reference this script from other scripts, so let's make it a static class by adding the word static between public and class. Let's also remove the : MonoBehaviour part, because we don't need to attach it to a GameObject. And since it no longer inherits from MonoBehaviour, let's delete the Start and Update functions. 

The resulting code should look like this:

1
using UnityEngine;
2
using System.Collections;
3
4
public static class SaveLoad {
5
6
}

Now, we're going to want to add some new functionality to this script, so immediately under where it says using System.Collections;, add the following:

1
using System.Collections.Generic; 
2
using System.Runtime.Serialization.Formatters.Binary; 
3
using System.IO;

The first line allows us to use dynamic lists in C#, but this is not necessary for serialization. The second line is what enables us to use the operating system's serialization capabilities within the script. In the third line, IO stands for Input/Output, and is what allows us to write to and read from our computer or mobile device. In other words, this line allows us to create unique files and then read from those files later.

We're now ready to serialize some data!

Making Serializable Classes

Now that our script has the ability to serialize, we are going to have to set up some classes to be serialized. If you think about a basic RPG, like Final Fantasy, it offers players the ability to create and load different saved games. So, create a new C# script called Game and give it some variables to hold three objects: a knight, a rogue, and a wizard. Change the code of this script to look like this:

1
using UnityEngine;
2
using System.Collections;
3
4
[System.Serializable]
5
public class Game { 
6
7
    public static Game current;
8
    public Character knight;
9
    public Character rogue;
10
    public Character wizard;
11
12
    public Game () {
13
        knight = new Character();
14
        rogue = new Character();
15
        wizard = new Character();
16
    }
17
        
18
}

The [System.Serializable] line tells Unity that this script can be serialized—in other words, that we can save all the variables in this script. Cool! According to the official docs, Unity can serialize the following types:

  • All basic data types (like int, string, float, and bool).
  • Some built-in types (including Vector2, Vector3, Vector4, Quaternion, Matrix4x4, Color, Rect, and LayerMask).
  • All classes inheriting from UnityEngine.Object (including GameObject, Component, MonoBehavior, Texture2D, and AnimationClip).
  • Enums.
  • Arrays and lists of a serializable type.

The first variable, current, is a static reference to a Game instance. When we create or load a game, we're going to set this static variable to that particular game instance so that we can reference the "current game" from anywhere in the project. By using static variables and functions, we don't have to use a gameObject's GetComponent() function. Handy! 

Notice that it's referencing something called a Character? We don't have that yet, so let's create a new script to house this class, and call it Character:

1
using UnityEngine;
2
using System.Collections;
3
4
[System.Serializable] 
5
public class Character {
6
7
    public string name;
8
9
    public Character () {
10
        this.name = "";
11
    }
12
}

You may be wondering why we needed a whole new class if we're just storing a string variable. Indeed, we could just replace Character in the Game script to use string instead. But I want to show you how deep this rabbit hole can go: you can save and load classes that reference other classes, and so on, as long as each class is serializable

Now that our classes are set up to be saved and loaded, let's hop back over to our SaveLoad script and add the ability to save games.

Saving a Game's State

A "Load Game" menu  usually shows a list of saved games, so let's create a List of type Game and call it savedGames. Make it a static List, so that there's only one list of saved games in our project. The code should look like this:

1
using UnityEngine;
2
using System.Collections;
3
using System.Collections.Generic;
4
using System.Runtime.Serialization.Formatters.Binary;
5
using System.IO;
6
7
public static class SaveLoad {
8
9
    public static List<Game> savedGames = new List<Game>();
10
11
}

Next, let's create a new static function to save a game:

1
    public static void Save() {
2
        savedGames.Add(Game.current);
3
        BinaryFormatter bf = new BinaryFormatter();
4
        FileStream file = File.Create (Application.persistentDataPath + "/savedGames.gd");
5
        bf.Serialize(file, SaveLoad.savedGames);
6
        file.Close();
7
    }    

Line 2 adds our current game to our list of saved games. That list is what we're going to serialize. To do so, we first need to create a new BinaryFormatter, which will handle the serialization work. This is what Line 3 does.

In Line 4, we're creating a FileStream, which is essentially a pathway to a new file that we can send data too, like fish swimming downstream in a river. We use File.Create() to create a new file at the location we pass in as its parameter. Conveniently, Unity has a built-in location to store our game files (which updates automatically based on what platform your game is built to) that we can reference using Application.persistentDataPath

Since we're creating a new file, however, we can't just say where the file is, we also have to cap off this pathway with the name of the actual file itself. There are two parts to this file:

  1. the file name
  2. the file type

We'll use savedGames for the file name, and we'll use a custom data type gd (for "game data") for the file type. Our result is a game file called savedGames.gd at the location set by Application.persistentDataPath. (In the future, you could save other types of things to this data type; for example, you could save the users' options settings as options.gd.)

Note: You can make the file type anything you want. For example, the Elder Scrolls series uses .esm as its file type. You could have as easily said savedGames.baconAndGravy.

In Line 5, we're calling the Serialize functionality of the BinaryFormatter to save our savedGames list to our new file. After that, we have the close the file that we created, in Line 6. 

Badda bing, badda boom. Our games are saved.

Loading a Game's State

In the Save function, we serialized our list of saved games at a specific location. Conversely, the code to load our games should look like this:

1
    public static void Load() {
2
        if(File.Exists(Application.persistentDataPath + "/savedGames.gd")) {
3
            BinaryFormatter bf = new BinaryFormatter();
4
            FileStream file = File.Open(Application.persistentDataPath + "/savedGames.gd", FileMode.Open);
5
            SaveLoad.savedGames = (List<Game>)bf.Deserialize(file);
6
            file.Close();
7
        }
8
    }

In Line 2, we check whether a saved game file exists. (If it doesn't, there will be nothing to load, obviously.) In Line 3, we create a BinaryFormatter the same way we did in the Save function. In Line 4, we create a FileStream—but this time, our fish are swimming upstream from the file. Thus, we use File.Open, and point to where our savedGames.gd exists using the same Application.persistentDataPath string. 

Line 5 is a bit dense, so let's unpack it: 

  • The bf.Deserialize(file) call finds the file at the location we specified above and deserializes it. 
  • We can't just spit binary at Unity and expect it to work, however, so we convert (or cast) our deserialized file to the data type we want it to be, which in this case is a List of type Game. 
  • We then set that list as our list of saved games. 

Lastly, in Line 6, we close that file the same way we did in the Save function. 

Note: The data type to which you cast the deserialized data can change depending on what you need it to be. For example, Player.lives = (int)bf.Deserialize(file);.

Conclusion

Our SaveLoad script is now complete, and should look like this:

1
using UnityEngine;
2
using System.Collections;
3
using System.Collections.Generic;
4
using System.Runtime.Serialization.Formatters.Binary;
5
using System.IO;
6
7
public static class SaveLoad {
8
9
    public static List<Game> savedGames = new List<Game>();
10
			
11
	//it's static so we can call it from anywhere

12
	public static void Save() {
13
		SaveLoad.savedGames.Add(Game.current);
14
		BinaryFormatter bf = new BinaryFormatter();
15
		//Application.persistentDataPath is a string, so if you wanted you can put that into debug.log if you want to know where save games are located

16
		FileStream file = File.Create (Application.persistentDataPath + "/savedGames.gd"); //you can call it anything you want

17
		bf.Serialize(file, SaveLoad.savedGames);
18
		file.Close();
19
	}	
20
	
21
	public static void Load() {
22
		if(File.Exists(Application.persistentDataPath + "/savedGames.gd")) {
23
			BinaryFormatter bf = new BinaryFormatter();
24
			FileStream file = File.Open(Application.persistentDataPath + "/savedGames.gd", FileMode.Open);
25
			SaveLoad.savedGames = (List<Game>)bf.Deserialize(file);
26
			file.Close();
27
		}
28
	}
29
}

Those are the basics of saving and loading in Unity. In the attached project file, you'll find some other scripts which show how I handle calling these functions and how I display the data using Unity's GUI.

If you need a head-start with your game development, try the Unity3D templates available on Envato Market.

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.