Синглтоны в Unity3D

Хочу поделиться 2-мя реализациями паттерна Синглтон (Singleton) в Unity3D, которые хорошо зарекомендовали себя. Оба паттерна наследованы от MonoBehaviour — их нужно использовать именно тогда, когда синглтон должен являться игровым объектом/частью сцены. Для обыкновенных синглтонов можно использовать стандартные для C# способы.

Собственно классы:

using UnityEngine;

public class GlobalSingletonBehaviour<T> : MonoBehaviour where T : GlobalSingletonBehaviour<T>
{
    private static bool destroyed;
    private static T current;
    protected bool awaken;

    public static T Current
    {
        get { return GlobalSingletonBehaviour<T>.current != null || destroyed ? GlobalSingletonBehaviour<T>.current : GlobalSingletonBehaviour<T>.Load(); }
        set { GlobalSingletonBehaviour<T>.current = value; }
    }

    private static T Load()
    {
        var instance = (T)FindObjectOfType(typeof(T));
        if (instance == null)
        {
            var obj = new GameObject(typeof(T).Name);
            instance = obj.AddComponent<T>();
        }
        instance.Awake();
        return instance;
    }

    public void Awake()
    {
        if (this.awaken)
        {
            return;
        }
        this.awaken = true;

        if (GlobalSingletonBehaviour<T>.current != null && GlobalSingletonBehaviour<T>.current != this)
        {
            Object.Destroy(this.gameObject);
            return;
        }

        GlobalSingletonBehaviour<T>.current = (T)this;
        Object.DontDestroyOnLoad(this.gameObject);
        this.DoAwake();
    }

    public void OnDestroy()
    {
        if (GlobalSingletonBehaviour<T>.current == this)
        {
            destroyed = true;
            this.DoDestroy();
        }
    }

    public virtual void DoAwake()
    {
    }

    public virtual void DoDestroy()
    {
    }
}

 

using UnityEngine;

public class AutoSingletonBehaviour<T> : MonoBehaviour where T : AutoSingletonBehaviour<T>
{
    private static T current;
    protected bool awaken;

    public static T Current
    {
        get { return AutoSingletonBehaviour<T>.current != null ? AutoSingletonBehaviour<T>.current : AutoSingletonBehaviour<T>.Load(); }
        set { AutoSingletonBehaviour<T>.current = value; }
    }

    private static T Load()
    {
        var instance = (T)FindObjectOfType(typeof(T));
        if (instance == null)
        {
            var obj = new GameObject(typeof(T).Name);
            instance = obj.AddComponent<T>();
        }
        instance.Awake();
        return instance;
    }

    public void Awake()
    {
        if (this.awaken)
        {
            return;
        }
        this.awaken = true;

        if (AutoSingletonBehaviour<T>.current != null && AutoSingletonBehaviour<T>.current != this)
        {
            Object.Destroy(this.gameObject);
            return;
        }

        AutoSingletonBehaviour<T>.current = (T)this;
        this.DoAwake();
    }

    public void OnDestroy()
    {
        if (AutoSingletonBehaviour<T>.current == this)
        {
            AutoSingletonBehaviour<T>.current = null;
            this.DoDestroy();
        }
    }

    public virtual void DoAwake()
    {
    }

    public virtual void DoDestroy()
    {
    }
}

GlobalSingletonBehaviour предназначен для синглтонов глобальных для всей игры, AutoSingletonBehaviour — для синглтонов, существующих в пределах сцены. Код оформлен в виде обобщенных (generic) классов. В методах DoAwake и DoDestroy можно определить логику инициализации/деинициализации синглтона.

Ниже пример реализации рабочего синглтона для логирования внутриигровых событий:

public class StatisticsManager : GlobalSingletonBehaviour<StatisticsManager>
{
    public override void DoAwake()
    {
#if UNITY_IPHONE && !UNITY_EDITOR
        Playtomic.Initialize(000, "000", "000");
        Playtomic.Log.View();
#endif
    }

    public override void DoDestroy ()
    {
#if UNITY_IPHONE && !UNITY_EDITOR
        Playtomic.Log.ForceSend();
#endif
    }

    protected void OnApplicationPause(bool paused)
    {
#if UNITY_IPHONE && !UNITY_EDITOR
        if (!paused)
        {
            Playtomic.Log.View();
        }
#endif
    }

    public void LogPlay()
    {
#if UNITY_IPHONE && !UNITY_EDITOR
        Playtomic.Log.Play();
#endif
    }

    public void LogEvent(string name, string group, bool unique)
    {
#if UNITY_IPHONE && !UNITY_EDITOR
        Playtomic.Log.CustomMetric(name, group, unique);
#endif
    }

    public void LogEvent(string name, string group)
    {
#if UNITY_IPHONE && !UNITY_EDITOR
        Playtomic.Log.CustomMetric(name, group);
#endif
    }

    public void LogEvent(string name)
    {
#if UNITY_IPHONE && !UNITY_EDITOR
        Playtomic.Log.CustomMetric(name);
#endif
    }

    public void LogCounter(string name, string level)
    {
#if UNITY_IPHONE && !UNITY_EDITOR
        if (SystemInfo.deviceModel.StartsWith("iPad"))
        {
            level = "hd-" + level;
        }
        Playtomic.Log.LevelCounterMetric(name, level);
#endif
    }

    public void LogCounter(string name, string level, bool unique)
    {
#if UNITY_IPHONE && !UNITY_EDITOR
        if (SystemInfo.deviceModel.StartsWith("iPad"))
        {
            level = "hd-" + level;
        }
        Playtomic.Log.LevelCounterMetric(name, level, unique);
#endif
    }

    public void LogAverage(string name, string level, double value)
    {
#if UNITY_IPHONE && !UNITY_EDITOR
        if (SystemInfo.deviceModel.StartsWith("iPad"))
        {
            level = "hd-" + level;
        }
        Playtomic.Log.LevelAverageMetric(name, level, value);
#endif
    }
}