Singletons in Unity

The Singleton Design Pattern

While the singleton design pattern seems like it should be simple enough to implement, in larger applications issues often arise from more complex object initialization. For example, in Unity's editor scripts may be recompiled on the fly, and Play Mode can be entered multiple times all while the editor continues to run. While there are several existing examples for singleton implementations in Unity, some only apply to runtime (gameplay) singletons and some of them have issues with newer project settings that may be difficult to track down without additional understanding. In this article, I'll cover singleton implementations for use in both Unity's editor and at runtime and discuss several potential issues with each.

Singletons in C# 

Before jumping into Unity specifics, let's look at a fairly basic singleton implementation in C#. (See Jon Skeet's great singletons article for additional implementations and their pros and cons.) A simple, thread-safe version can be written as follows:

public sealed class SimpleSingleton
{
// Private to prevent others from creating an instance.
private SimpleSingleton() { }

// The single instance of this class.
public static readonly SimpleSingleton instance = new SimpleSingleton();
}

There's not much see here. The use of the sealed keyword and the private constructor prevent derivation which could lead to more than one instance being created. And thread safety is provided by C#'s runtime initialization guarantees for class constructors and static fields. However, be sure to read through Jon's article (and his beforefieldinit article) to understand the issues involved with exactly when the singleton could be instantiated.

Issues with Singletons in Unity

The majority of issues that occur when using standard approaches for singletons in Unity's editor are related to its ability to recompile both editor plugins and gameplay scripts while the editor remains running. To do this the editor serializes instances of ScriptableObject and MonoBehaviour derived classes, performs a domain reload in which the old assemblies are unloaded and the newly compiled ones are loaded, and then it re-creates the objects by deserializing their saved state. Unfortunately, this process does not persist the state of static fields and both class constructors and static field initializers are called again after each reload which results in problems when using the above implementation. In addition to that, there is a newer project setting that prevents domains from reloading before entering Play Mode which has the opposite effect for static fields used at runtime. Let's take a quick look at each of these settings.

Issues with the Preference: "Script Changes While Playing"

The Script Changes While Playing setting is found in Edit / Preferences / General and controls what happens when a script modification is detected while still in Play Mode. The default setting is to Recompile and Continue Playing which will attempt to recompile, serialize the game's current state, reload the domain, and then deserialize the game's state. This is similar to how changes are handled outside Play Mode, and it suffers from the same issue that static fields are still not persisted across reloads.

Another issue with using this setting is that runtime-only types are more likely to be non-serializable (missing the [Serializable] attribute, etc.) resulting in their state being reset after a script change. This can result in bugs occurring after a reload that wouldn't normally be bugs, and it is for this reason that I never use this option. If you're a fan of the Recompile and Continue Playing option, don't worry, all but the simplest MonoBehaviour singleton below will work properly with this option.

Note: Unity should probably add a Project Setting to prevent users from selecting the Recompile and Continue Playing as the project's code determines whether the Continue Playing option will function properly.

Issues with the Project Setting: "Enter Play Mode Settings"

The second setting that affects static variables is found in Edit / Project Settings / Editor. The Enter Play Mode Settings options are relatively new in Unity and control what happens when the user enters Play Mode. By default and in previous versions (or when the Reload Domain setting is enabled), Unity performs a domain reload before entering Play Mode which ensures that all static variables are reset to mimic the normal behavior when starting from a full build. Since this operation can be quite slow, Unity added an option to skip this step, but it is then up to the developer to ensure that static fields are properly reset.

So how does this affect runtime singleton implementations? If domain reloading is disabled, the static field that holds the singleton instance will persist between multiple Play Mode executions and possibly lead to bugs depending on the singleton's base type. Note that Unity's documentation suggests "fixing" this issue using an attribute that executes "reset" code when Play Mode starts, but this is incorrect in my opinion. This is an issue that occurs only in the editor and static fields could reference other instances resulting in uncollected garbage until Play Mode is started again. Another, bigger issue with their recommendation is that the attribute is not supported on generic types. For these reasons, the runtime implementations below clean up when Play Mode exits.

