[Unity] 모노싱글톤 (MonoSingleton)

Learning 

When: 1학년 10월 22일

 

How:

모노싱글톤을 복사, 붙여 넣기로 사용하고는 있었지만, 구조를 완벽히 이해하지 못하는 것 같아서 혼자
만들어보며 공부하게 되었습니다.

 

Understanding:

  • 간단 설명: MonoSingleton은 싱글톤 패턴을 편하게 쓰기 위한 하나의 클래스입니다.
    이 클래스를 상속받으면, 상속한 클래스에 접근할 수 있는 instance 변수와
    Instance 프로퍼티를 생성해 주기 때문에 싱글톤 패턴이 필요한 클래스에서 인스턴스를
    만들고 할당하며 중복이면 삭제하는 지루한 반복 작업을 할 필요가 없어집니다.
using UnityEngine;

public class MonoSingleton<T> : MonoBehaviour where T : MonoSingleton<T>
{
    private static T instance;

    public static T Instance
    {
        get
        {
            if (instance == null)
            {
                instance = FindFirstObjectByType<T>();
                if (instance == null)
                {
                    GameObject singleton = new GameObject(typeof(T).Name);
                    instance = singleton.AddComponent<T>();
                }
            }

            return instance;
        }
    }

    protected virtual void Awake()
    {
        T[] managers = FindObjectsByType<T>(FindObjectsSortMode.None);

        if (managers.Length > 1)
        {
            Destroy(gameObject);
        }
    }

    protected virtual void OnDestroy()
    {
        if (instance == this)
        {
            instance = null;
        }
    }
}
  • 원리: MonoSingleton의 내부 코드는 쓰는 사람마다 조금씩 다를 수 있지만 기본적인 원리는 다 같고,
    위가 제가 쓰는 MonoSingleton입니다. 처음부터 하나씩 설명해 보겠습니다.
    • 제네릭 한정자: where 키워드로 T, 즉 이 클래스를 상속받을 때 형식 매개 변수 타입이 모노싱글톤을
      상속받거나, 모노싱글톤이여야 한다는 어지러운 코드입니다. 쉽게 설명하면 누군가 모노싱글톤을
      사용할 때는 반드시 형식 매개 변수를 넣어줘야 하는데, 그 형식에 제약을 겁니다.
      "그 매개 변수는 MonoSingleton<T> 형식이거나 그 형식을 상속받아야 한다" 라는 제약을 말이죠.
      where 키워드를 "우리는 다음과 같은 클래스만 받는다!" 라고 해석하고
      '(class) T : MonoSingleton<T>처럼 형식 매개 변수로 자기 자신을 넣고 모노싱글톤을 상속한
      T라는 예시 클래스'가 까다로운 where가 원하고 있는 클래스라고 해석하면 더욱 직관적입니다.
      결국 정답은 자신을 형식 매개 변수로 넣은 클래스입니다.
      class GameManager : MonoSingleton<GameManager> 이렇게 쓰면
      MonoSingleton<GameObject>를 상속받고 있기 때문에 제약에 맞습니다.
    • static: static 키워드는 메서드, 필드, 프로퍼티 등의 것들을 클래스 소속으로 만들어 객체를 생성하지 않아도
      사용할 수 있게 합니다. 객체는 직접 생성해야 하지만 클래스는 프로그램을 시작하기도 전에 만드는 것이기
      때문에 클래스 소속으로 만들면 객체가 없어도 사용할 수 있습니다.
    • get: Instance 프로퍼티는 읽기 전용 프로퍼티입니다. get은 있는데 set은 없기 때문입니다.
      백 필드의 instance 변수가 싱글톤 객체를 담는데, Awake나 Start 같은 자동으로 호출되는 메서드에서
      인스턴스를 할당하지 않기 때문에 처음 시작하면 인스턴스에 값이 없습니다. 그럼, 누군가가 참조하려고
      했을 때 Null 오류가 뜰 것입니다. 하지만 그렇다고 Awake 메서드에서 인스턴스를 할당하면
      다른 곳에서는 Awake에서 값을 참조하지 못하게 됩니다. 무엇의 Awake가 먼저 실행될지 알 수 없기
      때문입니다. 그래서 프로퍼티를 활용해 누군가 인스턴스에 접근하면 인스턴스가 할당되었는지
      확인하고 없다면 찾아서 할당합니다. 인스턴스는 참조 형식이기 때문에 이런
      Lazy Initialization(지연 초기화)로 필요하기 전까진 힙 메모리를 할당하지 않아 그 전까지의
      메모리를 아낄 수 있어 좋습니다. 그런데 하이라키창이 더러워지는 것을 싫어하는 사람이
      따로 컴포넌트를 붙이지 않는다면 FindFirstObjectByType<>() 메서드로도 찾을 수 없고,
      만약 그렇다면 직접 게임 오브젝트에 컴포넌트까지 붙여서 인스턴스를 할당합니다.
      참고로 객체가 여러 개일 경우 일관성을 유지하기 위해 FindAnyObjectByType<>()이 아닌,
      FindFirstObjectByType<>() 메서드를 사용합니다.
    • Awake: Awake에서는 씬에 존재하는 모든 T 타입, 즉 인스턴스의 타입의 모든 객체를 검사합니다.
      모노싱글톤도 싱글톤이기에 한 씬 내에 여러 객체가 존재해서는 안 됩니다. Awake가 실행된 객체가
      자신을 포함한 모든 객체를 검사했는데 그 객체의 수가 1개보다 많다면 자신을 Destroy합니다.
      어차피 인스턴스가 없어진다고 해도 get에서 삭제되지 않은 객체를 할당하기 때문에
      굳이 다른 오브젝트를 Destroy할 필요는 없습니다. 자식이 Awake를 사용할 수 있기 때문에
      virtual 키워드로 자식이 override 할 수 있게 합니다. (오버라이드 할 경우에는 중복 제거를 위해
      자식에서 base.Awake()를 해주어야 합니다.)
    • Destroy: 게임 오브젝트가 Destroy 되면 객체도 사라집니다. 하지만 Unity가
      오버로딩한 == 연산자는 아직 객체가 있다는 값을 도출합니다. 아래에 증명하는 코드가 있습니다.
      아무 스크립트의 객체를 담은 후 게임 오브젝트를 Destroy하고 null인지 검사했더니
      False가 출력되었습니다. 하지만 정상적인 객체라고는 할 수 없으니 만약 정적인 instance 변수가
      자신의 객체를 참조하고 있다면 객체를 null로 바꿉니다.
EnemyAnimation anyInstance = FindAnyObjectByType<EnemyAnimation>();
Destroy(anyInstance.gameObject);
Debug.Log(anyInstance == null); //False 출력

 

Result: 원래 Awake에서 인스턴스를 할당하는 기초적인 싱글톤만 쓰다가 모노싱글톤을 보니까 갑자기 제네릭 한정자를 쓰거나 프로퍼티에서 값을 할당하고 백 필드가 있고 FindAnyObjectByType 메서드를 사용하는 등 여러모로 새로웠기 때문에 모노싱글톤을 공부하면서 부가적인 메모리나 부모, 자식 관계에 대해서도 더 잘 알게 되어 좋았습니다.

'UnityEngine' 카테고리의 다른 글

[Unity] FSM_원리 (StateMachine)  (0) 2025.09.20
[Unity] FSM_원리 (State)  (0) 2025.09.19
[Unity] FSM_개념  (0) 2025.09.18
[Unity] UnityEvent  (0) 2025.09.17
[Unity] UI 선택 판정 버그  (0) 2025.09.12