高级 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;
    }
}