ScriptableObject-Based Editor Singletons

Since Unity will persist the state of ScriptableObject and MonoBehaviour derived classes across domain reloads, it makes sense to use one (or both) of these as a base class for singletons. Since ScriptableObjects have fewer restrictions on how and when they are created (MonoBehaviour components may only be created attached to a GameObject and those must live in a scene or prefab), they are a better candidate for editor plugins that don't require a GUI but need to persist state across domain reloads. It would be nice if Unity provided a base class for these types of plugins similar to the EditorWindow class but until they do, we're left to figure out how they should be properly implemented.

Ideally, we could simply take the previous example, derive from ScriptableObject, and call CreateInstance() instance of using new, but this would throw the following exception:

UnityException: CreateScriptableObjectInstanceFromType is not allowed to be called from a ScriptableObject constructor...

This occurs because Unity has some pretty specific restrictions on when it allows the creation of their types (likely to avoid other initialization and threading issues) and this includes calls from class constructors or static field initializers. So the call to CreateInstance() needs to be deferred until later and to do that we need to delay the creation of the instance until it is safe to do so using a property getter similar to the 1st method in Jon's paper. Since there are a few more steps involved, I decided to make a generic base class for it:

EditorSingleton.cs
// Copyright 2021 by Hextant Studios. https://HextantStudios.com
// This work is licensed under CC BY 4.0. http://creativecommons.org/licenses/by/4.0/
using UnityEngine;

namespace Hextant.Editor
{
// ScriptableObject-based singleton for GUI-less editor plug-ins.
// Note: OnEnable() / OnDisable() should be used to register with any global
// events to properly support domain reloads.
public abstract class EditorSingleton<T> : ScriptableObject
where T : EditorSingleton<T>
{
// The singleton instance. (Not thread safe but fine for ScriptableObjects.)
public static T instance => _instance != null ? _instance : Initialize();
static T _instance;

// Finds or creates the singleton instance and stores it in _instance. This
// can be called from a derived type to ensure creation of the singleton
// using the [InitializeOnLoadMethod] attribute on a static method.
protected static T Initialize()
{
// If the instance is already valid, return it. Needed if called from a
// derived class that wishes to ensure the instance is initialized.
if( _instance != null ) return _instance;

// Find the existing instance or creates a new one.
var instances = Resources.FindObjectsOfTypeAll<T>();
return instances.Length > 0 ? _instance = instances[ 0 ] :
CreateInstance<T>();
}

// Called once during creation of this instance. Derived classes should call
// this base method first if overridden.
protected virtual void Awake()
{
// Verify there is only a single instance; catches accidental creation
// from other CreateInstance() calls.
Debug.Assert( _instance == null );

// Ensure _instance is assigned here to prevent possible double-creation
// should the instance property be called by a derived class handler.
_instance = ( T )this;

// Prevent Resources.UnloadUnusedAssets() from destroying the singleton
// instance if called or when new scenes are loaded.
_instance.hideFlags = HideFlags.HideAndDontSave;
}
}
}

This code simply checks to see if the instance has been created and if so, returns it. Otherwise, it calls Initialize() which will find or create the singleton instance. During the first call to the Initialize() method, CreateInstance() will be called to create the singleton. This will result in a call to the Awake() handler which will assign the _instance field. This assignment is done here instead of in Initialize() to catch a possible double-creation issue that could occur if code in a derived type's Awake() or OnEnable() handlers resulted in another call to the instance property. Awake() also sets the hideFlags to HideAndDontSave to prevent the instance from being destroyed by any calls to UnloadUnusedAssets() or scene loads.

To solve the issue of the static _instance field being reset by a domain reload, the next call that is made to the instance property after a reload will call Initialize() again. This time FindObjectsOfTypeAll() will return the "orphaned" instance and reassign it to _instance.

When creating derived classes note that, unlike normal ScriptableObject and MonoBehaviour serialization to disk, Unity's editor will serialize both public and private/protected fields during domain reloads. If there's a field that you wish to not be serialized, just specify the [System.NonSerialized] attribute. The newer [SerializeReference] attribute can also be used to serialize references to non-UnityEngine.Object-derived classes, and it properly serializes null fields and fields whose types are base classes or interfaces. Also, remember that any custom structs or C# classes must specify the [System.Serializable] attribute for it to be serialized across domain reloads.

