Since our UI is composed of GameObjects, they are destroyed on a new scene load, thus we lose our menu as we enter the Game
scene when the Play
button is clicked.
We'll use the singleton method to store data, which will be used regularly, like the score, lives, and so on, and to ensure important UI elements persist from one scene to another. The singleton will also help us to easily access these variables during runtime.
The singleton pattern restricts the instantiation of a class to one unique object. It is useful when one object is required to have some generic actions available throughout different scenes.
With Unity, a singleton is a class that can only be instantiated once: it's attached to a GameObject in the scene that isn't destroyed on scene changing using the DontDestroyOnLoad()
property. This means that the GameObject and all its attached components or children, are persistent through scene changing. You might destroy it manually through code if required, but it's usually destroyed on application quit.
Also, the singleton has a static reference pointing to its instance, letting us easily access any of its variables or methods using MySingleton.Instance.anyVariable
.
If we ever try to access a method or variable through MySingleton.Instance
while the class isn't instantiated in the scene, it will be automatically created.
This is very useful and effective for global variables and common elements throughout the project; we'll see how to implement it in our MenuManager
component.
We will create a Singleton
class, from which any element we need to be a singleton should inherit. Create a new C# script named Singleton.cs
. Open it and change its class declaration to the following:
// Inherit from MonoBehavior and take another as T parameter
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
We declare that our Singleton
class inherits from MonoBehavior
—so that we still have access to neat functionalities such as Coroutines
—and takes MonoBehavior
as the T
parameter that will be used to know which class should be a singleton.
Remove both its default Start()
and Awake()
methods—we won't need them here.
Now, let's declare these three necessary global variables for our new Singleton
class:
// We'll need to store the current instance of the T class private static T _instance; //We'll use this to know if the app is currently closing private static bool appIsClosing = false;
Now that we have our necessary variables, let's declare the static Instance
of T
with an overridden getter method that will behave as follows each time it's accessed:
null
, don't do anything, and let the app close itselfT
component isn't in the scene, create it now and use itThis means we will always have only one instance of T
on the scene, and it will always be instantiated as soon as it's required.
Add this public
static
global variable just below our appIsClosing
bool:
// The public Instance of T, accessible from anywhere public static T Instance { // With an overridden getter method get { // If the app is closing... if (appIsClosing) { //... Return null, don't go further return null; } // If _instance is not assigned... if (_instance == null) { //... Find out if one already exists in the scene _instance = (T) FindObjectOfType(typeof(T)); // If it doesn't exist? Create it! if (_instance == null) { // Create a new GameObject... GameObject newSingleton = new GameObject(); //... Add the T component to it _instance = newSingleton.AddComponent<T>(); // Rename it with the T class's name newSingleton.name = typeof(T).ToString(); } // Mark it as DontDestroyOnLoad DontDestroyOnLoad(_instance); } // Return the final _instance return _instance; } }
From now on, if we try to access any script that inherits from the Singleton
class using AnyScript.Instance
, it will be created or retrieved and marked as DontDestroyOnLoad
, thus it will persist from one scene to another.
For security, we'll make sure it's marked as DontDestroyOnLoad
at Start()
, and if another instance of the script is found in the scene, we'll destroy it since we should only have one. Add the following Start()
method in our Singleton.cs
script:
// At start, for all singletons public void Start() { // Retrieve all instances of the singleton in the scene T[] allInstances = FindObjectsOfType(typeof(T)) as T[]; // If there's more than one of them if(allInstances.Length > 1) { // For each of the found instances... foreach(T instanceToCheck in allInstances) { // If the found instance is not the current one if(instanceToCheck != Instance) { // Destroy it now Destroy(instanceToCheck.gameObject); } } } // Mark the existent instance as DontDestroyOnLoad DontDestroyOnLoad((T) FindObjectOfType(typeof(T))); }
Make sure you do not have a Start()
method in your MenuManager.cs
script; otherwise, it will override the Start()
method of Singleton.cs
and not apply the DontDestroyOnLoad
property.
If you need a Start()
method in MenuManager
—or any other script inheriting from the Singleton
class—its first Start()
instruction must be:
base.Start();
That way, the Singleton
class's Start()
method will always be executed before your custom class's Start()
instructions.
Ok. From now on, even if the script is never accessed externally through code, it will be persisted from one scene to another.
We have one last issue to resolve with the Singleton.cs
script: when Unity quits, it destroys objects in a random order. If a script calls MenuManager.Instance
after it has been destroyed, it will create a weird ghost object that will stay on the editor scene that will still be there even after stopping the play mode!
Let's use our appIsClosing
bool to prevent this ghost object from being created. Add this OnApplicationQuit()
method to our Singleton.cs
script:
// When the application quits void OnApplicationQuit() { //... Set the appIsClosing boolean to true appIsClosing = true; }
When the application is quitting, our singleton will no longer be re-instantiated and will return null
. We will thus have a clean exit of the application and avoid buggy ghosts.
Now that we have Singleton.cs
, let's implement it in our MenuManager.cs
script.
We need our MenuManager
class to inherit from our new Singleton
class. Open the MenuManger.cs
script:
public class MenuManager : MonoBehavior {
Then, change its class declaration to this:
public class MenuManager : Singleton<MenuManager> {
Ok. That's it! Now, our UI Root
and all its children will be persistent through scene changing. Hit Unity's play button and press our menu's Play
button. You'll notice that our UI Root
is still present in the Hierarchy view, and our welcome text is still displayed.
Now that our main menu isn't destroyed on scene changing, our welcome text is still displayed within the Game
scene. Let's make sure it disappears using a second DisappearOnClick
component on our Play
button:
UI Root
| Main
| Buttons
| Play
GameObject.dis
with your keyboard to search for components.UI Root
| Welcome
GameObject in its Target field.That's it—when the Play
button is pressed, nothing is displayed on the Game
scene, even though all our UI is still here. We will thus be able to access it and show our main menu or options page anytime.
3.139.86.18