Skip to content

SingletonBehavior

Chillersanim edited this page Feb 10, 2020 · 2 revisions

SingletonBehaviour

Namespace: Unity_Tools.Components

Base class for MonoBehaviours that needs to implement the singleton pattern.

[InitializeOnLoad]
[ExecuteInEditMode]
public abstract class SingletonBehaviour<T> : 
    MonoBehaviour 
where T : 
    SingletonBehaviour<T>

Examples

The following example provides an implementation for a single, global scoreboard.
It offers a static accessor for the score and stores the actual score in an instance field.
This has the benefit, that the class works with the Unity serialization framework, you can edit the score in the inspector.
At the same time, the score is globally accessible and doesn't require a reference to the Scores instance.

public class ScoreManager : SingletonBehaviour<ScoreManager>
{
    // Only use instance fields to store data.
    [SerializeField]
    private int score;

    // Static accessor for the score
    public static int Score
    {
        get
        {
            // Don't access the instance without ensuring access first
            if (Scores.CanAccessInstance)
            {
                return Instance.score;
            }

            return -1;
        }
        set
        {
            if (Scores.CanAccessInstance)
            {
                Instance.score = value;
            }
        }
    }

    protected override void OnEnable()
    {
        // Always call the base implementation to ensure correct behavior
        base.OnEnable();

        // Make sure the score is not negative on game start.
        if (score < 0)
        {
            score = 0;
        }
    }
}

Remarks

Instance

Classes descending from SingletonBehaviour have a static Instance property providing a reference to the only existing instance of that class.
This property is guaranteed to return a valid instance, unless CanAccessInstance returns false.
If no instance exists, a new instance will be created.

Internal mechanisms ensure, that at any given time, at most one instance of any deriving class exists.
Manually creating a new instance is supported, however, if another instance already exists, the new instance will be destroyed immediatelly.

In case loading multiple scenes would cause multiple instances to exist, it is undefined which instance will get destroyed.
It is advised to use a global scene for singleton instances in this case.

CanAccessInstance

When the game is shutting down, the game objects and components get unloaded in some undefined order.
Creating new instances is prohibited at that point and leads to memory leaks and incomplete cleanup.

When the game is shutting down, the existence of an singleton instance is undefined and accessing the Instance property can cause an InvalidOperationException.
To prevent that, you should always check the CanAccessInstance property before accessing the Instance property.

Static vs Singleton

While many singletons can be represented using a static class, there are a few key difference which makes a singleton a better choice over static classes.

  • Serialization and inspector
    Unity doesn't serialize static classes.
    If you need your data to be stored or accessible in the inspector, you should use the singleton pattern.

  • Editor
    When starting and stopping games in the editor, Unity sometimes does not reload the assemblies.
    In this case, static fields and properties will retain their old state, leading to undefined behavior.
    However, MonoBehaviour instances will be properly reloaded, ensuring that SingletonBehaviours have a clean state.

  • Scenes
    It is possible to have individual singleton instance in individual scenes.
    This is useful, if you need to load a specific state for some scenes.
    Unless you load multiple scenes at once, the SingletonBehaviour works out of the box with this setup.
    On the other hand, a static class doesn't know about scenes and needs to be initialized manually.

Implementing the singleton pattern

If you have determined that a class needs to implement the singleton pattern, there are a few points to consider.

  • SingletonBehaviour is generic and requires you to provide the type of the deriving class as parameter. (See Example)
  • Only use instance fields and properties for storage, use static properties only to access instance fields. (See Static vs Singleton)
  • You can override OnEnable, OnDestroy and OnApplicationQuit, however you must also forward the call to the base implementation using base.[MethodName]. (See Example)
  • It is good practice to provide static properties and methods to access common functionality, as it removes the need to use the Instance property outside the class. (See Example)

Properties

  • CanAccessInstance
    Gets a value indicating whether accessing the singleton instance is allowed or not.
  • Instance
    Gets the single class instance, if no such instance exists, a new instance will be created.

Methods

  • [protected virtual] OnApplicationQuit() : void
    Called when Unity begins to shut down.
  • [protected virtual] OnDestroy() : void
    Called when the instance is being destroyed.
  • [protected virtual] OnEnable() : void
    Called when the instance is being enabled..

Thread safety

The SingletonBehaviour inherits from MonoBehaviour.
Like all MonoBehaviours, the SingletonBehaviour should only be accessed by the main thread.