Unity3D Tips #2 – The Singleton Pattern

Today we are going to talk about something truly unique… The singleton pattern!
The singleton pattern lets you write a class, which can only be instantiated once. A singleton object sure is ‘one of a kind’.

The singleton pattern is a very useful, yet very simple design pattern.
A design pattern is a general solution that you can apply to a problem in software design. In this case our problem is that we want a class, that there should only be one instance of, and we want easy access to that instance.

If you are a relatively new programmer, you might be thinking “What use is this? Why on earth would I write a class that can only be instantiated once?”
Now think of objects like an input manager responsible for reading input from the keyboard and mouse, or an audio handler like the one we described in our last post, responsible for playing audio in your game.
Having more than one instance of these, might cause some very incorrect behavior.

Okay, so there’s that. But what about writing a static class? Using a static class you can have a class with global access, and it can’t be instantiated so you will definitely not have multiple instances.
It seems that a static class could do the job, but it has some downsides:

• You can’t extend MonoBehaviour with a static class, and thereby not apply your class as a component on a GameObject in Unity.
• You can’t pass around a static class as a parameter
• You can’t implement an interface with a static class

Let’s try and look at a very simple implementation of the Singleton Pattern in Unity.

using UnityEngine;

public class AudioHandler : MonoBehaviour
{
    // Public field, set in the inspector we can access
    // the audio clip through the singleton instance
    public AudioClip explosionClip;
    
    // Static singleton property
    public static AudioHandler Instance { get; private set; }
    
    void Awake()
    {
        // Save a reference to the AudioHandler component as our singleton instance
        Instance = this;
    }

    // Instance method, this method can be accesed through the singleton instance
    public void PlayAudio(AudioClip clip)
    {
        audio.clip = clip;
        audio.Play();
    }
}

Now if you want to call the PlaySound() method from another class, you simply do it like this.

// Play sound
AudioHandler.Instance.PlayAudio(AudioHandler.Instance.explosionClip);

A great thing, as you see in the above example, is that you have easy access to the public variables set in the inspector. We can access the sound clips from the inspector through our singleton instance.

Let’s try and look at another more complex implementation of the Singleton Pattern

using UnityEngine;

public class AudioHandler : MonoBehaviour
{
    // Public field, set in the inspector we can access
    // the audio clip through the singleton instance
    public AudioClip explosionClip;
    
    // Static singleton property
    public static AudioHandler Instance { get; private set; }
    
    void Awake()
    {
        // First we check if there are any other instances conflicting
        if(Instance != null && Instance != this)
        {
            // If that is the case, we destroy other instances
            Destroy(gameObject);
        }

        // Here we save our singleton instance
        Instance = this;

        // Furthermore we make sure that we don't destroy between scenes (this is optional)
        DontDestroyOnLoad(gameObject);
    }

    // Instance method, this method can be accesed through the singleton instance
    public void PlayAudio(AudioClip clip)
    {
        audio.clip = clip;
        audio.Play();
    }
}

This implementation ensures that there can only be a single instance of the class, even if you accidently have more than one applied as a component in Unity. It also has the advantage/disadvantage depending on the context, of not getting destroyed between different scenes. Another feature that you might find good or bad is that the static instance variable doesn’t get reset between play sessions.

In a true singleton implementation, we have something called lazy instantiation. What this means is that the singleton instance itself, will not be created before you try to access it the first time around. This will generally spare you some resources, and is another great advantage of the singleton pattern. Now the reason we didn’t have any lazy instantiation in the above example, is because we want to be able to assign our audio clips in the inspector. This means that even before your game starts, the instance already exists, because you applied as a component by dragging and dropping.

I’m going to round up this post with a general implementation of the singleton pattern with lazy instantiation, which you can use if you are not going to assign any inspector fields.

using UnityEngine;

public class Singleton : MonoBehaviour
{
    // This field can be accesed through our singleton instance,
    // but it can't be set in the inspector, because we use lazy instantiation
    public int number;
    
    // Static singleton instance
    private static Singleton instance;
    
    // Static singleton property
    public static Singleton Instance
    {
        // Here we use the ?? operator, to return 'instance' if 'instance' does not equal null
        // otherwise we assign instance to a new component and return that
        get { return instance ?? (instance = new GameObject("Singleton").AddComponent<Singleton>()); }
    }

    // Instance method, this method can be accesed through the singleton instance
    public void DoSomeAwesomeStuff()
    {
        Debug.Log("I'm doing awesome stuff");
    }
}

The use is exactly the same; you call methods through the singleton instance.

// Do awesome stuff through our singleton instance
Singleton.Instance.DoSomeAwesomeStuff();

Now because of lazy instantiation, the first time you use the singleton instance, it is initialized and a new GameObject appears in the hierarchy.

And we can see that our awesome method actually got called in the console.

I hope you will find the singleton pattern useful, and if you have any trouble, improvements or feedback please leave a comment below 🙂

Comments

comments

Powered by Facebook Comments