How to handle data between scenes in Unity

Cover Blog

Thousands of seekers already tried to solve this puzzle in Unity: how to handle data between scenes.

I haven’t seen such a difficult quest since the Holy Grail !

For instance everybody has faced at least once the issue of keeping the health and the experience of the player between two different scenes.

Anyway, don’t panic, we have solved difficult cases and we are certainly not afraid of this one.

In the very beginning we’ll cover some theoretical parts just to be sure to know what we are talking about. 

I think as usual in Unity there are different ways to achieve a result, so we’ll check the easiest way to do it and if it could be a valid one.

In the end we’ll build our own way to handle data between scenes step by step defeating the weaknesses.

Let the journey begin.

Data Persistence

Data Persistence

We need to talk about Data persistence because to handle data between scenes we need in some way to let them persist.

But just be careful to not confuse it with saving and loading data like what we did in a “memory card” a long time ago.

I mean it’s not like saving the progress of your game to load them in another play.

Here we just want  to let our data persist during a single game.

Why do we need to separate these two types of persistence?

Well, to avoid complexity and avoiding slowdowns. We can think of handling data between scenes as a subset of the data persistence for saving and loading your game progress.

Talking about persistence though , the scriptable objects just came to my mind, why don’t you have a look at our article about “Write Better Code With The Unity ScriptableObject“.

PlayerPrefs an easy way but not the best

Player Prefs

Obviously PlayerPrefs immediately jumps to our mind, it’s really easy to use, everybody likes it, it has a good name, a good reputation!

Let’s see it in action:

//Set data to PlayerPrefs
PlayerPrefs.SetFloat("health", 100);
PlayerPrefs.SetFloat("experience", 1000);

//Get data from PlayerPrefs
float health = PlayerPrefs.GetFloat("health");
float experience = PlayerPrefs.GetFloat("experience");

As you can see it’s really simple to let the data persist!

But there should be a trick, it’s too easy like this. Let’s list some pros and cons.

Pro

  • Easy to use.
  • Unity does all the stuff for us.

Cons

  • We can only save 3 kinds of data: string, int, bool.
  • It saves on file all this stuff so all the data is exposed easily to be manipulated by bad people.
  • It should be used to store and access player preferences between game sessions, not important data.

So it’s better to leave the PlayerPrefs to its dirty job and think about an alternative way to do it.

Handle Data Step 1: GameControl – a special GameObject

Special Game Object

First of all I want to talk about what I call “special game objects” like a GameControl for instance. I called them like this because they have a special behaviour and they are hidden to the player. They could be really powerful if used in the right way.

For instance a GameControl could handle data between scenes. Let’s assume we need to carry the health and the experience of our player, here is the snippet:

public class GameControl : MonoBehaviour
{
    //Data to persist
    public float health;
    public float experience;
}

Pretty easy right? Just an object with 2 variables.

This will not solve our problem, but think about a first step, now we can fragment data in different “special game objects” like this if needed and we also solved the problem we had with PlayerPrefs of only 3 data types, here we can store the types we want like array, dictionary etc…

Handle Data Step 2: DontDestroyOnLoad method

Do Not Destroy On Load

Now it’s time to give extra powers to our GameControl. Surfing in Unity documentation during my free time (what a wonderful life) I found this method: DontDestroyOnLoad.

I immediately fell in love with it, basically it doesn’t destroy the target Object when loading a new Scene.

Exactly what we need! Let’s add it to our GameControl:

public class GameControl : MonoBehaviour
{
    //Data to persist
    public float health;
    public float experience;

    void Awake()
    {
        //Let the gameobject persist over the scenes
        DontDestroyOnLoad(gameObject);
    }

}

Now we can add to the first scene and if from that scene we navigate to a second scene the object will persist. In this example we want to handle data between 2 scenes:

So right now we have an object that finally persists from different scenes but we still have 2 different problems:

  • Data access
  • Object Reference management

We need to access and modify the values wherever we are. So in the first scene we could have the reference to the GameControl, but what in the second screen or in the third?

If we have 50 scenes which are our levels and we would like to test the 48th level, we would need to start from the first to let our GameControl persist.

So if we can’t solve these problems we can’t be satisfied. Let’s go!

Handle Data Step 3: Static Reference

Singleton

Let’s solve the first problem: Data Access. We need a way to access this object without sharing its reference.

