Using singletons in Unity

Hi everyone! I hope you are ready to get some great Easter holiday time? Or maybe advance your Unity projects? If so, this may interest you!

I am Guilhem, the main programmer at Space Lizard Studio and I will discuss the use of singletons in unity, how to mix them with monobehaviors and get a clean implementation of those.

This course is for beginner or intermediate programmers, or advanced programmers starting to use Unity.

What is a Singleton?

A singleton is a design pattern for objects that are not supposed to be created more than once. A music manager for instance: you probably don’t want more than one music manager in your game at any given time. The Singleton helps you to enforce this rule, and gives you a shortcut to access this music manager.

A very simple singleton implementation could be:

public class Singleton <T> where T: Singleton<T>, new()
{
    protected Singleton() { }
    public static T Instance 
    { 
        get 
        {
            if (instance == null)
                instance = new T();
            return instance; 
        } 
    }

    protected static T instance = null;

}

[...]

public class MusicManager: Singleton<MusicManager>
{}

Let’s start with the first line: “protected Singleton () {}”. Here we say “hey that constructor is off limit, you can’t create a singleton by using new Singleton<T>() or new MusicManager()”. Instead, we want people to use the property “Instance”. So to access the music manager, you would do MusicManager.Instance(). If it is the first time someone call the music manager, then it will create a music manager. It will save it in a static variable called instance (with a small ‘i’). The second time someone calls the music manager, it will find this already created instance and return it. No one can create it twice anymore!

Why should I use it?

Because it’s crazy convenient. First, you don’t need to check if a music manager already exists, or not, or how to handle two music managers, etc. But the second and most important reason, is that now you can call MusicManager.Instance from anywhere in your code. Let’s say you have a MusicChangerEvent for instance, you can easily do something like that:

MusicManager.Instance.SetMood(MusicMood.Mysterious);

What if I want a MonoBehavior singleton?

As you get used to those convenient Singleton, you may wonder how to make them monobehaviors AND singletons? Well it’s a bit different, but not necessary more complex:

public class MonoSingleton<T> : MonoBehaviour where T : MonoSingleton<T>
{
    public static T Instance { get { return instance; } }
    protected static T instance = null;

    protected virtual void Awake()
    {
        if (instance != null)
        {
            Debug.Log("You have two instances of " + gameObject +" mono singleton!");
            gameObject.SetActive(false);
            Destroy(gameObject);
        }
        else
            instance = (T)this;
    }
}

public class BulletsManager: MonoSingleton<BulletsManager>
{}

Here we can’t make the constructor private or protected – this is a monobehavior. So instead you may want to destroy any additional monobehavior that gets created, and warn the user about this.

The awake function is marked as virtual. If you haven’t encountered this word before, it means that this function can be overridden by any class inheriting MonoSingleton (its “children”) if they want to. So virtual is a bit like “check if my children have changed the behavior of this function”. The children will simply have to declare the function again and replace “virtual” by “override”.

You will note that this is a bit different from the precedent case: what if you haven’t added this mono singleton to the scene? Well, it will crash. Not cool eh? So we need to do two things:

  • add an error message before it crashes in the Instance.get. This will be useful for simple MonoSingletons that are mandatory in the scene. Make the message meaningful, because your designers will be the ones reading it. Something like: “Hey Mike, you forgot to add a Bullet Manager to the scene”.
  • create a special mono singleton that can instantiate itself if it is not in the scene. This will be useful for objects that you may want to have sometime in your scene, but not always. They need to be OK to load from disk and create a small stutter. A special UI error message would fit for instance: as the player unplugs the game pad, you want to display your UI error message. This is unlikely to happen every time, so it is the perfect case. As you will pause the game as soon as the player unplugs the cable, it is OK to have a 1s stutter while the MonoSingleton is loaded and instantiated.

This second MonoSingleton will be a bit different:

public abstract class  MonoRobustSingleton<T> : MonoBehaviour where T : UiSingleton<T>
{
    public static T Instance
    {
        get
        {
            if (instance == null)
            {
                string fileName = "Prefab/MonoSingletonPrefabs/" + typeof(T).Name;
                GameObject newSingleton = (GameObject)GameObject.Instantiate(Resources.Load(fileName));
                return newSingleton.GetComponent<T>();
            }
            return instance;
        }
    }

    protected static T instance;

    protected virtual void Awake()
    {
        if (instance != null)
        {
            Debug.LogError("MonoRobustSingleton " + gameObject + " is instanciated twice.");
            Destroy(this);
            return;
        }

        instance = (T)this;
    }
}

public class PadDisconnectedErrorMessage: MonoRobustSingleton<PadDisconnectedErrorMessage>
{}

We still have the static instance, and the error messages in the Awake function to handle unexpected scenarios. But the instance creation is different: we load a prefab from the resources folder. We could go the easy way and create a GameObject and add a component T to it. The issue though is that your MonoRobustSingleton will often need other components and some children to work. So we force the user to create a prefab, name it PadDisconnectedErrorMessage and store it in the Resources folder. This folder is a bit magic: whatever file is placed inside it will be taken by Unity upon building your game. It will be accessible later for hot loading, even if no one references it explicitly. The files in the other folders will only be included in the built if they are referenced somehow by an object in your scene. So our MonoRobustBehavior prefabs are safe in this resource folder! They will be lazy-loaded whenever needed (lazy in the sense “only done if required, not loaded by default).

Conclusion

The mono behavior singletons are not as safe as the classical singleton, nor as easy to use, but they remain a very powerful system. For our game, we based our UI system on MonoRobustBehavior: we wanted to load the bare minimum UI with the scene, to speed up loading times and allow higher modularity (ie: call any UI screen when you need it, no matter the context or the scene setup). So when you press “pause” or go through the menus, we hot load the UI as required. Although the framerate does go down whenever a new page is drawn, it is impossible to tell the difference. It would be visible though if we used it to load enemies for instance, as a small lag is much more noticeable when you move a character in real time than when you press a button to go from a menu page to another. But it saved us a few seconds of loading screen on our game! (we have simple scenes but heavy UI).

Anyway, I hope this post will help you with singletons! I know some people find them overused, but for simple projects I find them perfect and easy to understand. If you have any comment, disagree or even think I am Hitler reincarnated, leave a comment!

Social media frenzy

Leave a Reply

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