Seongwon-Choi
Seongwon-Choi

Reputation: 457

how to create a generic singleton class in Unity?

I'm a beginner of Unity.

I had a question while studying.

I refer to the document below.

using UnityEngine;

public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T _instance;

    private static object _lock = new object();

    public static T Instance
    {
        get
        {
            if (applicationIsQuitting) {
                Debug.LogWarning("[Singleton] Instance '"+ typeof(T) +
                    "' already destroyed on application quit." +
                    " Won't create again - returning null.");
                return null;
            }

            lock(_lock)
            {
                if (_instance == null)
                {
                    _instance = (T) FindObjectOfType(typeof(T));

                    if ( FindObjectsOfType(typeof(T)).Length > 1 )
                    {
                        Debug.LogError("[Singleton] Something went really wrong " +
                            " - there should never be more than 1 singleton!" +
                            " Reopening the scene might fix it.");
                        return _instance;
                    }

                    if (_instance == null)
                    {
                        GameObject singleton = new GameObject();
                        _instance = singleton.AddComponent<T>();
                        singleton.name = "(singleton) "+ typeof(T).ToString();

                        DontDestroyOnLoad(singleton);

                        Debug.Log("[Singleton] An instance of " + typeof(T) + 
                            " is needed in the scene, so '" + singleton +
                            "' was created with DontDestroyOnLoad.");
                    } else {
                        Debug.Log("[Singleton] Using instance already created: " +
                            _instance.gameObject.name);
                    }
                }

                return _instance;
            }
        }
    }

    private static bool applicationIsQuitting = false;

    public void OnDestroy () {
        applicationIsQuitting = true;
    }
}

if instance was null, Why GameObject to add to AddComponent ?

and Why use that FindObject function ?

and Why use Singleton in Unity?

I don't Know Singleton Overall flow..

please give to me Code Review..

As a beginner, I do not know much. I need your help.

Please give me your think.

Upvotes: 0

Views: 3558

Answers (2)

Will Lacey
Will Lacey

Reputation: 344

I thought I had an elegant solution to this problem (generic singleton):

public static class Singleton
{
    /************************************************************/
    #region Functions

    public static T Get<T>(
        bool findObjectOfType = false, 
        bool dontDestroyOnLoad = false, 
        bool unparentGameObject = false) where T : MonoBehaviour
    {
        if (findObjectOfType && SingletonInstance<T>.instance == null) 
        {
            TrySet(Object.FindObjectOfType<T>(), dontDestroyOnLoad);
            LogManager.Log($"called Get<{typeof(T)}>() before instance was set; calling FindObjectOfType<{typeof(T)}>");
        }
        return SingletonInstance<T>.instance;
    }

    public static bool TrySet<T>(
        T instance, 
        bool dontDestroyOnLoad = false, 
        bool unparentGameObject = false) where T : MonoBehaviour
    {
        // NOTE: method does not need to be called; BUT if called, FindObjectOfType() is avoided during lazy init
        if (SingletonInstance<T>.instance != null)
        {
            LogManager.Log($"{instance.name} called Set<{typeof(T)}>() when singleton already exists");
            if (!IsSingleton(instance)) 
            {
                LogManager.Log($"there are two different singleton instances, calling DestroyImmediate for {instance.name}");
                Object.DestroyImmediate(instance.gameObject);
            }
            return false;
        }
        else if (instance != null)
        {
            SingletonInstance<T>.instance = instance;
            if (unparentGameObject) instance.transform.SetParent(null);
            if (dontDestroyOnLoad) Object.DontDestroyOnLoad(instance.gameObject);
            return true;
        }
        else
        {
            LogManager.LogError($"called Set<{typeof(T)}>() when given instance is null");
            return false;
        }
    }

    public static bool IsSingleton<T>(T instance) where T : MonoBehaviour
    {
        return ReferenceEquals(SingletonInstance<T>.instance, instance);
    }

    #endregion
    /************************************************************/
    #region Subclasses

    private static class SingletonInstance<T> where T : MonoBehaviour
    {
        public static T instance;
    }

    #endregion
    /************************************************************/
}

where a class that has/is a singleton looks like:

    /************************************************************/
    #region Properties

    public static TestRunner Instance => Singleton.Get<TestRunner>(findObjectOfType: true);

    #endregion
    /************************************************************/
    #region Functions

    private void Awake() => Singleton.TrySet(this, dontDestroyOnLoad);

    #endregion
    /************************************************************/

or if you wanted to use abstraction, you could create an abstract MonoSingleton class that looks like:

public abstract class MonoSingleton<T> : MonoBehaviour where T : MonoBehaviour
{
    /************************************************************/
    #region Fields

    [Header("Singleton Settings")]
    [Tooltip("whether Object.DontDestroyOnLoad() is called on this")]
    [SerializeField] private bool dontDestroyOnLoad;
    [Tooltip("whether this GameObject unparents itself")]
    [SerializeField] private bool unparentGameObject;

    #endregion
    /************************************************************/
    #region Properties

    private T _Instance => this as T;
    public static T Instance => Singleton.Get<T>();

    #endregion
    /************************************************************/
    #region Functions

    protected void Awake() 
    {
        if (Singleton.TrySet(_Instance, dontDestroyOnLoad, unparentGameObject))
        {
            MonoSingleton_Awake();
        }
    }
    
    protected void OnDestroy()
    {
        if (Singleton.IsSingleton(_Instance))
        {
            MonoSingleton_OnDestroy();
        }
    }

    protected void OnEnable() 
    {
        if (Singleton.IsSingleton(_Instance))
        {
            MonoSingleton_OnEnable();
        }
    }
    
    protected void OnDisable() 
    {
        if (Singleton.IsSingleton(_Instance))
        {
            MonoSingleton_OnDisable();
        }
    }
    
    protected virtual void MonoSingleton_Awake() {}
    
    protected virtual void MonoSingleton_OnEnable() {}

    protected virtual void MonoSingleton_OnDisable() {}

    protected virtual void MonoSingleton_OnDestroy() {}

    #endregion
    /************************************************************/
}

where the final implementation of a MonoSingleton<T> would look like:

public class NewMonoSingleton : MonoSingleton<NewMonoSingleton>
{
    
}

where you would override Awake, OnDestroy, OnEnable, OnDisable if you need to. Otherwise, the script is pretty light.

Upvotes: 0

Programmer
Programmer

Reputation: 125275

if instance was null, Why GameObject to add to AddComponent ?

If the instance of the script you want to create is null if (_instance == null):

1.Create new GameObject

GameObject singleton = new GameObject();

2.Create new instance of that script and attach it to the GameObject that is created above. In Unity, a component must be attached to a GameObject. The AddComponent function is used to attach a component to a GameObject.

_instance = singleton.AddComponent<T>();

Why use that FindObject function ?

If the instance of the script you want to create is null if (_instance == null), check if that script instance already exist in the scene. The FindObjectOfType function is used to find this type of script only. Let's say that we have a script called SceneLoader and we pass SceneLoader to the Singleton class, it will check if an instance of SceneLoader is already in the scene and return that instance of it. It will return null if it does not exist.

Why use Singleton in Unity?

You use it when you only want to have one instance of a type of script in the scene. Also, with DontDestroyOnLoad, it means that this instance will still be there even when next scene is loaded. It will not be destroyed like other scripts.

please give to me Code Review

You can ask for code improvement on the codereview site. If you are new to Unity you can find Unity project tutorials on their site that can get you started easily here.

Upvotes: 4

Related Questions