ABOUT ME

Unity로 만드는 게임 개발의 즐거움과 배움을 함께 나누는 공간입니다. 실전 코드 예제와 개발 팁, 프로젝트 경험을 공유하며, 창의적인 아이디어와 함께 성장하는 개발자 커뮤니티를 만들어갑니다.

Today
Yesterday
Total
  • 싱글톤이란 무엇인가? - Singleton기초
    Unity 2024. 12. 15. 20:33
    특정 클래스의 인스턴스를 단 하나만 생성하고, 어디서든 접근할 수 있도록 보장하는 디자인 패턴

     

    게임 개발에서 관리 클래스는 중요한 데이터를 저장하고, 시스템을 조율하는 역할을 한다.
    그러나 이러한 클래스가 복수로 생성된다면, 데이터 충돌이나 불일치가 발생할 위험이 있다.
    이러한 상황을 방지하고, 데이터를 중앙에서 일관성 있게 관리하기 위해 싱글톤 패턴(Singelton Pattern) 을 사용한다.

    왜 필요할까?

    1. 데이터를 중앙에서 관리하는 것이 가능하다.
      - 게임 상태, 점수, 설정 등은 하나로 관리되어야 하는데, 이를 싱글톤 패턴이 적용된 매니저 클래스에서 관리하여 데이터의 혼동을 방지한다.
    2. 전역에서 접근하는 것이 가능하다.
      - 다수의 객체가 해당 GameManager에 접근해야 하는 경우가 생기는데, 어디서든 접근할 수 있어 코드가 간소화 된다.
    3. 중복된 클래스의 선언을 방지한다.
      - 하나의 인스턴스만 생성되는것을 보장하여 불필요한 인스턴스가 생성되는 것을 방지하고, 데이터의 일관성을 보장한다.

    근데 사실 이렇게 얘기해도 한번에 와닿지는 않는다.
    예시 코드와 함께 알아보자.

    예시 : GameManager

    먼저 Singleton이 적용되지 않은 GameManager의 사례를 보자.
    ```
    public class GameManager : MonoBehaviour
    {
    public int score = 0;
    public bool isGameOver = false;

    public class GameManager : MonoBehaviour
    {
        public int score = 0;
        public bool isGameOver = false;
        public void AddScore(int points)  
        {  
            score += points;  
            Debug.Log($"Current Score: {score}");  
        }  
        public void GameOver()  
        {  
            isGameOver = true;  
            Debug.Log("Game Over!");  
        }  
        
    }

     

    게임 매니저는 게임의 점수와 게임 오버 여부를 관리하는 클래스다.
    플레이어가 어떤 오브젝트를 먹었을 때 AddScore 함수를 호출해 점수를 추가하고, 추락 시 Collider에 충돌하면 GameOver 함수가 호출되어 게임이 종료된다.

    이 클래스는 다른 스크립트에서 사용하려면 특정 인스턴스에 대한 참조를 명시적으로 전달받아야 한다.

    GameManager를 한 개의 객체로만 사용한다면 문제가 없다. 하지만 게임 UI를 관리하는 UI 클래스가 있고, 여러 개의 아이템이 GameManager에 접근해야 하는 상황은 흔히 발생한다.

    그런데 만약 실수로 GameManager를 여러 개 생성했고, UI 클래스와 플레이어가 서로 다른 인스턴스를 참조하고 있다면 어떤 일이 벌어질까?

     

    스코어는 GameManager1에서 증가하지만 UI는 GameManager2를 참조하고 있어 점수가 제대로 업데이트되지 않을 것이다.

    이런 상황을 방지하기 위해 사용하는 디자인 패턴이 바로 **싱글톤(Singleton)**이다.

    현재 GameManager에 Singleton을 패턴을 적용하여 보자.

    
    public class GameManager : MonoBehaviour
    {
        //GameManager Instance를 통해 다른 클래스에서 접근이 가능하다.
        public static GameManager Instance { get; private set; }
        public int score = 0;  
        public bool isGameOver = false;  
    
        private void Awake()  
        {  
            if (Instance == null)  
            {  
                Instance = this;  
                DontDestroyOnLoad(gameObject);   
            }  
            else  
            {  
                Destroy(gameObject);   
            }  
        }  
    
        public void AddScore(int points)  
        {  
            score += points;  
            Debug.Log($"Current Score: {score}");  
        }  
    
        public void GameOver()  
        {  
            isGameOver = true;  
            Debug.Log("Game Over!");  
        }
    }

    싱글톤 GameManager의 특징

    1. Awake에서 인스턴스 초기화

    Awake() 메서드에서 Instance가 비어 있으면 현재 오브젝트를 싱글톤 인스턴스로 설정한다.
    이미 인스턴스가 존재하면 새 오브젝트를 삭제한다.

    2. DontDestroyOnLoad(gameObject)

    GameManager 오브젝트가 씬 전환 시 삭제되지 않도록 보장한다.
    모든 씬에서 동일한 데이터를 유지할 수 있다.

    3. 접근 보장

    GameManager.Instance를 사용하면 어디서든 GameManager의 기능과 데이터를 호출할 수 있다.

    GameManager.Instance.AddScore(10);
    GameManager.Instance.GameOver();

     

     

    GameManager 구조 설명


    다음과 같은 구조로 구성되어, GameManager를 유일 Instance로 생성하고 등록한다.

    이때 GameManager가 새로 생성되는 경우, Instance가 Null인지 체크를 진행하게 된다.
    !null인 경우, Instance가 이미 메모리에 존재한다는 얘기이므로 스스로를 삭제한다.

    싱글톤의 장점은 무엇일까?

    1. 하나의 중앙 관리 인스턴스로 데이터 충돌 방지
    2. 모든 클래스에서 간편하게 Instance를 통해 접근 가능
    3. 동일한 코드로 다양한 씬에서 데이터 관리 가능

    -> 전체적으로, 데이터가 중앙 관리 되며 모든 컴포넌트에서 접근 가능하다는 점이 큰 장점으로 꼽힌다.

    하지만, 싱글톤을 사용할 땐 굉장히 주의해야 한다.

    싱글톤은 전역적으로 접근할 수 있다는 장점이, 단점으로 작동하기도 한다.

    1. 결합도 증가

    보통 좋은 소프트웨어 일수록 모듈의 독립성이 높다고 한다. 독립성이 높다라고 할 수 있는 기준은 강한 응집도 / 느슨한 응집도 라고 한다.

    결합도 / 응집도 설명


    해당 부분에 대해서는 다른 포스트에서 더 상세히 다루도록 하겠다.
    싱글톤을 사용할수록 코드 간의 결합도가 증가하여, 유지보수가 어려워진다 = 한 클래스를 수정하면, 다른 클래스를 수정해야 할 가능성이 증가한다.

    2. 어려운 테스트

    싱글톤 클래스는 테스트 환경에서 대체하거나 모의 객체(Mock Object)로 교체하기 어렵다.
    Unity에서는 TDD(Test-Driven Development, 테스트 주도 개발) 방식을 사용할 수 있는데, 싱글톤을 도입하면 이 과정이 복잡해진다.
    예를 들어, 싱글톤 인스턴스는 전역적으로 고정되어 있기 때문에 테스트마다 새로운 상태로 초기화하거나, 가짜 데이터로 교체하는 것이 번거로워진다.
    이는 코드의 테스트 가능성을 낮추고, 결과적으로 안정성을 떨어뜨릴 수 있다.

    3. 잠재적인 메모리 문제

    DontDestroyOnLoad를 통해 싱글톤 오브젝트를 유지하는 경우, 게임이 종료될 때까지 메모리에서 해제되지 않는다.
    프로젝트가 커지면서 싱글톤에 지나치게 많은 데이터나 로직이 포함되면 메모리 사용량이 증가할 수 있다.
    또한, 인스턴스 관리가 제대로 이루어지지 않으면 예상치 못한 동작이나 메모리 누수가 발생할 수 있다.

    싱글톤 사용 시 고려할 점

    싱글톤은 강력한 도구이지만, 잘못 사용하면 코드 품질을 떨어뜨릴 위험이 있다. 이를 방지하기 위해 다음과 같은 점을 염두에 두어야 한다:

    1. 최소한의 책임 부여

    싱글톤 클래스는 데이터 관리나 간단한 작업에만 사용하고, 과도한 책임을 부여하지 않는다.

    2. 적절한 테스트 구조 설계

    테스트 환경에서 싱글톤을 쉽게 대체할 수 있도록 인터페이스를 활용하거나 DI(Dependency Injection)를 고려한다.

    3. 필요한 경우에만 사용

    모든 상황에 싱글톤을 적용하기보다, 전역 접근이 꼭 필요한 경우에만 사용하도록 제한한다.
    결론적으로, 싱글톤은 적절히 사용하면 매우 유용한 패턴이지만, 잘못 활용하면 코드의 확장성과 유지보수를 저해할 수 있다.
    특히 Unity 프로젝트에서 여러 시스템과 복잡하게 엮일 가능성이 있다면, 대안적인 설계를 검토하는 것이 좋다.

    다음 포스트에서는 매니저에서 상속받을 수 있는 싱글톤 클래스를 작성하고 분석해보겠다.

Designed by Tistory.