An ideal way to store variables between scenes is through a singleton manager class. By creating a class to store persistent data, and setting that class to DoNotDestroyOnLoad()
, you can ensure it is immediately accessible and persists between scenes.
Another option you have is to use the PlayerPrefs
class. PlayerPrefs
is designed to allow you to save data between play sessions, but it will still serve as a means to save data between scenes.
Using a singleton class and DoNotDestroyOnLoad()
The following script creates a persistent singleton class. A singleton class is a class that is designed to only run a single instance at the same time. By providing such functionality, we can safely create a static self-reference, to access the class from anywhere. This means that you can directly access the class with DataManager.instance
, including any public variables inside the class.
using UnityEngine;
/// <summary>Manages data for persistance between levels.</summary>
public class DataManager : MonoBehaviour
{
/// <summary>Static reference to the instance of our DataManager</summary>
public static DataManager instance;
/// <summary>The player's current score.</summary>
public int score;
/// <summary>The player's remaining health.</summary>
public int health;
/// <summary>The player's remaining lives.</summary>
public int lives;
/// <summary>Awake is called when the script instance is being loaded.</summary>
void Awake()
{
// If the instance reference has not been set, yet,
if (instance == null)
{
// Set this instance as the instance reference.
instance = this;
}
else if(instance != this)
{
// If the instance reference has already been set, and this is not the
// the instance reference, destroy this game object.
Destroy(gameObject);
}
// Do not destroy this object, when we load a new scene.
DontDestroyOnLoad(gameObject);
}
}
You can see the singleton in action, below. Note that as soon as I run the initial scene, the DataManager object moves from the scene-specific heading to the "DontDestroyOnLoad" heading, on the hierarchy view.
Using the PlayerPrefs
class
Unity has a built in class to manage basic persistent data called PlayerPrefs
. Any data committed to the PlayerPrefs
file will persist across game sessions, so naturally, it is capable of persisting data across scenes.
The PlayerPrefs
file can store variables of types string
, int
and float
. When we insert values into the PlayerPrefs
file, we provide an additional string
as the key. We use the same key to later retrieve our values from the PlayerPref
file.
using UnityEngine;
/// <summary>Manages data for persistance between play sessions.</summary>
public class SaveManager : MonoBehaviour
{
/// <summary>The player's name.</summary>
public string playerName = "";
/// <summary>The player's score.</summary>
public int playerScore = 0;
/// <summary>The player's health value.</summary>
public float playerHealth = 0f;
/// <summary>Static record of the key for saving and loading playerName.</summary>
private static string playerNameKey = "PLAYER_NAME";
/// <summary>Static record of the key for saving and loading playerScore.</summary>
private static string playerScoreKey = "PLAYER_SCORE";
/// <summary>Static record of the key for saving and loading playerHealth.</summary>
private static string playerHealthKey = "PLAYER_HEALTH";
/// <summary>Saves playerName, playerScore and
/// playerHealth to the PlayerPrefs file.</summary>
public void Save()
{
// Set the values to the PlayerPrefs file using their corresponding keys.
PlayerPrefs.SetString(playerNameKey, playerName);
PlayerPrefs.SetInt(playerScoreKey, playerScore);
PlayerPrefs.SetFloat(playerHealthKey, playerHealth);
// Manually save the PlayerPrefs file to disk, in case we experience a crash
PlayerPrefs.Save();
}
/// <summary>Saves playerName, playerScore and playerHealth
// from the PlayerPrefs file.</summary>
public void Load()
{
// If the PlayerPrefs file currently has a value registered to the playerNameKey,
if (PlayerPrefs.HasKey(playerNameKey))
{
// load playerName from the PlayerPrefs file.
playerName = PlayerPrefs.GetString(playerNameKey);
}
// If the PlayerPrefs file currently has a value registered to the playerScoreKey,
if (PlayerPrefs.HasKey(playerScoreKey))
{
// load playerScore from the PlayerPrefs file.
playerScore = PlayerPrefs.GetInt(playerScoreKey);
}
// If the PlayerPrefs file currently has a value registered to the playerHealthKey,
if (PlayerPrefs.HasKey(playerHealthKey))
{
// load playerHealth from the PlayerPrefs file.
playerHealth = PlayerPrefs.GetFloat(playerHealthKey);
}
}
/// <summary>Deletes all values from the PlayerPrefs file.</summary>
public void Delete()
{
// Delete all values from the PlayerPrefs file.
PlayerPrefs.DeleteAll();
}
}
Note that I take additional precautions, when handling the PlayerPrefs
file:
- I have saved each key as a
private static string
. This allows me to guarantee I am always using the right key, and it means that if I have to change the key for any reason, I do not need to ensure I change all references to it.
- I save the
PlayerPrefs
file to the disk after writing to it. This probably won't make a difference, if you do not implement data persistence across play sessions. PlayerPrefs
will save to the disk during a normal application close, but it may not naturally call if your game crashes.
- I actually check that each key exists in the
PlayerPrefs
, before I attempt to retrieve a value associated with it. This might seem like pointless double-checking, but it is a good practice to have.
- I have a
Delete
method that immediately wipes the PlayerPrefs
file. If you do not intend to include data persistence across play sessions, you might consider calling this method on Awake
. By clearing the PlayerPrefs
file at the start of each game, you ensure that any data that did persist from the previous session is not mistakenly handled as data from the current session.
You can see PlayerPrefs
in action, below. Note that when I click "Save Data", I am directly calling the Save
method, and when I click "Load Data", I am directly calling the Load
method. Your own implementation will likely vary, but it demonstrates the basics.
As a final note, I should point out that you can expand upon the basic PlayerPrefs
, to store more useful types. JPTheK9 provides a good answer to a similar question, in which they provide a script for serialising arrays into string form, to be stored in a PlayerPrefs
file. They also point us to the Unify Community Wiki, where a user has uploaded a more expansive PlayerPrefsX
script to allow support for a greater variety of types, such as vectors and arrays.