Be aware that editor events are not persisted during domain reloads. Therefore, it is important to perform any necessary event registration inside the OnEnabled() handler (called after the domain reloads) and deregistration inside the OnDisabled() handler (called before it unloads).

One final thing to note is that OnDestroy() will never be called (at least from my testing) even when the editor exits. This avoids issues where one singleton may access a previously destroyed one which would result in it being re-created. If needed, it's possible to do any final cleanup by registering with the EditorApplication.quitting event in OnEnable().

A simple example using the EditorSingleton class that persists a _count field between script recompiles is as follows:

EditorSingletonExample.cs
using UnityEditor;
using UnityEngine;
using Hextant.Editor;

public sealed class EditorSingletonExample : EditorSingleton<EditorSingletonExample>
{
// Initialize the singleton when the editor loads.
[InitializeOnLoadMethod]
static void OnLoad() => Initialize();

// Add a main menu option to log the number of times the singleton has
// been enabled (from domain reloads).
[MenuItem( "Test/Editor Singleton Enabled Count" )]
static void LogCount() => Debug.Log( $"LogCount: {instance._count}" );

// Log the number of times the singleton has been enabled (from domain reloads).
void OnEnable() => Debug.Log( $"Enabled: {++_count}" );

int _count;
}

The static OnLoad() method is declared using Unity's [InitializeOnLoadMethod] attribute to create the singleton instance when the editor starts. You might be tempted to use the similar-sounding class attribute [InitializeOnLoad] which causes the class constructor to be called when the editor starts (or during a domain reload), but, once again, this will result in a UnityException because of the call that will be made to CreateInstance().

The OnLogCount() method demonstrates how to access the singleton through its instance property by registering a main menu callback with the [MenuItem] attribute.

ScriptableObject-Based Runtime Singletons

Since no editor-specific APIs were used, the ScriptableObject-based singleton above could also be used for runtime singletons. However, there are a few issues that likely make MonoBehaviour a better candidate.

The first issue with ScriptableObject-based runtime singletons is that they will not be destroyed when exiting play mode. This can be solved by registering for the playModeStateChanged event and destroying the singleton there, though this only needs to be done in the editor. Note that this is done when the state value is EnteredEditMode as the ExitingPlayMode is called before MonoBehaviour instances are destroyed.

Next, singletons need to properly prevent re-creation during destruction (and creation outside of Play Mode). Both issues can be handled by checking the Application.isPlaying property which is false when singletons are destroyed by the callback above when entering edit mode.

What prevents ScriptableObject from being a good base class for some runtime singletons is that there is no runtime Application.update event, unlike in the editor which has EditorApplication.update. If this isn't an issue and you still wish to use a ScriptableObject-based singleton for use at runtime, the following can be used:

ScriptableObjectSingleton.cs
// Copyright 2021 by Hextant Studios. https://HextantStudios.com
// This work is licensed under CC BY 4.0. http://creativecommons.org/licenses/by/4.0/
using UnityEngine;

