高階 Unity Singleton

此示例將 Internet 上的 MonoBehaviour 單例的多個變體組合成一個,並允許你根據全域性靜態欄位更改其行為。

此示例使用 Unity 5 進行測試。要使用此單例,你需要做的就是按如下方式擴充套件它:public class MySingleton : Singleton<MySingleton> {}。你可能還需要覆蓋 AwakeSingleton 來使用它而不是通常的 Awake。要進一步調整,請更改靜態欄位的預設值,如下所述。

  1. 此實現使用 DisallowMultipleComponent 屬性為每個 GameObject 保留一個例項。
  2. 這個類是抽象的,只能擴充套件。它還包含一個需要重寫的虛擬方法 AwakeSingleton,而不是實現普通的 Awake
  3. 此實現是執行緒安全的。
  4. 這個單例已經過優化。通過使用 instantiated 標誌而不是例項 null 檢查,我們避免了 Unity 實現 == 運算子所帶來的開銷。 ( 瞭解更多
  5. 當它即將被 Unity 銷燬時,此實現不允許對單例例項的任何呼叫。
  6. 這個單例有以下選擇:
  • FindInactive:是否查詢附加到非活動 GameObject 的相同型別元件的其他例項。
  • Persist:是否在場景之間保持元件存活。
  • DestroyOthers:是否要銷燬同型別的任何其他元件並且只保留一個。
  • Lazy:是否動態設定單例例項(在 Awake 中)或僅按需設定(當呼叫 getter 時)。
using UnityEngine;

[DisallowMultipleComponent]
public abstract class Singleton<T> : MonoBehaviour where T : Singleton<T>
{
    private static volatile T instance;
    // thread safety
    private static object _lock = new object();
    public static bool FindInactive = true;
    // Whether or not this object should persist when loading new scenes. Should be set in Init().
    public static bool Persist;
    // Whether or not destory other singleton instances if any. Should be set in Init().
    public static bool DestroyOthers = true;
    // instead of heavy comparision (instance != null)
    // http://blogs.unity3d.com/2014/05/16/custom-operator-should-we-keep-it/
    private static bool instantiated;

    private static bool applicationIsQuitting;

    public static bool Lazy;

    public static T Instance
    {
        get
        {
            if (applicationIsQuitting)
            {
                Debug.LogWarningFormat("[Singleton] Instance '{0}' already destroyed on application quit. Won't create again - returning null.", typeof(T));
                return null;
            }
            lock (_lock)
            {
                if (!instantiated)
                {
                    Object[] objects;
                    if (FindInactive) { objects = Resources.FindObjectsOfTypeAll(typeof(T)); }
                    else { objects = FindObjectsOfType(typeof(T)); }
                    if (objects == null || objects.Length < 1)
                    {
                        GameObject singleton = new GameObject();
                        singleton.name = string.Format("{0} [Singleton]", typeof(T));
                        Instance = singleton.AddComponent<T>();
                        Debug.LogWarningFormat("[Singleton] An Instance of '{0}' is needed in the scene, so '{1}' was created{2}", typeof(T), singleton.name, Persist ? " with DontDestoryOnLoad." : ".");
                    }
                    else if (objects.Length >= 1)
                    {
                        Instance = objects[0] as T;
                        if (objects.Length > 1)
                        {
                            Debug.LogWarningFormat("[Singleton] {0} instances of '{1}'!", objects.Length, typeof(T));
                            if (DestroyOthers)
                            {
                                for (int i = 1; i < objects.Length; i++)
                                {
                                    Debug.LogWarningFormat("[Singleton] Deleting extra '{0}' instance attached to '{1}'", typeof(T), objects[i].name);
                                    Destroy(objects[i]);
                                }
                            }
                        }
                        return instance;
                    }
                }
                return instance;
            }
        }
        protected set
        {
            instance = value;
            instantiated = true;
            instance.AwakeSingleton();
            if (Persist) { DontDestroyOnLoad(instance.gameObject); }
        }
    }

    // if Lazy = false and gameObject is active this will set instance
    // unless instance was called by another Awake method
    private void Awake()
    {
        if (Lazy) { return; }
        lock (_lock)
        {
            if (!instantiated)
            {
                Instance = this as T;
            }
            else if (DestroyOthers && Instance.GetInstanceID() != GetInstanceID())
            {
                Debug.LogWarningFormat("[Singleton] Deleting extra '{0}' instance attached to '{1}'", typeof(T), name);
                Destroy(this);
            }
        }
    }
    
    // this might be called for inactive singletons before Awake if FindInactive = true
    protected virtual void AwakeSingleton() {}

    protected virtual void OnDestroy()
    {
        applicationIsQuitting = true;
        instantiated = false;
    }
}