본문 바로가기
개발/Unity

Unity에서 추상 클래스 기반 Singleton 패턴 구현하기

by DinoDev 2025. 10. 6.
728x90
반응형

게임을 만들다 보면, 전역에서 접근 가능한 매니저 객체가 필요합니다.
예를 들어 GameManager, AudioManager, UIManager처럼 하나만 존재해야 하는 클래스들 말이죠.

이럴 때 사용하는 대표적인 디자인 패턴이 바로 Singleton(싱글톤) 입니다.
이번에는 일반적인 싱글톤이 아니라, 추상 클래스를 기반으로 공통 로직을 재사용할 수 있는 구조로 만들어봅시다.


1️⃣ 기본 아이디어

Unity에서는 MonoBehaviour 기반의 싱글톤을 만들 때,
다른 클래스들이 동일한 패턴을 반복해서 작성하는 경우가 많습니다.
예를 들어 이런 형태를 여러 매니저가 각각 구현하곤 하죠

public class GameManager : MonoBehaviour
{
    public static GameManager Instance;

    void Awake()
    {
        if (Instance == null)
            Instance = this;
        else
            Destroy(gameObject);
    }
}

이런 중복을 줄이기 위해 제네릭 추상 클래스 Singleton<T>를 만들면,
모든 매니저가 상속만으로 동일한 싱글톤 동작을 자동으로 가지게 됩니다.


2️⃣ Singleton 추상 클래스 구현

using UnityEngine;

public abstract class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T _instance;
    public static T Instance
    {
        get
        {
            if (_instance == null)
            {
                // 씬에 존재하는 인스턴스 찾기
                _instance = FindFirstObjectByType<T>();

                // 없으면 자동 생성
                if (_instance == null)
                {
                    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); // 중복 생성 방지
        }
    }
}

💡 핵심 포인트

  • where T : MonoBehaviour : 제네릭 타입 T가 MonoBehaviour여야 한다는 제약 조건
  • FindObjectOfType<T>() : 씬 내 존재 여부 확인
  • DontDestroyOnLoad() : 씬 전환 시에도 유지
  • protected virtual void Awake() : 상속받는 클래스가 Awake()를 오버라이드할 수 있도록 허용

3️⃣ GameManager에 적용하기

이제 GameManager를 Singleton<T>를 상속받아 구현하면 됩니다.

using UnityEngine;

public class GameManager : Singleton<GameManager>
{
    public int score = 0;

    protected override void Awake()
    {
        base.Awake(); // 반드시 호출해야 함
        Debug.Log("GameManager Awake");
    }

    public void AddScore(int value)
    {
        score += value;
        Debug.Log($"현재 점수: {score}");
    }
}

이렇게 하면 GameManager는 자동으로 싱글톤 동작을 가지게 됩니다.
씬에 하나만 존재하도록 보장되고, 필요하면 자동 생성까지 이루어집니다.


4️⃣ 다른 스크립트에서 사용하기

public class Player : MonoBehaviour
{
    void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Coin"))
        {
            GameManager.Instance.AddScore(10);
        }
    }
}

GameManager.Instance로 전역 접근이 가능하죠.
씬에 GameManager가 없어도 자동 생성되므로, 따로 등록할 필요가 없습니다.


5️⃣ 확장 가능한 구조

이 추상 클래스 기반 구조의 장점은, AudioManager, UIManager, SpawnManager 등 다양한 매니저 클래스가 모두 같은 로직을 상속받아 일관성 있게 동작한다는 것입니다.

public class AudioManager : Singleton<AudioManager>
{
    public void PlayBGM(string name)
    {
        Debug.Log($"BGM 재생: {name}");
    }
}

⚠️ 주의할 점

  • MonoBehaviour 기반 싱글톤이므로, Unity 씬 안에서만 유효합니다.
  • base.Awake()를 호출하지 않으면 인스턴스 초기화가 되지 않습니다.
  • 멀티스레드 환경이나 순수 C# 유틸 클래스에는 일반 C# 싱글톤을 사용하는 게 좋습니다.

✅ 정리

항목설명
패턴 Singleton (제네릭 기반 추상 클래스)
장점 중복 제거, 일관된 싱글톤 관리, 자동 생성
사용 예 GameManager, AudioManager, UIManager 등
주의점 반드시 base.Awake() 호출, MonoBehaviour 한정
728x90
반응형