// A ScriptableObject-based singleton for use at runtime (in-game).
// Note: OnEnable() / OnDisable() should be used to register with any global events
// to properly support domain reloads.
public abstract class ScriptableObjectSingleton<T> : ScriptableObject
where T : ScriptableObjectSingleton<T>
{
// The singleton instance. (Not thread safe but fine for ScriptableObjects.)
public static T instance => _instance != null ? _instance : Initialize();
static T _instance;

// Finds or creates the singleton instance and stores it in _instance. This can
// be called from a derived type to ensure creation of the singleton using the
// [RuntimeInitializeOnLoadMethod] attribute on a static method.
protected static T Initialize()
{
// Prevent runtime instances from being created outside of Play Mode or
// re-created during OnDestroy() handlers when exiting Play Mode.
if( !Application.isPlaying ) return null;

// If the instance is already valid, return it. Needed if called from a
// derived class that wishes to ensure the instance is initialized.
if( _instance != null ) return _instance;

// Find the existing instance (across domain reloads) or create a new one.
var instances = Resources.FindObjectsOfTypeAll<T>();
return instances.Length > 0 ? _instance = instances[ 0 ] :
CreateInstance<T>();
}

// Called once during creation of this instance. Derived classes should call
// this base method first if overridden.
protected virtual void Awake()
{
// Verify there is only a single instance; catches accidental creation
// from other CreateInstance() calls.
Debug.Assert( _instance == null );

// Ensure _instance is assigned here to prevent possible double-creation
// should the instance property be called by a derived class handler.
_instance = ( T )this;

// HideAndDontSave prevents Resources.UnloadUnusedAssets() from destroying
// the singleton instance if called or when new scenes are loaded.
_instance.hideFlags = HideFlags.HideAndDontSave;
}

// Called when the singleton is destroyed after exiting play mode.
protected virtual void OnDestroy() => _instance = null;

// Called when the singleton is created *or* after a domain reload in the editor.
protected virtual void OnEnable()
{
#if UNITY_EDITOR
UnityEditor.EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
#endif
}

#if UNITY_EDITOR
// Called when entering or exiting play mode.
void OnPlayModeStateChanged( UnityEditor.PlayModeStateChange stateChange )
{
// Note that EnteredEditMode is used because ExitingPlayMode occurs *before*
// MonoBehavior.OnDetroy() which is likely too early.
if( stateChange == UnityEditor.PlayModeStateChange.EnteredEditMode )
{
UnityEditor.EditorApplication.playModeStateChanged -=
OnPlayModeStateChanged;
DestroyImmediate( this );
}
}
#endif
}

To instantiate this singleton at the start of the game, the [RuntimeInitializeOnLoadMethod] attribute must now be used. The BeforeSceneLoad flag can even be passed into this attribute to create the singleton before any scenes are loaded if desired:

[RuntimeInitializeOnLoadMethod( RuntimeInitializeLoadType.BeforeSceneLoad )]
static void OnLoad() => InitializeInstance();

MonoBehaviour-Based Runtime Singletons

While ScriptableObject-based runtime singletons are sometimes helpful, MonoBehavior-based singletons will likely be a bit more useful for specific gameplay classes that need to be accessed quickly and also expose editable properties or perform per-frame updates. As with any singleton, it's worth taking time to decide which classes should really be singletons. For example, it might seem convenient at first to make a Player class singleton, but that will cause problems should the game need to support multiple players later. Likewise, it might seem reasonable to create a SceneManager singleton to track "global" state for your current scene, but that quickly fails if multi-scene loading is needed. Not every game needs to worry about these issues, but it's something to think about.

Since the project that I'm currently working on supports multiple players and it's possible for each player to load a different scene, I've limited the use of runtime singletons to my Game and World classes. The Game behavior is added to a GameObject in a similarly named Game scene and it references several global resources that are always loaded. The World singleton is instantiated from a prefab when a new playthrough begins (or a previously saved one is loaded) and contains any global state for the current playthrough (current players, state of quests, etc.). I feel pretty certain that there will only be one of each of these classes, so let's first look at two singleton implementations that should be placed in a scene or created manually.

SimpleMonoBehaviourSingleton.cs
// Copyright 2021 by Hextant Studios. https://HextantStudios.com
// This work is licensed under CC BY 4.0. http://creativecommons.org/licenses/by/4.0/
using UnityEngine;

// A simple MonoBehaviour-based singleton for use at runtime.
// Note: This implementation does not support the "Recompile and Continue Playing"
// editor preference!
public class SimpleMonoBehaviourSingleton<T> : MonoBehaviour
where T : SimpleMonoBehaviourSingleton<T>
{
// The singleton instance.
public static T instance => _instance;
static T _instance;

// Called when the instance is created.
protected virtual void Awake()
{
// Verify there is not more than one instance and assign _instance.
Debug.Assert( _instance == null,
"More than one singleton instance instantiated!", this );
_instance = ( T )this;
}

// Clear the instance field when destroyed.
protected virtual void OnDestroy() => _instance = null;
}

