Nuanced Questions on Singleton and Game Manager in Unity

I would like to ask some nuanced questions about Game Managers specifically within Unity. I have a formal Computer Science degree (but 20 years ago) and have written SW in Java (also 20 years ago). Minimal C# experience. I am familiar with CS theory, thread safety, and basic familiarity with design patterns like static classes, singletons and service locators.

In short - I don’t want to debate design patterns generically but rather I want to understand what’s different about C# and Unity.

I am new to Unity and C# and so I don’t know what to do with all the variance in everyone’s approaches to game managers. (Singletons seemed easy in Java). With my questions below, I am trying to develop a nuanced understanding of why the advice is so divergent.

More context: The game I am trying to write will have multiple scenes. The game will need save/load capability. At some point it will be multiplayer.

  • Should my game managers extend from MonoBehaviour? What are the reasons for or against?

  • RELATED: What are reasons for or against having the game manager attached to a scene hierarchy object?

  • I don’t understand why some sample code for game managers uses generics. What problems do generics solve specific to a game manager?

  • I thought I read that you can’t define your own constructors if you extend from MonoBehavior, yet I see sample code with constructors defined and even these constructors being called. What am I misunderstanding?

  • It seems that DontDestroyOnLoad is necessary to preserve the singleton across multiple scenes but I saw a comment on this thread arguing against it. Is the person basically saying that if you don’t extend MonoBehaviour (which ties to question 1) you don’t need to worry about DontDestroyOnLoad? Reference: [Help] How do you set up a GameManager?

  • I’ve seen some code destroying GameObjects that already exist to preserve the singleton-ness. Sometimes it is just destroying “this”. Sometimes the code even keeps track of a list of instances to destroy. Surprised to see all this exception handling. Is this an issue only if you extend from MonoBehaviour? Reference: c# - In Unity, how do I correctly implement the singleton pattern? - Game Development Stack Exchange

  • Surprised to see so little code and discussion on protecting against serialization, cloning, reflection. What am I missing?

The core of Unity is that you have scenes, these are filled with game objects, of which have any number of components on them (including always a transform component). You also have project folders full of assets.

Game objects, components, and project assets, all have a root-base class of UnityEngine.Object. I like to think of anything that is a unity object as ‘real’ or ‘physical’. They’re tangible, you can make them and see them in your project assets or scene, and most importantly, they have a twin living in Unity’s C++ side (making them managed objects).

Honestly this is just because every game is different, and every one has a different preference. I wouldn’t worry too much about everyone’s different approaches.

I’ll try answer your questions.

This is down to preference, and also, partly down to developer knowledge. Most folks use a monobehaviour manager because it’s A: The only way they know how, and B: the easiest approach.

But if you know what you’re doing, you can also use static classes and sometimes even scriptable objects to make scene independent managers.

This is usually just a case of having a reusable generic base-class to quickly make managers. Some people like this, some say it’s bad practice. I’m impartial. I have my own scriptable object singleton base class because it’s convenient.

In some instances you can use constructors with monobehaviours. I believe parameterless constructors are called when the object comes into existence, but I’d consider it bad practice (we have Awake/Start/OnEnable for this reason). Don’t worry about this while you’re learning.

This goes back to my pre-amble about Unity objects. Monobehaviours have to live in scenes, so for the manager to be alive an instance needs to live somewhere in a currently loaded scene. DontDestroyOnLoad just moves game objects to a special scene that doesn’t ever get unloaded.

Static classes and scriptable objects don’t live in scenes, this, don’t need DontDestroyOnLoad.

This is again, just personal preference and down to implementation details. I wouldn’t worry about this while you’re new.

You can’t serialize out anything inheriting from UnityEngine.Object. Any UnityEngine.Object can be instantiated on a whim, and who would ever bother with the last one? Seems like a fools errand.

Anyway, bottom line is there is no one true singleton pattern. It will vary on a case by case basis, and depend on your specific implementation details and your personal preferences.

