Persistent UI

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.

Singleton

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.

The Singleton class

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:

  • If the app is closing right now, return null, don't do anything, and let the app close itself
  • If the T component isn't in the scene, create it now and use it
  • Otherwise, if it already exists in the scene, don't create a new one; use the existing one

This 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)));
}

Note

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.

The MenuManager implementation

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.

Removing the welcome text

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:

  1. Select our UI Root | Main | Buttons | Play GameObject.
  2. Click the Add Component button in the Inspector view.
  3. Type dis with your keyboard to search for components.
  4. Select Disappear On Click and hit Enter or click on it with your mouse.
  5. Drag our 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.86.18