This is the code that I use in my project for the singletons mentioned above. I just inline it and don't bother with the base class because it is so simple. However, there are two requirements that must be met to use it properly.

The first requirement is that the Script Changes While Player preference must not be set to Recompile and Continue Playing as the static _instance value will not be reset if the code recompiles while in Play Mode.

The second requirement is that the singleton must have a higher script priority than other scripts that may try to access it in their Awake() handler so that the singleton's _instance field is assigned first. This can be done using the Project Settings / Script Execution Order or using the [DefaultExecutionOrder] attribute on the derived class. This shouldn't be an issue in practice as it is a general requirement for MonoBehaviors that Awake() handlers do not attempt to access another behavior; the Start() handler should be used instead to avoid similar issues.

I prefer this method over the next one as I like to keep an eye on initialization order, but if you want to use the Continue Playing option, the following can be used instead:

MonoBehaviourSingleton.cs
// Copyright 2021 by Hextant Studios. https://HextantStudios.com
// This work is licensed under CC BY 4.0. http://creativecommons.org/licenses/by/4.0/
using UnityEngine;

// A MonoBehaviour-based singleton for use at runtime. Add to a single 'global' scene.
// Note: OnEnable() / OnDisable() should be used to register with any global events
// to properly support domain reloads.
public class MonoBehaviourSingleton<T> : MonoBehaviour
where T : MonoBehaviourSingleton<T>
{
// The singleton instance.
public static T instance => _instance != null ? _instance :
Application.isPlaying ? _instance = FindObjectOfType<T>() : null;
static T _instance;

// Called when the instance is created.
protected virtual void Awake()
{
// Verify there is not more than one instance and assign _instance.
Debug.Assert( _instance == null || _instance == this,
"More than one singleton instance instantiated!", this );
_instance = ( T )this;
}

// Clear the instance field when destroyed.
protected virtual void OnDestroy() => _instance = null;
}

This MonoBehaviour-based singleton works with the Recompile and Continue Playing option by using the FindObjectOfType() method which will lookup the instance when it is called after a domain reload has reset the static _instance field. As with the previous implementation, MonoBehaviors should not access the singleton during Awake() handlers unless script priorities are assigned.

There may be cases where it's not possible to rely on the user having a global scene to place a singleton component and the ScriptableObject variant won't work because per-frame updates are required. In those cases, a MonoBehavior-based singleton is needed that can survive scene transitions and be created on-demand:

OnDemandMonoBehaviourSingleton.cs
// Copyright 2021 by Hextant Studios. https://HextantStudios.com
// This work is licensed under CC BY 4.0. http://creativecommons.org/licenses/by/4.0/
using UnityEngine;

// A MonoBehaviour-based singleton for use at runtime. It should *not* be placed in
// a scene as it will be created on demand.
// Note: OnEnable() / OnDisable() should be used to register with any global events
// to properly support domain reloads.
public class OnDemandMonoBehaviourSingleton<T> : MonoBehaviour
where T : OnDemandMonoBehaviourSingleton<T>
{
// The singleton instance.
public static T instance => _instance != null ? _instance :
Application.isPlaying ? Initialize() : null;
static T _instance;

// True if the singleton instance has been destroyed. Used to prevent possible
// re-creation of the singleton when exiting.
static bool _destroyed;

// Finds or creates the singleton instance and stores it in _instance. This can
// be called from a derived type to ensure creation of the singleton using the
// [RuntimeInitializeOnLoadMethod] attribute on a static method.
protected static T Initialize()
{
// Prevent re-creation of the singleton during play mode exit.
if( _destroyed ) return null;

// If the instance is already valid, return it. Needed if called from a
// derived class that wishes to ensure the instance is initialized.
if( _instance != null ) return _instance;

// Find the existing instance (across domain reloads).
if( ( _instance = FindObjectOfType<T>() ) != null ) return _instance;

// Create a new GameObject instance to hold the singleton component.
var gameObject = new GameObject( typeof( T ).Name );

// Move the instance to the DontDestroyOnLoad scene to prevent it from
// being destroyed when the current scene is unloaded.
DontDestroyOnLoad( gameObject );

// Create the MonoBehavior component. Awake() will assign _instance.
return gameObject.AddComponent<T>();
}

// Called when the instance is created.
protected virtual void Awake()
{
// Verify there is not more than one instance and assign _instance.
Debug.Assert( _instance == null,
"More than one singleton instance instantiated!", this );
_instance = ( T )this;
}

// Clear the instance field when destroyed and prevent it from being re-created.
protected virtual void OnDestroy()
{
_instance = null;
_destroyed = true;
}

// Called when the singleton is created *or* after a domain reload in the editor.
protected virtual void OnEnable()
{
#if UNITY_EDITOR
UnityEditor.EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
#endif
}

#if UNITY_EDITOR
// Called when entering or exiting play mode.
static void OnPlayModeStateChanged( UnityEditor.PlayModeStateChange stateChange )
{
// Reset static _destroyed field. Required when domain reloads are disabled.
// Note: ExitingPlayMode is called too early.
if( stateChange == UnityEditor.PlayModeStateChange.EnteredEditMode )
{
UnityEditor.EditorApplication.playModeStateChanged -=
OnPlayModeStateChanged;
_destroyed = false;
}
}
#endif
}

