본문 바로가기

공부/유니티

[Unity] 제네릭 클래스를 상속받아 싱글톤 구현하기

싱글톤 패턴이란 특정 클래스의 인스턴스가 단 하나만 생성되도록 보장하는 디자인 패턴이다.

싱글톤 패턴을 처음 본다면 위의 말이 무슨 말인지 이해하기 힘들 것 같은데, 

사실 단순하게 현재 실행되고 있는 씬에 해당 클래스가 단 하나만 있다는 말이다.

 

씬에 해당 클래스가 단 하나만 있기에 해당 클래스에 접근하고 싶을 때, 쉽게 접근할 수 있다.

이러한 장점 때문에 Manager 역할을 하는 클래스에 주로 사용된다.

 

이제 어떻게 구현하는지 알아보자.


먼저 기본적인 싱글톤 구현 방법이다.

public class GameManager : MonoBehaviour
{
    private static GameManager instance;

    public static GameManager Instance
    {
        get
        {
            // 만약 인스턴스가 없다면
            // => 씬에 해당 클래스가 없거나 비활성화 되어있을 때
            if (instance == null)
            {
                // GameManager 클래스를 가지고 있는 오브젝트를 생성하여 인스턴스에 넣어줍니다.
                instance = new GameObject("GameManager").AddComponent<GameManager>();
            }
            return instance;
        }
    }

    void Awake()
    {
        // 만약 인스턴스가 없다면
        if (instance == null)
        {
            // 인스턴스에 자기 자신을 넣어줍니다.
            instance = this;
            // 씬 이동간에 파괴되지 않습니다.
            // 선택사항이며, 파괴되지 않는 오브젝트는 부모 오브젝트가 없어야합니다.
            DontDestroyOnLoad(this.gameObject);
        }
        else
        {
            // 자기 자신을 삭제합니다.
            Destroy(this);
        }
    }
    
    ... 추가 로직 ...
}

 

근데 이렇게 작성하면 스크립트 위에 싱글톤을 구현해야해서 보기도 불편하고, 필요할 때 마다 구현하기도 귀찮다.


이를 해결하기 위해 제네릭 클래스를 활용하여 싱글톤을 만들 수 있다.

추가로 Awake나 Start 대신 인스턴스가 생성되었을 때, 실행될 Initialize 메소드도 넣어주었다.

using UnityEngine;

public class Singleton<T> : MonoBehaviour where T : Singleton<T>
{
    protected static T instance;
    
    public static T Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new GameObject(nameof(T)).AddComponent<T>();
                instance.Initialize();
            }
            return instance;
        }
    }

    protected virtual void Awake()
    {
        if (instance == null)
        {
            instance = this as T;
            instance.Initialize();
        }
    }

    protected virtual void Start()
    {
        if (instance != this)
            Destroy(gameObject);
    }

    protected virtual void Initialize()
    {

    }
}

 

이제 싱글톤을 사용하고 싶다면 다음과 같이 사용하면 된다.

public class GameManager : Singleton<GameManager>
{
    protected override void Initialize()
    {
        base.Initialize();
        
        // Awake문을 사용하고 싶을 경우 재정의하여 사용
    }
    ... 추가 로직 ...
}

추가로 파괴되지 않는 싱글톤도 제네릭으로 만들어보았다.

using UnityEngine;

namespace FrameWork
{
    public class PersistentSingleton<T> : MonoBehaviour where T : PersistentSingleton<T>
    {
        protected static T instance;

        public static T Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = FindObjectOfType<T>();
                    if (instance == null)
                    {
                        instance = new GameObject(nameof(T)).AddComponent<T>();
                        instance.Initialize();
                    }
                }
                return instance;
            }
        }

        protected virtual void Awake()
        {
            if (this.transform.parent != null)
            {
                this.transform.SetParent(null);
            }

            if (instance == null || instance == this)
            {
                instance = this as T;
                DontDestroyOnLoad(transform.gameObject);
            }
            else
            {
                if (this != instance)
                {
                    Destroy(this.gameObject);
                }
            }
        }

        protected virtual void Initialize()
        {

        }
    }
}