The term “singleton” comes up rather often once you’re past the first few hurdles in coding for Unity, and upon researching it, you likely have seen anything from it being a really bad thing to it being that one solution you absolutely need.
It seems that the diversity in opinions about Singletons stems a lot from varying knowledge, so this article is intended to clarify what a Singleton really is, what it isn’t, and whether it’s good or bad.
The singleton pattern
The term “singleton” was coined by a book called Design Patterns by the “Gang of Four”. It’s a very influential book that explains how code doesn’t have to be written entirely from scratch, but can use specific patterns to implement solutions for recurring problems. When you build a house, you don’t make a hole in the wall and put some glass in it – you build a window. If you use patterns like these, people don’t have to understand your code from the ground up, but they can see the bigger picture you created from parts they already know.
One of the patterns that are described in the book is the singleton pattern. It can be described as a class that will never have more than one instance.
This can be implemented in multiple ways.
A generic implementation
A general (not Unity-related) singleton class could look like this:
public class MySingleton { public static MySingleton Instance { get; private set; } // Static constructor that creates an instance. static MySingleton() { Instance = new MySingleton(); } // Private constructor so noone else can make another instance. private MySingleton() { } public void AMethod() { } }
This particular class works almost exactly the same as this static class:
public static class MyStaticClass { public static void AMethod() { } }
The static class is much shorter without offering much less.
What the above singleton class can do is:
- It allows for easily resetting its state (if it ever gets one) by just creating a new object and assigning it to
instance
. - It has the potential to utilize polymorphism to allow swapping out behaviors at runtime.
However, both things can be implemented without implementing a singleton – the fact that only one instance of this class exists at any given time is not required for a strategy pattern or for resetting data.
On top of this particular singleton’s very limited usefulness, both of these classes equally introduce global variables, which are not rarely criticized.
All of this makes it easy to see why singletons often don’t have the best reputation.
However, there are more implementations.
Awake singletons – the most common implementation in Unity
In the Unity context, singletons usually occur as a different implementation:
public class MySingleton : MonoBehaviour { public static MySingleton instance { get; private set; } private void Awake() { if (instance) { Destroy(gameObject); return; } instance = this; } public void AMethod() { } }
Let’s name this pattern the “awake singleton”.
The main difference to the above implementation is that the class inherits from MonoBehaviour
. This means that we cannot write our own (private) constructor, and thus cannot prevent multiple instances from being created. So technically, there can be multiple instances at any given time, but as soon as Awake
is called, all but one will destroy themselves and their GameObject.
This pattern, since it qualifies as a singleton, is often frowned upon, but it should be noted that the context is different. Unlike the generic implementation, which doesn’t offer any (worthwhile) benefits over a static class, we are now looking at a MonoBehaviour
instance. This special Unity class can do a few things that a static class would not be able to do:
- Receive
MonoBehaviour
events.
If you want to react to events likeUpdate
orOnApplicationFocus
, you need an object instance for that. - Have serialized fields.
If you want to set up references or other values in the editor, you cannot do that with a static class, as Unity doesn’t serialize them. - Use other components.
Imagine an AudioSource responsible for background music – it would make sense to put a singleton script next to it to handle music requests and to avoid having multiple background music players. - Run coroutines.
Coroutines, especially vanilla ones, have been getting less attractive over the years. Many developers prefer UniTask, UniRx or MEC over Unity’s own coroutines.
However, coroutines are still easy to use, and if you’re writing a code package, you might want to avoid having any of these third-party packages as a dependency.
It stands to argue that most of these points are really the results of flaws in Unity’s design. In fact, Unity has gotten some improvements for 1. over the years – you can use PlayerLoops to avoid an “Update
worker”; and instead of OnApplicationFocus
, you can subscribe to Application.focusChanged
. It might still be easier to just have a MonoBehaviour
with an Update
implementation than to get into PlayerLoops.
Given the growing list of cleaner alternatives, the stronger points are definitely 2 and 3. They require an object instance because that’s how Unity works. So the overall issue with general singletons not having worthwhile advantages over static classes really doesn’t apply to this specific implementation. There are specific advantages to this pattern variant in Unity.
However, there is a new set of issues that comes with it.
Most importantly, it is outside of the class’s control whether there’s an instance of it. Other systems, like a scene or another piece of code, are responsible for the instantiation, and have the option to destroy the instance. Thus, the class itself cannot guarantee that deserialized values are present, or that its AudioSource exists.
This singleton variant is very often used to declare globally available services, and they’re often called SomethingManager
. But having an object (that probably plays a key role in the program) relying on external factors like scene setup or different code parts is a horrible idea. It’s generally smart to write code in a way that is as independent as possible from whatever happens in the editor. If a class is supposed to globally provide a service, that service should always be available, regardless of how the current scene is set up.
This is why there is another variant I would like to recommend here.
Nested worker singletons – a different Unity implementation
If the issue with the previous variant is that the code should not be dependent on mandatory setup in the editor, fixing it means making the code work at all times, like a static class.
The following pattern, which I like to call the “nested worker singleton”, is how you can achieve that:
public static class MyService { private class Worker : MonoBehaviour { // ... } private static Worker worker; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] private static void Initialize() { var gameObject = new GameObject("My Service Worker"); worker = gameObject.AddComponent<Worker>(); Object.DontDestroyOnLoad(gameObject); gameObject.hideFlags = HideFlags.HideAndDontSave; } public static void AMethod() { worker.//... } }
Here’s a list of the elements of this pattern, and what they do:
- There is a nested MonoBehaviour class inside the static class.
This prevents theMonoBehaviour
from being instantiated in the editor – it can neither be dragged onto a GameObject, nor be added with the AddComponent menu. - The
Initialize
method is called once when the game starts, thanks to its attribute.
This makes initialization completely independent from any setup done (or not done) in the editor.
The attribute’s parameter (BeforeSceneLoad
) makes sure that the worker is initialized before any objects in a loaded scene could require its services. - The
Worker
class is instantiated once, which makes it a singleton. - The GameObject created for the
Worker
instance is hidden and flagged withDontDestroyOnLoad
, which protects the worker from being destroyed unless this code wants it to.
This pattern gives us a singleton Worker
instance, which
- as a
MonoBehaviour
, is able to serve an actual purpose, so the “could as well be static” issue doesn’t apply. - doesn’t rely on being set up in the editor, because the code takes care of the instantiation itself.
Comparing the options
Although the nested worker singleton is very useful, the point here is not to suggest that it is better than the “awake singleton”. Which of the two makes more sense to use depends on the context.
For example, a background music player, network service or globally available coroutine runner should not rely on being set up in the editor, so the nested worker class makes more sense.
However, there are also cases where objects registering themselves in Awake
makes sense. Those cases are generally related to scene design. Imagine a tower defense game where the enemies’ target is somewhere in your scene. It would make sense to make the target’s position statically available (albeit only for the spawning enemies). But doesn’t make sense to instantiate it via code, since it should be positioned in the editor by a level designer. In this case, an awake singleton component is absolutely valid.
Awake singletons should always be used in a way where a missing instance doesn’t break other systems.
Serialized fields/master scenes
One capability that is lost when choosing the nested worker implementation over the awake singleton is serialized fields. If we implement our background music player with a nested worker singleton, how would we define the default AudioClip to play, or the AudioSource’s mixer group?
This question is what drives some developers to go for a “setup scene” or “master scene”. That is a scene that contains a bunch of awake singletons and resides at the top of the build list to initialize everything when the game starts.
This approach can actually be made to work pretty smoothly even in the editor, by adding an editor script that quickly loads the master scene whenever play mode is entered. This makes sure that all the globally desired singletons are available regardless of the scene that was started.
The main reason I still dislike this approach is that I generally want to avoid “do not touch” situations in the editor. If someone is able to break your code by editing a (master) scene in the editor, that code just isn’t very robust.
That’s why I implemented a different solution called “ModuleSettings” in my Asset Store package Soda. In short, it works like this:
With it, you can define serialized fields in objects that are completely hidden in the editor. They don’t show up in any scene or even the assets. Instead, you can assign values in the project settings window, where you cannot accidentally delete the ModuleSettings object or add a second one. The object is retrieved through ModuleSettings.Get<T>()
, so there are also no strings involved in loading the values.
I very frequently use this system. But if you don’t own Soda or have anything similar at your disposal, master scenes are really not a bad solution.
Conclusion
The term “singleton” is loaded with all kinds of preconceptions. I believe that people who criticize Unity developers for using singletons often do so because they’ve learned that “singleton = bad”, and haven’t re-evaluated this stance for the Unity context.
The “awake singleton” pattern that is often used is actually a very good option for some use cases, and still not wrong in many others. Unity’s design causes this pattern to offer some specific advantages that a generic singleton doesn’t provide.
However, awake singletons are very often seen in a context where they cause a globally available service to rely on setup done in the editor (scenes or ScriptableObjects) in order to work at all. In many cases, this makes the project more error prone. A simple static class could be a better alternative if no MonoBehaviour
is needed. Otherwise, the nested worker singleton might be a much better option.
In general, the singleton pattern in Unity has a greater lesson to teach us:
Never use or avoid something based on preconceptions. Instead, evaluate the pros and cons at hand.
Make sure you know the reasons behind popular opinions, check whether they even apply for you, and compare the alternatives by evaluating them the same way, with as little bias as possible.