How To Handle Data Between Scenes In Unity
📂

How To Handle Data Between Scenes In Unity

image

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

image

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.

PlayerPrefs an easy way but not the best

image
👉🏻
`PlayerPrefs` is a class that stores Player preferences between game sessions. It can store string, float and integer values into the user’s platform registry.

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

image

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, the experience, the color of the flag of our player, here is the snippet:

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

Pretty easy right? Just an object with 3 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, objects etc…

Handle Data Step 2: DontDestroyOnLoad method

image

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;
		public Color flagColor;

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

}

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

image

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!

🆓 Wait, We have a gift for you. Download the exclusive indie marketing guide

Handle Data Step 3: Static Reference

image

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;
		public Color flagColor;

    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;
Color flagColor = GameControl.control.flagColor;

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

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

image

Let’s solve the last problem: Object Reference management.

What if we want to test the 48th level? We should start from the 1st level otherwise our GameControl is not inherit from the previous scenes.

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!

image

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, but we want also to instantiate it the first time if it's not.

We can do one important check before getting the static Control property. Every time it’s called it will check if one instance already exists, otherwise it will get a new one which will persist between scenes from that moment. Basically just the first time it's called a new instance will be instantiated.

This will allow us to reach 2 important goals:

  • Avoid to manually add the GameControl object to any scenes.
  • Test any scenes without anomalies caused by objects not instantiated.

Remember the rule of Singleton: “one and only one will live forever and ever!” or maybe something like this.

public class GameControl : MonoBehaviour
{
	//The path to load the GameControl prefab  e.g. "Prefabs/GameControl"
	private static string prefabPath;
	
  //Static reference
  private static GameControl control;
  public static GameControl Control
  {
      set
      {
        control = value;
      }
      get
      {
	        //Check if the control instance is null
	        if (control == null)
	        {
	          //Initialise the reference game object
	          Object gameControlRef = Resources.Load(prefabPath);
	          GameObject controlObject = Instantiate(gameControlRef) as GameObject;
	          if (controlObject != null)
	          {
	            //Set the instance as the single one available
	            control = controlObject?.GetComponent<GameControl>();
	            //Let the gameobject persist over the scenes
	            DontDestroyOnLoad(controlObject);
	          }
	        }
	        return control;
      }
  }

  //Data to persist (here the list of your data you want to keep between scenes)
  public float health;
  public float experience;
}

Nothing change if we want to access 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;
Color flagColor = GameControl.control.flagColor;

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

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:

image

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!

image

Gladio Games is a gaming company, we simply love making games for us and for our clients. As you can see from this blog, we like to share what we learn and help the gaming community around the world to make better games.

So, if you are looking for a company for the creation of games, advergames, AR/VR experiences, send us a message. www.gladiogames.com - info@gladiogame.scom

image