So don’t worry too much, honestly. There is seldom, if ever, only one ‘correct’ way to do things.

6 Likes

That is a common misconception / oversimplification repeated often in the Unity community.

You can use a parameterless constructor just fine. For example:

public sealed class Enemy : MonoBehaviour
{
    public readonly IReadOnlyList<IInteractable> interactables;

    public Enemy()
    {
        interactables = BuildInteractables();
    }

“Components can’t have constructors, always use Awake/Start instead” is a really good rule of thumb, but not actually true.

The (very valid) practical reasons why this rule is repeated a lot are:

  • The constructor can be called outside the main thread.

  • The constructor is called before the deserialization process.

  • The constructor can be called when a scene that is being loaded is only half-finished loading.

  • When a prefab is instantiated, a constructor can be executed several times, due to some temporary instances being created during the deserialization process.

This adds a lot of limitations to when using a constructor would be possible:

  • You can’t call any non-thread safe methods.
  • You can’t use FindObjectOfType or GetComponent.
  • You can’t know what values serialized fields will have after deserialization has finished, and any values you assign to them will get overridden during deserialization.

With these limitations in mind, they can still be very useful for using methods to initialize readonly fields.

Many of these limitations also don’t apply if a component is guaranteed to only be created using AddComponent, not Instantiate or SceneManager.LoadScene.

1 Like

It’s a commonly used pattern to provide the type of the derived manager type to the base type, allowing the base type to define members that use the concrete manager type.

public abstract class Manager<TManager> : MonoBehaviour where TManager : Manager<TManager>
{
    public static TManager Instance { get; private set; }

    private void Awake() => Instance = this;
}

public sealed class PlayerManager : Manager<PlayerManager> { }

public class Example
{
    public void NoCastingNeeded()
    {
        PlayerManager playerManager = PlayerManager.Instance;
    }
}
3 Likes

Some Benefits:

  • Being able to assign Object references to serialized fields by drag-and-dropping in the Inspector is a really powerful tool.
  • Being able to easily examine the state of serialized fields of a manager in the editor in play mode is really useful for debugging.
  • Being able to locate your managers in the scene can help with their discoverability.

Some Cons:

  • When exiting play mode / quitting the application, your managers can get destroyed before your other objects do, leading into missing reference exceptions.
  • You can’t use constructor injection to pass arguments to your managers.
  • Controlling the initialization order of your managers can be more awkward than if they were built wholly in code.
1 Like
  1. The need to destroy existing “singleton” can rise from the combination of: wanting to preserve game manager and it’s state across scenes (with DontDestroyOnLoad), wanting to allow directly starting and playing a specific scene from editor (convenient during development). This can result in setup where you have an instance of DontDestroyOnLoad Game manager in each scene thus allowing to directly run any of them, and the duplication during scene transition getting resolved by destroying one of them (depending on which state you want to keep). There are other other ways to satisfy both problems, but this is what some people do.

Few more factors for choosing between MonoBehavior based game manager or against it:

  • MonoBehavior base game manager can more easily react to certain unity callbacks like Update, FixedUpdate. If you have a non monobehavior based manager, you will likely still need some kind of glue layer which reacts to Unity stuff and requests game manager simulation to advance.

  • Game manager might depend on certain Unity components which control some Unity subsystems, having all that preconfigured on single Game object can be more convenient than initializing all that from code. It also helps ensuring that the lifetime of those extra components and game manager is the same.

  • MonoBehavior based manager can more easily reference prefabs and Scriptable objects without having to manually manage loading/unloading of resources or delegating resource management to some Unity object.

  • Integration with Unity hot reload functionality.

  • Having a non MonoBehavior based game manager can make it easier to simulate your core game logic without most of Unity stuff at the pace of your choice for the purpose of: unit testing, AI predicting future state while choosing moves, server side logic, analyzing the game content and difficulty, fast forward functionality.

