Lam Cao's Blog

A Journey in Game Dev

A singleton utility for gameObjects

I use a fair number of singletons in my game projects. And I think I have written something similar the code block below for too many times now. So have you, probably.

void Awake() 
{
    if (instance == null)       
        instance = this;
    else if (instance != this)  
        Destroy(gameObject);
    DontDestroyOnLoad(gameObject);
}

Getting tired of copy&pasting that code snippet over and over, I want to archive the same thing in just one line of code. When someone looks at my code, I want them to recognize the singleton pattern in less than half a second – “Oh hell, singleton again!”

I did a bit of research and found some interesting solutions and examples online except all of them use inheritance, interface or a mix of both. There is nothing wrong with those solutions at all. They aren’t just not exactly what I want. However, some approaches I found really inspired me. I picked some of their ideas and put all together in my own implementation. Here are my requirements:

  • Support Get<T>() that can give me the singleton instance and Register<T>() that can replace the code block above.
  • Manage singletons centrally.
  • Be able to enforce singleton at any time or outside of Awake and Start easily.
  • No interface, inheritance or anything similar – I just don’t like them this time.
  • No need to use a gameobject on scene – just no!

Implementation

I use a static class that has a dictionary of the references of singletons being active in the game. Only one object of a given type can be added/registered, which is exactly what we want!

public static class SingletonUtility 
{
    private static Dictionary<Type, MonoBehaviour> singletons = new Dictionary<Type, MonoBehaviour>()
}

Get is straightforward. If a MonoBehavior object of the given type has been registered, returns it, null otherwise. Later I added the option to search for objects if none has been registered, mostly for non-standard use cases. It can be found in the source code at the end of this post.

public static T Get<T>() where T : MonoBehaviour 
{
    Type type = typeof(T);
    return Get(type) as T;
}
public static MonoBehaviour Get(Type type) 
{
    MonoBehaviour instance;
    //instance found, not null or destroyed, return it
    if (singletons.TryGetValue(type, out instance) && instance)
        return instance;
    Validate(type); //instance not found at this point, remove ref if needed.
    return null;
}

Register is a bit trickier. I want to support at least 2 ways to register a singleton: Register<T>() (type is provided) and Register(MonoBehavior instance) (an instance is provided). The second option is easy enough. Simply search for the type in dictionary, destroy if something of the same type exists, otherwise add the instance as a new singleton. The other way is a bit more expensive and less predictable. With only a type given, we must search for objects of that type on the scene, which can be arbitrary complex!

public static void Register<T>(bool dontDestroyOnLoad = false) where T : MonoBehaviour 
{
    T instance = Get<T>();
    T[] ins = GameObject.FindObjectsOfType<T>();
    //singleton not registered
    if (!instance && ins.Length >= 1) 
    {
        instance = ins[0];
        Register(ins[0], dontDestroyOnLoad);
    }
    //delete extra 
    if (instance && ins.Length > 1) 
    {
        foreach (var i in ins) 
        {
            if (i != instance)
                GameObject.Destroy(i.gameObject);
        }
    }
    else if (ins.Length == 0)
        Debug.LogWarning($"Type {typeof(T)} cannot be found on scene. Make sure there is an object before register.");
}
public static void Register(MonoBehaviour instance, bool dontDestroyOnLoad = false) 
{
    Type type = instance.GetType();
    if (HasInstance(type)) //check if another instance already exists
        GameObject.Destroy(instance.gameObject);
    else 
    {
        singletons.Add(type, instance);
        if (dontDestroyOnLoad)
            GameObject.DontDestroyOnLoad(instance);
    }
}

How to use

Ok I got Get and Registerdown. Now it’s time to use them! The Awake() function at the beginning now needs only 1 line to do the same thing. There is no need to sacrifice inheritance or mess around with interfaces. It works just the way it did before except only 1 line is needed.

static MonoExample instance => SingletonUtility.Get<MonoExample>();
void Awake() 
{
    SingletonUtility.Register<MonoExample>(true);
}

To be fair that’s quite a bit of work to save several lines of code, but I do think it will be all worth it in long-term especially when I can certainly carry this over to other projects. If you are maintaining some sort of library for your games, this may be a good addition. If you’re not maintaining a library, hopefully, this will inspire you to start doing so.

Full source code: SingletonUtility.cs

Leave a Reply

Your email address will not be published. Required fields are marked *