This singleton is similar to the ScriptableObject implementation in that it is created on-demand and now has to prevent re-creation of the instance when exiting Play Mode. Unlike the ScriptableObject version, the isPlaying check will still be true when its scene is destroyed, so an additional _destroyed boolean is needed to prevent re-creation. This is checked by the Initialize() method before attempting to find or create an instance.

During normal use, when an instance isn't found, Initialize() will create a new GameObject instance to hold the singleton component and call the DontDestroyOnLoad() method. This moves the object into the special DontDestroyOnLoad scene so that it can survive scene changes. Initialize() then finishes by adding a new instance of the singleton to the game object.

To support the Play Mode option to disable domain reloads, the _destroyed field needs to be reset to false after Play Mode exits. If this is not done, then the next time Play Mode is started the value will remain true, and Initialize() will return null instead of creating a new singleton instance. This is again done using the editor's playModeStateChanged event.

Note that this type of MonoBehavior-based singleton should not be placed in a scene as it will be created by accessing the instance property. To help prevent this, derived singletons can use the [AddComponentMenu("")] attribute with an empty string which prevents it from being shown in Add Component lists although it can still be added to a GameObject via drag-and-drop.

Issues with OnDisable() and OnDestroy()

One important thing to remember regardless of the type of MonoBehaviour-based singleton used is that the order of destruction is not guaranteed when exiting Play Mode or loading a new scene in-game. OnDisable() and OnDestroy() will be called back-to-back per GameObject instance which means that code in either of these handlers needs to check that singleton instances are not null before attempting to access them. (Also keep in mind that OnDisable() and OnEnable() are called each domain reload if the Play Mode setting is set to Recompile and Continue Playing.)

Thread Safety

You might have noticed the lack of thread safety in the above singleton implementations. This is because Unity will already throw an exception if any of its methods (CreateInstance, FindObjectsOfTypeAll, and even isPlaying) are called from outside the main thread. It would be nice if Unity exposed the CurrentThreadIsMainThread() method to make it easier to assert that this is true (it can be accessed via reflection if desired), but since the other methods will catch it, I decided to leave it out as the general rule is to avoid the use of ScriptableObject and MonoBehaviour-derived classes from background threads.

Final Thoughts

Hopefully, this article has been helpful to anyone curious about singleton implementations and their issues in Unity. While they're not the hardest thing to write, implementations that do not take into account domain reloading and proper cleanup can cause bugs that are hard to track down later. Unity could make this process considerably easier by persisting static fields (perhaps only those tagged with the [SerializeField] attribute) and providing a base class for editor plugins that do not require a UI. Maybe that will happen at some point but until then, feel free to ask any questions or suggest improvements to the above singleton implementations. In the next few articles, I'll cover some additional singleton examples, how our global scene loading is handled, and how our scenes are structured to avoid the need for certain singletons.