  • Non MonoBehavior based approach can make it clearer what the state of game is, keeping it separate from Game object hierarchy and serializing it.

1 Like

Do note that C# also supports static constructors (a different flavor of a ctor altogether), and there might be cases where this is desirable, due to issues of order execution with MonoBehaviours. I must add I never had to use this feature, but if it worked, that’s the only ctor I would ever use in a MB.

Static ctors are called before the runtime is up, and thus before Unity gets to initialize its internals, and put things into motion, which lets you gain a full round of pre-runtime initialization, maybe fill up your static fields, look ups etc.

tl;dr I have never used an ordinary ctor in an MB, and even though you can, I’d advise against it. There are probably loads of good arguments and explanations above.

To the extent that you can, try to avoid game manager kind of objects, especially ones that need to be in a scene in order for the game to work.

There’s a bunch of reasons for this:

  • Testing gets harder, as in order to make a valid scene you have to remember to include a bunch of objects
  • If you want to load several scenes at once (a powerful tool!), you have to make decisions about which copy of the manager object to use
  • Initialization order of manager objects in relation to each other can cause hard-to-track ordering bugs.
  • In order to understand how the game works, you have to understand a bunch of manager objects.

A good rule of thumb is that you should be able to take your player character prefab, place it in an empty scene, and then be able to enter play mode and play the game. If it’s a 2D platformer, you might need a platform, etc, sure, but if you need to put in a million objects to have things be right, development slows down, and the game is harder to understand.

Making the game work without manager singletons can be hard work, as you have to think hard about how things know about other things instead of just throwing data and messages into a manager somewhere, but it’ll for sure be worth it in the end.

What do you mean by “protection” here? Things in Unity are fundamentally serializable, otherwise building levels with MonoBehaviours just wouldn’t work. Cloning is implemented through serialization (you can pretty much Instantiate anything), so it’s not something you can or should protect against.

1 Like

That’s where my almighty “Game Globals” prefab comes in :smile:

1 Like

First off - wow - thank you for all the in depth and highly nuanced responses. I will read them and reply back to your follow up questions back to me. My game progressed to a point where something like a game manager became necessary. I ran into some bugs, decided to look at forums and youtube for answers, then ran into weirder bugs and then I got confused.

I love the nuanced responses in this thread so I just wanted to thank all of you and then go and read everything in depth.

Let me rephrase this question as I wrote it poorly. What should I consider to make sure serialization doesn’t break my game manager. And I want to make sure I don’t accidentally clone my game manager object through some stupid bug. This question is more about thinking ahead to future features of network play or save/load.

Yup understood. My question here is coming from trying to incorporate some sample code from tutorials or forums, and then running into unexpected bugs because I want to do something unique. After all my goal is not to copy tutorials but to build something unique. After struggling with bugs when I pull in sample, I’m trying to understand what’s happening behind the scenes (no pun intended). I’d rather not use sample code, but I don’t know enough yet to just directly do what I want to do.

Thanks - I haven’t gotten to scriptable objects yet in my learning. Will keep this in mind.

I stopped coding in ~2004 before generics took off so apologize for what may be an odd question. I remember doing base classes without them but thought they were convenient for lists and iterators. All a bit new to me. Is this (generic base-class) just basic good practice then?

Hmm OK… I think I’ll have to come back to this one once I get to a point of having to save and load state.

Referring to reflection here yeah. Not sure either:) But I swear I ran into some Unity code posted on a forum that looked like it used reflection. Again new to C# and Unity so I don’t even know what I’m reading sometimes.

Managers and MomoB’s: The only way unity runs code is through an Update() function attached to a gameObject currently in the scene. Only Component sub-types can be on objects, and only Monobehaviour components automatically have their Update()'s run. So … we may as well have the main-entry point Update() in the game manager, which means GM’s must inherit from MonoB. To get them in the scene you just stick them on some empty gameObject.

