본문 바로가기
C#

전역으로 관리되는 class 를 만들고 싶을 때 (singleton)

by Oz Driver 2025. 3. 10.

 

게임을 만들다 보면 클래스를 넘나들며, 전역적으로 접근해서 사용하고 싶은 데이터들이 존재하게 됩니다.

이때 어떤 형태로 클래스를 만들 것인가에 대한 고민이 생기게 됩니다.

그래서 이번에 이 부분에 대해 한번 다뤄보겠습니다.

 

정적(static) 클래스로 만들기

유니티가 아닌 일반적인 C# 프로젝트에서는 전역적으로 데이터를 관리하기 위해 정적 클래스를 사용할 수 있습니다.

public static class GameManager
{
    public static int score;
}

 

이렇게 하면 GameManager.score로 어디서든 간편하게 접근할 수 있습니다. 하지만 문제는 유니티에서는 이것만으로 충분하지 않다는 점입니다.

 

정적 클래스의 단점

정적 클래스는 특성상 애초에 인스턴스를 만들 수 없습니다.

즉, new GameManager() 같은 객체 생성이 불가능합니다.

따라서 객체 생성이 불가능하므로 상속도 할 수 없습니다.

 

유니티에서는 MonoBehaviour를 상속받을 수 없으므로 Start(), Update(), DontDestroyOnLoad() 같은 유니티의 기능을 활용할 수 없습니다.

유니티 오브젝트가 아니므로 GameObject 에 붙여 관리할 수 없습니다.

 

즉, 단순한 데이터 저장소로는 괜찮지만, 게임 상태 관리까지 하기에는 한계가 있습니다.

만약 이러한 기능이 필요하지 않다면 정적 클래스로도 충분합니다.

하지만 유니티의 기능을 사용해야 한다면 MonoBehaviour를 상속받아야 합니다.

 

 

MonoBehaviour 를 상속받아 만들기

정적 클래스로는 Start(), Update() 같은 유니티 라이프사이클 함수를 사용할 수 없으므로, MonoBehaviour를 상속한 일반 클래스를 만들어 보겠습니다.

public class GameManager : MonoBehaviour
{
    public int score;
}

 

이제 유니티 씬에서 GameObject를 만들고 GameManager 스크립트를 붙이면 동작할 것입니다. 하지만 씬이 변경되면 어떻게 될까요?

 

일반 클래스의 단점

씬이 변경되면 GameManager 오브젝트도 사라집니다.

씬이 하나라면 문제없지만, 여러 씬에서 게임 상태를 유지해야 한다면 한계가 있습니다.

만약, 씬이 하나만 있다면 이 방법으로도 충분합니다. 하지만 여러 씬을 넘나들면서 관리해야 한다면 씬이 변경될 때도 유지되는 방법이 필요합니다.

 

DontDestroyOnLoad() 적용하기

씬이 변경될 때도 GameManager가 살아남도록 DontDestroyOnLoad()를 사용해보겠습니다.

public class GameManager : MonoBehaviour
{
    void Awake()
    {
        DontDestroyOnLoad(gameObject); // 씬이 바뀌어도 유지됨
    }
}

 

이제 GameManager 오브젝트는 씬이 바뀌어도 사라지지 않습니다. 하지만 이 방법에도 단점이 있습니다.

 

DontDestroyOnLoad()의 단점

같은 GameManager 오브젝트가 중복 생성될 가능성이 있습니다.

씬을 변경할 때마다 새로운 GameManager가 생성되면 여러 개가 존재하는 문제가 발생할 수 있습니다.

이 문제를 해결하기 위해서는 싱글톤 패턴을 사용해야 합니다.

 

 

싱글톤 적용하기

게임 매니저가 씬이 바뀌어도 하나만 유지되도록 싱글톤 패턴을 적용해보겠습니다.

public class GameManager : MonoBehaviour
{
    public static GameManager Instance;
    public int score; 
    public int playerHealth = 100; 

    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            
            // 씬 변경 시에도 유지
            DontDestroyOnLoad(gameObject); 
        }
        else
        {
            Destroy(gameObject); // 중복 생성 방지
        }
    }
}

 

이제 GameManager는 최초 1회만 생성되며, 씬이 바뀌어도 유지됩니다.

 

싱글톤의 장점

씬이 변경되어도 오직 하나의 인스턴스만 유지됩니다.

GameManager.Instance를 통해 어디서든 접근할 수 있습니다.

중복 생성이 자동으로 방지됩니다.

 

. Instance 없이 사용하는 방법

사실 이것으로 충분할 수도 있지만, 작은 불편함이 생깁니다.

싱글톤 방식에서는 GameManager.Instance.score처럼 항상 .Instance를 붙여야 하는 번거로움이 있다는 것입니다.

이를 해결하기 위해 정적 변수를 활용하면 됩니다.

public class GameManager : MonoBehaviour
{
    public static GameManager Instance;
    public static int score; 
    public static int playerHealth = 100; 

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

 

이제 GameManager.score처럼 .Instance 없이도 바로 접근할 수 있습니다.

GameManager.score = 10;
Debug.Log(GameManager.score); // 출력: 10

 

씬이 하나일 때는 괜찮지만…

이 코드는 하나의 씬으로 구성된 프로젝트에서는 안전하게 작동합니다.  
하지만 씬이 여러 개인 경우, 각 씬에서 GameManager가 중복 생성될 수 있으므로  싱글톤 구조를 조금 더 보완해줄 필요가 있습니다.

추후에는 씬이 여러 개인 상황에서도 안정적으로 작동하는 싱글톤 패턴과 Resources 폴더에서 자동으로 로딩하는 방법에 대해 소개하겠습니다.

 

 

마무리

작은 프로젝트에서는 static class를 쓰면 충분합니다.

유니티의 Start(), Update() 등을 사용해야 한다면 MonoBehaviour를 상속해야 합니다.

씬이 여러 개라면 DontDestroyOnLoad() 가 필요합니다.

씬이 바뀌어도 하나만 유지되도록 하려면 싱글톤이 필요합니다.

코드를 더 간결하게 하려면 .Instance 없이 static 변수를 활용할 수 있습니다.

 

정리하자면, 단순한 프로젝트에서는 static을, 씬을 넘나드는 관리가 필요하면 싱글톤을 선택하는 것이 가장 좋은 방법일 것입니다.