This tutorial assumes basic knowledge of Unity Engine. If you don’t have your own project set up, you can freely grab the project example linked here. You will also find a download of the completed project at the end of this article.
If you’re struggling with saving data between two scenes, this is the tutorial for you.
Starting Point
Download the example project:
[GitHub Repository link]
[ZIP Download]
The logic
Unity is a game engine with its own philosophy. Even though there are quite a few alternatives, it’s quite unique in the way it handles the building blocks of any game – game objects, scenes, code, scene graph. And by unique, I mean how easy it is to understand it.
If you’ve tried giving Unity a test run because it’s free to download, you were probably introduced to how its scripting is implemented. „Scripts“, written in either C#, JavaScript (or, since Unity 5, UnityScript) or Boo language, are components that are attached to any game object. From within a script, you’re free to access and manipulate the object the script is attached to, or any linked objects. It’s quite intuitive, easy to use, and fun to build with.
Supposedly, then you tried building a second level; say your first level was the interior of the house, and then your second level is outside of the house. The transition is using the door to load the next scene.
Here’s the core problem we’ll be tackling today. Each level within the Unity engine is called a „Scene“. You can edit the Scene using the graphical editor however you like. You can transition the scene using a single line of code (that is triggered, perhaps, by the player touching the door, or using an object, etc). Each scene has objects, which have „components“.
Generic „Objects“ represent what ever else makes up your level (meshes, triggers, particles, etc)
Each scene is built in its initial state. Transitioning the scene to another one means the new scene will be loaded at its initial state (naturally). But what about the player’s statistics, for example his ammo count, or experience, or inventory?
How do we preserve the data, when we can only write code in „Scripts“ – components of Game Objects – which just get destroyed during scene transitions?
Here’s where we get a bit crafty. There IS a way to preserve game objects through scene transitions, effectively building not a scene-wide object, but a game-wide object, which will keep our data.
Here’s the basic workflow of the game using such an object:
We need to save the data, transition the scene, and then load the data back.
Here’s the basic breakdown of the logic we will be using:
- Regardless of the scene we are in (even if it’s scene 1), FIRST initialize the player with starting data.
- Then, copy over the data from the Global Object.
Global Object initialization logic:
- Initialize with starting data, and preserve our instance through scene transitions.
What this flow does is ensure that scene 1 will always initialize the player with the starting data. Then, if you save data into the Global Object before any scene transition, you have ensured that this data will always be loaded into the next level’s Player object.
This assumes you have placed the same Player object (preferably a Prefabbed object) into every scene. Note that the „Player object’s initialization logic“ is applicable to any object which needs the illusion of being „preserved through scenes“; we’re only using the Player as the most obvious example.
The code
OK, enough of the graphs and abstract thinking for now. Let’s get to coding.
I am assuming that by this point, you do have two scenes, and an implementation for transitioning between them – refer to the starting project at the top of the article. You’re playing the same player avatar in both, and you just need to save the player’s data between them to give the illusion of the same „player object“.
Let’s first create the game-wide Global Object. It’s important we get this one down right, so let’s figure out what we need it to do:
- We need to be able to access it from any other script, from any part of the game. A logical choice for it would be the Singleton design concept. If you don’t know what that is, cool, you’re about to learn something new.
- We need it to only be initialized once, and carry over through the scene transitions.
- We need it to hold any data we may need to carry over. We know variables we need to save, so we’ll just type those in.
First, go to your first scene, and create a new empty Game Object. Rename it to something fitting, like „GameMaster“ or „GlobalObject“.
Next, create a new C# script (preferably within a new folder – remember to keep things organized). Give it a fitting name. My script’s name is „GlobalControl“.
Attach the new empty C# script to the new Game Object, and open the Script in your editor of choice. MonoDevelop, which comes with Unity, is good, but you can also use Visual Studio.
Put this code into the GlobalControl Script:
public class GlobalControl : MonoBehaviour
{
public static GlobalControl Instance;
void Awake ()
{
if (Instance == null)
{
DontDestroyOnLoad(gameObject);
Instance = this;
}
else if (Instance != this)
{
Destroy (gameObject);
}
}
}
The basic premise of the singleton design pattern is that there is one single public static instance of one class. Within the awake method (one to be called when the object is supposed to be loaded), we are making sure of that by saying „If there is another instance, destroy that one and make sure that the instance is this one“.
Notice a very peculiar function call within the Awake
method, “Don’t Destroy On Load”. This is a part of our solution to the problem of cross-scene persistence. This is what will keep the gameObject this script is attached to alive and carry it over to the other scene. The rest of the Singleton concept ensures that if there is another copy of the object with this same script attached (and there will be, you need to put this object into every scene), then the other object will be destroyed and this one (original) will be saved.
If you wish, you can test this now. Put the GameMaster or GlobalObject (or whatever is carrying this script) into every scene you have, and try to transition the scenes at run-time. You will notice there is only one such global object in the scene at any given time.
If we now write data into it, it will be saved!
Now, onto the other part of the problem:
What do we need to save?
For this tutorial’s purpose, suppose your Player has three statistics:
- HP, with starting value of 100,
- Ammo, with starting value of 0,
- XP, with starting value of 0.
These are saved somewhere within your Player object. Which script exactly, doesn’t really matter. We need to have the same variables within our GlobalObject as well, so add them to your code:
public class GlobalControl : MonoBehaviour
{
//[...]
public float HP;
public float Ammo;
public float XP;
//[...]
Now we are ready to save the data. We only need to save the data when we are transitioning the scene and load it when we are starting a scene.
Here’s how to save the data from the script where you keep your player’s variables:
public class PlayerState : MonoBehaviour
{
//[...]
public float HP;
public float Ammo;
public float XP;
//[...]
//Save data to global control
public void SavePlayer()
{
GlobalControl.Instance.HP = HP;
GlobalControl.Instance.Ammo = Ammo;
GlobalControl.Instance.XP = XP;
}
//[...]
It’s wise to build a dedicated function for saving the player data into the instance.
There is now just one more step missing: loading from the GlobalControl. You can easily call that in the Start function of your Player’s State script:
public class PlayerState : MonoBehaviour
{
//[...]
public float HP;
public float Ammo;
public float XP;
//[...]
//At start, load data from GlobalControl.
void Start ()
{
HP = GlobalControl.Instance.HP;
Ammo = GlobalControl.Instance.Ammo;
XP = GlobalControl.Instance.XP;
}
//[...]
With this code, our data flow would look something like this:
This goes for every scene transition, universally. Even transitioning back from Scene 2 to Scene 1 will now keep your player’s statistics!
There are, of course, a few kinks to work out. For example, if you quit to main menu and start a new game (without quitting the game altogether), you need to reset the saved data, otherwise you’ll be starting a new game with player stats from the previous session!
Why not public static class?
At this point, if you’re familiar with C# and .NET programming, you might be wondering why we aren’t simply using something like this:
public static class GlobalObject
Contrary to what you might intuitively think, public static classes do not actually persist game-wide. Since any class (that is, any script) is attached to a game object, it will get destroyed when you load a new scene. Even if another scene has a new public static class in it, the data inside will be reset – that is, the static class will be initialized anew at scene load.
This is why we must use the DontDestroyOnLoad method, and a bit more code to ensure we have only one instance of the class we intend to carry across levels.
Polishing and preparing for the next tutorial
You have probably noticed that in this example it’s not hard to manually type the three needed values to GlobalData and back. But what if we have a larger, more complex game with dozens, if not hundreds of player variables to keep track of?
Below, we’ll polish our code to be not just prettier, but to offer additional functionality we’ll explain in the followup tutorial, along with dealing with Save and Load mechanics as well.
First, let’s create a new script within our project. This will be a bit different type of script, because it will not extend the MonoBehavior class nor will it be attached to any object.
We’ll call it “Serializables” and it will look like this:
using UnityEngine;
using System.Collections;
using System;
using System.Collections.Generic;
public class PlayerStatistics
{
public float HP;
public float Ammo;
public float XP;
}
As you can see, there are no functions, no namespaces, only a single class with no constructor, holding our three known variables. Why are we doing this?
So that, in our Global Object, we can turn individual statistics into a single class to contain them:
public class GlobalControl : MonoBehaviour
{
//[...]
public float HP;
public float Ammo;
public float XP;
//[...]
…like this:
public class GlobalControl : MonoBehaviour
{
//[...]
public PlayerStatistics savedPlayerData = new PlayerStatistics();
//[...]
And then the same in the player’s variables:
public class PlayerState : MonoBehaviour
{
//[...]
public float HP;
public float Ammo;
public float XP;
//[...]
This gives us an additional layer of security. We can’t accidentally write wrong the player variables into saved variables (eg. XP = HP):
public class PlayerState : MonoBehaviour
{
//[...]
public PlayerStatistics localPlayerData = new PlayerStatistics();
//[...]
Now, when we want to save the data, we simply remove this:
//Save data to global control
public void SavePlayer()
{
GlobalControl.Instance.HP = HP;
GlobalControl.Instance.Ammo = Ammo;
GlobalControl.Instance.XP = XP;
}
…and instead, copy the reference to the class that contains our data. All values inside will stay where they are supposed to:
//Save data to global control
public void SavePlayer()
{
GlobalControl.Instance.savedPlayerData = localPlayerData;
}
Same for loading the player data in the player’s start function!
Now we have all our player’s statistics in a class that represents only the player’s data, with nothing extra. During your game’s development, when you need more player variables saved and loaded, simply add them to the class – the saving and retrieving of player data to/from Global Object stays the same.
Conclusion
In the next article, we will go through saving and loading the entire class to the hard drive (not just a Global Object) by using serialization.
If you got stuck at any point or just want to see what the finished project looks like, you can download it here:
[GitHub Repository]
[ZIP Download]
Questions? Comments? Let us know in the area below!
Frequently Asked Questions (FAQs) about Saving Data Between Scenes in Unity
What is the best practice for saving data between scenes in Unity?
The best practice for saving data between scenes in Unity is to use a Singleton pattern. This pattern allows you to create an object that persists across multiple scenes without being destroyed. You can store any data you want to persist in this object. The Singleton pattern is easy to implement and use, and it ensures that only one instance of the object exists in the game at any time. This prevents data duplication and inconsistencies.
How can I use PlayerPrefs to save data between scenes?
PlayerPrefs is a useful class in Unity that allows you to save and load data between game sessions and scenes. You can use it to save simple data types like integers, floats, and strings. To save data, you use the PlayerPrefs.Set functions, and to load data, you use the PlayerPrefs.Get functions. Remember to call PlayerPrefs.Save after setting data to ensure it is saved to disk.
Can I use ScriptableObjects to save data between scenes?
Yes, ScriptableObjects can be used to save data between scenes in Unity. ScriptableObjects are a type of object that you can create in your game and store large amounts of shared data. They are especially useful when you want to store data that doesn’t change during gameplay, like configuration data or game settings.
How can I pass data between scenes without using Singletons?
If you prefer not to use Singletons, you can use the DontDestroyOnLoad function to pass data between scenes. This function makes a game object persistent, meaning it won’t be destroyed when a new scene is loaded. You can attach a script to this object to store and manage the data you want to pass between scenes.
How can I save complex data types between scenes?
For complex data types, you can use serialization to save and load data between scenes. Serialization is the process of converting complex data structures into a format that can be stored and retrieved later. Unity supports both binary and JSON serialization. You can use the System.Serializable attribute to make your custom classes serializable.
Can I use global variables to save data between scenes?
While you can use global variables to save data between scenes, it’s generally not recommended. Global variables can lead to code that is hard to debug and maintain. Instead, consider using other methods like Singletons, PlayerPrefs, or ScriptableObjects, which are more flexible and manageable.
How can I save player progress between scenes?
To save player progress between scenes, you can use a combination of methods. For example, you can use PlayerPrefs to save simple data like the player’s score or level, and a Singleton to store more complex data like the player’s inventory or game state.
Can I use databases to save data between scenes?
Yes, you can use databases to save data between scenes in Unity. Unity supports SQLite databases, which are lightweight and easy to use. You can use a database to store complex data structures and query them efficiently.
How can I save data between scenes in a multiplayer game?
In a multiplayer game, you can use a server to save data between scenes. The server can store the game state and synchronize it between all connected clients. This ensures that all players see the same game state, regardless of which scene they are in.
Can I use the new Unity DOTS system to save data between scenes?
The Unity DOTS (Data-Oriented Technology Stack) system is a new way of writing code that focuses on data and performance. While it’s possible to use DOTS to save data between scenes, it’s not the most straightforward method and might not be necessary for most games. It’s usually easier and more efficient to use other methods like Singletons, PlayerPrefs, or ScriptableObjects.
Self taught programmer, enthusiastic video gamer, and a serious caffeine addict. In free time from Informatics Tech university obligations, working on an independent video game, ever more learning new programming concepts, and enjoying tutoring and teaching people coding and game development.