DontDestroyOnLoad: Old unity only allowed 1 scene at a time. Loading a scene deleted the current one, which deleted the game manager. DontDestroyOnLoad solved that by allowed objects to be permanent. The standard process was to put the game manager in the opening scene, have it call DDoL on itself, and load the next scene (it would then probably search for specific scene objects for it to call). When Unity added the ability to load multiple scenes at once (loadAdditive instead of load), DDoL became pointless. We still have a scene with the game manager and friends, but now we load (and remove) everything else additively (which is more code, but worth it).

Constructors: (obviously) Monobehaviors are created and initialized by Unity. Awake() and Start() are both constructors, already. There are two of them to solve synch issues – when a scene is loaded, all Awakes() are called first, then all Starts(), which allows you to move “I depend on these other objects having initted themselves” code from Awake() into Start(). It’s not perfect, but solves most problems (if you need more complex initialization, have the game manager do it). Unity also automatically loads everything set through the Inspector (before Awake(), I think). The normal way to create a Monobehaviour mid-scene is Instantiate-ing a prefab containing one (which runs Awake() and so on). All of this means that the no-constructors advice is saying: “scripts” don’t need constructors, you won’t new them and you’re working against the framework if you try.

Destroying excess singletons: this is an old-style Unity pattern/hack. If you didn’t want to use the 1-time starting scene trick and you wanted a singleton on a gameObject, you could put it in a scene the game come to quickly, but also might repeatedly come back to. To avoid multiple copies, each new instance of that singleton (automatically created when the scene was reloaded) used a static to check “is there already a copy?” and deleted itself (otherwise it set the static to point to itself and called DDonLoad). In other words, it was a way to idiot-proof them. The trick still works, but today people either use additive scenes, or use ScriptableObjects for singletons (which allow use of the Inspector to make nice links; but are Assets, which exist globabally so don’ t need to be put into a scene so they can be loaded).

1 Like

That hasn’t been the case for quite a while - PlayerLoop shipped in 2019.

And while that doesn’t have great documentation, it allows you to very effectively just stick in a delegate to get called every frame before/after Update, LaterUpdate, or even FixedUpdate.

If you want a wrapper for that without hassle or strange side-effects, I’ve got one available.

Code sample:

public static class MyCustomSystem {

   [RuntimeInitializeOnLoadMethod]
   private static void Initialize() {
       PlayerLoopInterface.InsertSystemBefore(typeof(MyCustomSystem), UpdateSystem, typeof(UnityEngine.PlayerLoop.Update.ScriptRunBehaviourUpdate));
   }

   private static void UpdateSystem() {
       Debug.Log("I get called once per frame!");
   }
}

This is a great alternative to a MonoBehaviour in the scene if you want code to run every frame without the overhead and hassle of a GameObject.

3 Likes

Yeah, this is an issue related to having one’s singletons derive from MonoBehaviour.

With plain old C# object it is really easy to implement the singleton pattern in a fool-proof manner:

public class Manager
{
    public static Manager Instance { get; } = new Manager();
  
    private Manager(){}
}

In Unity making sure that only exactly one instance of a component can exist is a more difficult problem to solve, because components can be attached to any scene or prefab asset in edit mode before the game is even launched. Scanning all the files in the entire project for duplicates each time a prefab containing a singleton is reimported would be very slow.

This is why one of the most common ways to implement the singleton pattern in Unity is to check in Awake if an instance already exists, and if so, destroy the excess instance.

public class Manager : MonoBehaviour
{
    public static Manager Instance { get; private set;}
  
    private void Awake()
    {
        if(Instance != null && Instance != this)
        {
            Destroy(this);
            Debug.LogError("There can only be one.");
            return;
        }

        Instance = this;
    }
}

Another approach is to not actually implement the singleton pattern at all, but just make sure to only load a single instance in your code and call it a day. Then there is no need to have logic for detecting and destroying excess instances.

