[Unity] Singleton Pattern

Lingtea_luv·2025년 6월 18일
0

Unity

목록 보기
25/30
post-thumbnail

Singleton Pattern


싱글톤 패턴은 씬에 단 하나만 존재하며, 전역적인 접근이 가능한 클래스를 구현할 때 사용되는 디자인 패턴이다. 팀 프로젝트나, 개인 프로젝트에서 매니저 클래스를 구현할 때 자주 사용되었으며, 사실 상 게임을 제작하는 프로그래머 입장에서는 가장 친숙한 패턴이 아닐까 싶다.

Singleton<T>

싱글톤 패턴을 적용시킨다고 하면, 미리 제네릭으로 구현한 싱글톤을 상속하도록 하는 경우가 대부분이다. 제네릭으로 구현하는 방식은 사람마다 다르지만, 거의 아래와 비슷한 형식으로 구현된다.

public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T _instance;
    public static T Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = FindObjectOfType<T>();
				
    			// 해당 코드는 자동 생성 싱글톤 방식으로(디버깅이 어려움)
  				// 명시적으로 씬에 배치하는 것을 선호하는 경우 굳이 작성하지 않는다.
                if (_instance == null && Application.isPlaying)
                {
                    var obj = new GameObject(typeof(T).Name);
                    _instance = obj.AddComponent<T>();
                }
            }
            return _instance;
        }
    }

    protected virtual void Awake()
    {
        if (_instance == null)
        {
            _instance = this as T;
            DontDestroyOnLoad(gameObject);
        }
        else if (_instance != this)
        {
            Destroy(gameObject);
        }
    }
}

위 코드는 이번 프로젝트에서 제네릭으로 구현한 싱글톤이다. 이렇게 만들어놓으면 상속을 통해 싱글톤의 성질을 가지는 매니저 클래스를 간단하게 만들 수 있다.

public class DataManager : Singleton<DataManager>
{
}

하지만 이번 프로젝트에서 싱글톤 패턴을 사용할 때 문제가 하나 생겼는데, 바로 DontDestroyOnLoad 이다.

DontDestroyOnLoad

DontDestroyOnLoad는 씬이 전환되어도 오브젝트를 파괴하지 않고 유지시키고자 할 때 사용한다. 게임 매니저와 같이 여러 씬(모든 씬)에서 공통으로 유지되어야하는 매니저라면 해당 코드를 사용하여 계속 존재하도록 만들 수 있는 것이다.

하지만 만약 특정 씬에서만 전역적인 접근을 위해 사용되는 용도라면 DontDestroyOnLoad 가 필요하지않다. 예를 들면 씬 전용 매니저인 LevelManager나 씬 전용 UI, Utility Class가 대표적이다. 이런 경우는 중복 호출 방지를 위해 씬이 전환될 경우 파괴되는 것이 안전할 것이다.

따라서 싱글톤을 구현할 때 DontDestroyOnLoad 까지 상속받을 것인지에 대한 처리가 함께 되어있으면 좋겠다는 생각을 했고, 간단하게 bool 타입 플래그를 추가하여 구현해보았다.

Singleton<T> with flag

public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T _instance;
    public static T Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = FindObjectOfType<T>();
  
                if (_instance == null && Application.isPlaying)
                {
                    var obj = new GameObject(typeof(T).Name);
                    _instance = obj.AddComponent<T>();
                }
            }
            return _instance;
        }
    }

	// 파괴 여부 옵션 플래그 : 기본값은 true로 설정
	protected virtual bool ShouldDontDestroy => true;


    protected virtual void Awake()
    {
        if (_instance == null)
        {
            _instance = this as T;
            
            if(ShouldDontDestroy)
            {
            	DontDestroyOnLoad(gameObject);
            }         
        }
        else if (_instance != this)
        {
            Destroy(gameObject);
        }
    }
}

위와 같이 플래그와 함께 구현을 하면 DontDestroyOnLoad 이 필요 없는 클래스의 경우 다음과 같이 작성하면 된다.

public class LevelManager : Singleton<LevelUIManager>
{
    protected override bool ShouldDontDestroy => false;
	
    ...
}

이러면 특정 씬에서 전역적인 접근을 가지지만, 씬이 전환되면 파괴되도록 만들 수 있다.

profile
뚠뚠뚠뚠

0개의 댓글