2019
11.15

 

 

1) 기본형 싱글톤

 - DonDestroyOnLoad 를 삭제하면 해당 씬에서만 쓰이는 싱글톤으로 사용.

public class SingletonExample : MonoBehaviour
{
    #region 싱글톤
    private static SingletonExample _instance = null;

    public static SingletonExample Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = (SingletonExample)FindObjectOfType(typeof(SingletonExample));
                if (_instance == null)
                {
                    Debug.Log("There's no active ManagerClass object");
                }
            }
            return _instance;
        }
    }

    void Awake()
    {
        if (_instance != null && _instance != this)
        {
            DestroyImmediate(gameObject);
        }
        else
        {
            _instance = this;
            DontDestroyOnLoad(this.gameObject);
            AwakeAfter();
        }
    }
    #endregion
    
    void AwakeAfter()
    {
    
    }
}

 

2) 다른 씬으로 전환할 때 마다 특정 함수 호출하는 싱글톤 

 - 문제점 : DonDestroyOnLoad상태의 오브젝트는 Awake랑 Start가 최초 씬에서만 호출된다. 하지만 씬이 전환될 때마다 처리를 해주어야 하는 경우가 있다.

 - 해결 : DonDestroyOnLoad + 다른 씬으로 갈때 OnLevelLoaded 델리게이트를 체인 시켜준다.

 

 - 주의할 점은 함수의 호출 순서가  Awake -> OnEnable -> OnLevelLoaded -> Start 순이라서 Awake가 아니라 Start에서 호출할 경우 첫 번째 씬에서는 체인 메소드가 실행되지 않는다.

 - https://docs.unity3d.com/kr/530/Manual/ExecutionOrder.html 함수 호출 순서 참고.

 

public class LevelLoadedSingleton : MonoBehaviour
{
    #region 싱글톤
    private static LevelLoadedSingleton _instance = null;

    public static LevelLoadedSingleton Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = (LevelLoadedSingleton)FindObjectOfType(typeof(LevelLoadedSingleton));
                if (_instance == null)
                {
                    Debug.Log("There's no active ManagerClass object");
                }
            }
            return _instance;
        }
    }

    void Awake()
    {
        if (_instance != null && _instance != this)
        {
            DestroyImmediate(gameObject);
        }
        else
        {
            _instance = this;
            DontDestroyOnLoad(gameObject);
            
            // 델리게이트 체인을 걸어준다. Awake -> OnEnable -> OnLevelLoaded -> Start 순이라서 첫 씬도 
            SceneManager.sceneLoaded += OnLevelLoaded;
        }
    }

    private void OnLevelLoaded(Scene scene, LoadSceneMode mode)
    {
        // 레벨 변경시에 호출됨.
    }

    #endregion
}

 

 

3) 심화. 제네릭 싱글톤

 - 1)의 싱글톤 패턴은 새 클래스를 만들 때 마다 클래스 이름을 5번이나 써줘야 한다는 문제점이 있다. 

 - 때문에 제네릭 <T> 로 상속하여 간단하게 만들어 볼 수 있다.

 

 

Base가 되는 Singleton.cs 는 아래와 같이 만들어두고,

public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    protected static T _instance = null;
    public static T Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = FindObjectOfType(typeof(T)) as T;
                if (_instance == null)
                {
                    Debug.Log("싱글턴 " + typeof(T) + "이 씬에 존재하지않음");
                }
            }
            return _instance;
        }
        set => _instance = value;
    }
}

 

상속시킬 스크립트에서는 : Singleton<해당클래스명> 으로 상속시켜주면 된다.

public class SingletonChildExample : Singleton<SingletonChildExample>
{
    #region 싱글톤
    void Awake()
    {
        if (Instance != null && Instance != this)
        {
            DestroyImmediate(gameObject);
        }
        else
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            AwakeAfter();
       }
    }
    #endregion
}

 

 

사용중인 싱글톤

더보기
using UnityEngine;

namespace Mentum
{
    /// <summary>
    /// SceneBoundSingleton : 씬에 한정된 싱글톤 객체로, 씬 전환 시 삭제됨.
    /// PersistentSingleton : 씬 전환 후에도 유지되는 영구적인 싱글톤.
    /// AutoSceneSingleton : 씬 한정 싱글톤으로, 존재하지 않을 경우 자동으로 생성됨.
    /// AutoPersistentSingleton : 씬 전환 후에도 유지되는 싱글톤으로, 없으면 자동 생성됨.
    /// </summary>
    public abstract class Singleton<T> : MonoBehaviour where T : Component
    {
        protected static T _instance;
        public static T Instance
        {
            get
            {
                if (_isOnQuit) return null;
                if (_instance == null)
                {
                    _instance = FindAnyObjectByType(typeof(T)) as T;
                    if (_instance == null)
                        Debug.LogError($"싱글턴 {typeof(T)}이 씬에 존재하지않음.");
                }
                return _instance;
            }
        }
        public static bool IsExist => !_isOnQuit && _instance != null;
        protected static bool _isOnQuit = false;

        protected abstract bool IsDonDestroyOnLoad { get; }

        protected void Awake()
        {
            if (_instance != null && _instance != this)
                DestroyImmediate(gameObject);
            else
            {
                _instance = this as T;
                if (IsDonDestroyOnLoad)
                    DontDestroyOnLoad(gameObject);

                InitOnAwake();
            }
        }

        protected virtual void InitOnAwake()
        {
        }

        protected virtual void OnApplicationQuit()
        {
            _isOnQuit = true;
        }

        protected virtual void OnDestroy()
        {
            if (_instance == this)
                _instance = null;
        }

        //Domain Reload를 언체크한 상황에서도 동작하는 초기화 애트리뷰트
        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
        protected static void ReloadDomain()
        {
            _instance = null;
            _isOnQuit = false;
        }
    }

    public class SceneBoundSingleton<T> : Singleton<T> where T : Component
    {
        protected sealed override bool IsDonDestroyOnLoad => false;
    }

    public class PersistentSingleton<T> : Singleton<T> where T : Component
    {
        protected sealed override bool IsDonDestroyOnLoad => true;
    }

    public class AutoSceneSingleton<T> : Singleton<T> where T : Component
    {
        protected sealed override bool IsDonDestroyOnLoad => false;
        public new static T Instance
        {
            get
            {
                if (_isOnQuit) return null;
                if (_instance == null)
                {
                    _instance = FindAnyObjectByType(typeof(T)) as T;
                    if (_instance == null)
                        _instance = new GameObject($"{typeof(T).Name}").AddComponent<T>(); // 새로 생성함
                }
                return _instance;
            }
        }
    }

    public class AutoPersistentSingleton<T> : Singleton<T> where T : Component
    {
        protected sealed override bool IsDonDestroyOnLoad => true;
        public new static T Instance
        {
            get
            {
                if (_isOnQuit) return null;
                if (_instance == null)
                {
                    _instance = FindAnyObjectByType(typeof(T)) as T;
                    if (_instance == null)
                        _instance = new GameObject($"{typeof(T).Name}").AddComponent<T>(); // 새로 생성함
                }
                return _instance;
            }
        }
    }
}