(edit: Removed readonly modifier from Instance property of second Manager class)

1 Like

Yes. Thanks for calling this out. Helpful to me and other readers who are wondering why there is so much “extra” code.

Ok. I’m trying to figure out what’s really happening here.

  1. I thought “readonly” means it can only be modified in declaration or constructor. Does this mean the constructor of the object calls Awake() during its construction phase?
  2. why no lock? I imagine there’s some implicit protection if I do things right but don’t know what those things are.
  3. should I and where would I add DontDestroyOnLoad in here?

How does one make sure only a single instance is loaded? I don’t understand what Unity does behind the scenes to know what I can or can’t trust.

Sorry, that was just a silly mistake I made! There should have been no readonly modifier, and Awake is not called from within a constructor.

Yeah this implementation has not been created with thread-safety in mind. Most of the public APIs in Unity are not thread safe, and writing multi-threaded code in Unity is not that common place, and as such, many singleton implementations also are not thread-safe.

Even in a single-threaded application Manager.Instance can be null if any client component is loaded before the Manager component is, so this implementation also relies on the manager being given an early execution order, being loaded as part of a preloading scene before the main scene where all the clients components are, or something to that effect.

If you put it into a scene like “Managers” and load it using SceneManager.LoadScene("Managers", LoadSceneMode.Additive), or put it into the first scene of your game and load the next scene additively, then you don’t need to do that.

If you use DontDestroyOnLoad then Unity basically does this for you automatically, loading a scene called DontDestroyOnLoad additively and moving the manager into it.

[DefaultExecutionOrder(-1000)] // Helps ensure the manager initializers before other components
public class Manager : MonoBehaviour
{
    public static Manager Instance { get; private set;}

    private void Awake()
    {
        if(Instance != null && Instance != this)
        {
            Destroy(this);
            Debug.LogError("There can only be one.");
            return;
        }

        Instance = this;
        DontDestroyOnLoad(gameObject); // Makes the manager survive SceneManager.LoadScene(string)
    }
}

Using a preloading scene is an easy way to achieve that. If you put all your managers into the first scene, and then load the next scene additively, you’ll end up with one instance of all the managers.

If you want to be able to launch the game from any scene, you can also add some code to handle loading the scene containing your managers on-the-fly whenever you enter play mode.

using UnityEngine;
using UnityEngine.SceneManagement;

public sealed class ManagerSceneLoader
{
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    private static void OnGameLaunching() => SceneManager.LoadScene("Managers", LoadSceneMode.Additive);
}
1 Like

Thanks so much. This is very helpful context. I am currently just manually loading scenes and starting things through the editor so I haven’t encountered the scene loading stuff you mention. This is a handy thread to reference as soon as I get there. I can now understand forum posts and sample code that I couldn’t just yesterday.

I’m curious about this part below.

I started experimenting with having my game manager pass events on state changes and I certainly created a bug. I thought it might be thread safety related and found another route without event passing. What are the most common things people do in Unity that end up creating thread safety issues?

1 Like

In standard Unity you don’t have to think about race conditions or thread safety, because every “chunk” runs completely before switching. This applies to Starts(), Updates(), OnCollisions() and even coroutines (which are the only things which can sleep themselves). Put another way, each frame Unity runs everyone’s Update(), but not all-at-once. It runs them 1-at-a-time, not going to the next until the previous returns. A fun side-effect of this is that if you get an infinite loop anywhere, the whole thing freezes – there’s no time slice where Unity shelves it and moves onto the next.

That might seem bad, but Unity’s market was small indie games, and game designer/programmers aren’t going to learn about concurrency, not when other game engines don’t make them learn it.

1 Like

I would go a step further and say that it’s good that Unity’s update loop is single-threaded. It’d be an absolute nightmare if you had multiple threads messing with objects in unpredictable orders!

You have to explicitly ask for concurrency (e.g. by using the Job system) in Unity.