If only we had a way to do that… Oh yeah! We can use the power of Singleton pattern, let me clarify this is a hybrid Singleton but the basics are the same.

We can create a static reference of the GameControl and access it through that.

But let’s see it in action:

public class GameControl : MonoBehaviour
{
    //Static reference
    public static GameControl control;

    //Data to persist
    public float health;
    public float experience;

    void Awake()
    {
        //Let the gameobject persist over the scenes
        DontDestroyOnLoad(gameObject);
        //Check if the control instance is null
        if (control == null)
        {
            //This instance becomes the single instance available
            control = this;
        }
    }
}

Now if we want to access to the GameControl we can simply do this:

//Read data directly
Debug.Log(GameControl.control.health);
Debug.Log(GameControl.control.experience);

//Read data through a local variable
float currentHealth = GameControl.control.health;
float currentExperience = GameControl.control.health;

//Edit data 
GameControl.control.health += 10;
GameControl.control.experience += 100;

Great! Now we can access our GameControl from everywhere with simple snippets! But we have still one last problem. Let’s face it!

Handle Data Step 4: Prefab is the way

Prefabs

Prefabs can really help us, we can once finished store our GameControl in a Prefab. In this way we can add in every scene we like.

So what are we waiting for? Let’s create a prefab of our GameControl!

Create a prefab

But we need to make just one important change in our GameControl script. We want to handle data between scenes, so we want to let it persist and not have one GameControl in each scene.

We can do one important check in the Awake method of our object, every time it’s instantiated it will check if another instance already exists and in case it will destroy itself. Remember the rule of Singleton: “one and only one will live forever and ever!” or maybe something like this.

public class GameControl : MonoBehaviour
{
    //Static reference
    public static GameControl control;

    //Data to persist
    public float health;
    public float experience;

    void Awake()
    {
        //Let the gameobject persist over the scenes
        DontDestroyOnLoad(gameObject);
        //Check if the control instance is null
        if (control == null)
        {
            //This instance becomes the single instance available
            control = this;
        }
        //Otherwise check if the control instance is not this one
        else if (control != this)
        {
            //In case there is a different instance destroy this one.
            Destroy(gameObject);
        }
    }
}

Now we should be able to load the scene we prefer and only the first GameControl instantiated will remain alive and will change and share data with all the other objects in the game. Let’s try for example to load the second scene with a GameControl initialised with health to 100 and experience to 1000:

You could have noticed I used the word instantiate quite a lot here, if you like to know more about it please we have an entire article where we discuss about it: Unity Instantiate Tutorial.

(Optional): ControlManager

Control Manager

If you don’t like the way that this object has to destroyed if already instantiated because in your game it’s full of data, we can think also to a ControlManager which is in charge to instantiate a GameControl or any Control objects if needed. In this way you should only have the ControlManager in your scenes instead of the GameControl:

public class ControlManager : MonoBehaviour
{
    //Reference to the prefab of the game control
    private Object gameControlRef;

    void Awake()
    {
        //Initialise the reference
        gameControlRef = Resources.Load("Prefabs/GameControl");

        //Check if the game control instance is null
        if (GameControl.control == null)
        {
            //This instance becomes the single instance available
            Instantiate(gameControlRef);
        }
        //The job is done this object is not used anymore so destroy it
        Destroy(gameObject);
    }
}

The ControlManager will decide if a Control Object should be instantiated or not. So a control will never be instantiated twice and the manager is a simple object without heavy data.

Conclusions

Here we are, finally and, let me say now, as usual we solved a case!

Now we know that the data can persist in different ways in Unity and we focused our attention to handle data between scenes.

We used in the very beginning the PlayerPrefs, it worked, but it’s not right for our purpose so we decided to find a better way proceeding by these steps:

  • Create an object to store the data we wanted to persist.
  • Use the DontDestroyOnLoad method to let it persist scene by scene.
  • Add a static reference to the object to let other objects access quickly and easily to its variables.
  • Create a prefab of the object and check that just one instance is alive during the game.

Great job partner! It could have been a nightmare without you.

See you for the next case!

More from Romeo Violini

Unity for 2D Game Development

It’s 2020, and 2D Games are more popular than ever. We have...
Read More

1 Comment

  • Thanks! I will consider this for a sports type game. There are ‘Innings’ or periods of play that dictate when the game is over. Points and strikes, and outs data must dynamically persist from the first inning to the last. Researching ‘Innings’ now…

Leave a Reply

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