Unity

Unity Tip : SingleTon Pattern(싱글톤 패턴)

유니얼 2023. 11. 11. 01:56
728x90

Unity에서 게임을 개발할 때, 플레이어 데이터나 게임 관리자와 같은 특정 요소를 효율적으로 관리하는 것은 매우 중요합니다. 이를 위해 유용한 디자인 패턴 중 하나는 Singleton 패턴입니다. 이 블로그 게시물에서는 Unity Singleton 패턴에 대해 자세히 알아보고, 이 패턴의 개념과 유용성, 그리고 Unity 프로젝트에서 효과적으로 구현하는 방법에 대해 설명하겠습니다.

 

참고 자료 링크 :

https://www.yes24.com/Product/Goods/114854688

 

유니티로 배우는 게임 디자인 패턴 - 예스24

우아하게 게임을 개발하고 싶다면 디자인 패턴을 배우자 이 책은 유니티의 고급 프로그래밍 기술과 디자인 패턴으로 작업을 시작할 준비가 된 모든 게임 개발자를 위해 쓰였다. 소프트웨어 디

www.yes24.com

Singleton 패턴이란?

Singleton 패턴은 소프트웨어 엔지니어링에서 사용되는 디자인 패턴으로, 클래스에 단 하나의 인스턴스만 존재하고, 해당 인스턴스에 대한 전역적인 액세스 지점을 제공하는 것을 말합니다. Unity 게임 개발의 맥락에서 Singleton은 게임 수명 주기 동안 단일 인스턴스로 존재해야 하는 중요한 게임 객체와 리소스를 관리하는 데 자주 활용됩니다.

Unity에서 Singleton 패턴을 사용하는 이유

Singleton 패턴을 사용하는 이유는 다음과 같습니다:

  1. 중앙 집중식 제어: Unity Singleton은 게임 관리자, 플레이어 데이터, 오디오 컨트롤러, UI 핸들러 등과 같은 중요한 게임 구성 요소들을 중앙에서 관리할 수 있도록 해줍니다. 이를 통해 코드의 중복을 피하고, 효율적인 게임 관리를 할 수 있습니다.
  2. 전역적인 액세스: Singleton 패턴은 어디서든지 Singleton 객체에 접근할 수 있는 전역적인 액세스 지점을 제공합니다. 이는 다른 객체들이 Singleton 객체를 필요로 할 때 편리하게 사용할 수 있도록 도와줍니다.
  3. 상태 유지: Singleton 객체는 게임 수명 주기 동안 유지되므로, 게임의 상태와 데이터를 유지할 수 있습니다. 이는 게임의 진행 상황을 관리하거나 데이터를 공유하는 데 유용합니다.

Singleton 패턴의 구현 예시

아래의 코드는 Unity에서 Singleton 패턴을 구현하는 간단한 예시입니다. 이 예시에서는 GameManager라는 클래스를 Singleton으로 만들어 게임의 전반적인 관리를 담당합니다.

1단계: 기본 싱글톤 클래스 만들기

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SingleTon<T> : MonoBehaviour where T : MonoBehaviour
{
    protected static T _instance;
    public static bool HasInstance => _instance != null;
    public static T TryGetInstance() => HasInstance ? _instance : null;
    public static T Current => _instance;

    /// <summary>
    /// 싱글톤 디자인 패턴
    /// </summary>
    /// <value>인스턴스</value>
    public static T Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = FindObjectOfType<T>();
                if (_instance == null)
                {
                    GameObject obj = new GameObject();
                    obj.name = typeof(T).Name + "_AutoCreated";
                    _instance = obj.AddComponent<T>();
                }
            }
            return _instance;
        }
    }

     /// <summary>
     /// Awake에서 인스턴스를 초기화합니다. 만약 awake를 override해서 사용해야 한다면 base.Awake()를 호출해야 합니다.
     /// </summary>
     protected virtual void Awake()
     {
         InitializeSingleton();
     }

     /// <summary>
     /// 싱글톤을 초기화합니다.
     /// </summary>
     protected virtual void InitializeSingleton()
     {
         //게임이 실행중이 아니라면 종료합니다.
         if (!Application.isPlaying)
         {
             return;
         }

         _instance = this as T;
     }
}

 

SingleTon Class는 MonoBehaviour를 상속받은 제네릭 클래스로, Unity에서 사용되는 컴포넌트들을 싱글톤으로 만들기 위한 용도로 설계되었습니다.

  • HasInstance: 인스턴스가 있는지 여부를 반환하는 속성입니다.
  • TryGetInstance(): 인스턴스가 있으면 해당 인스턴스를 반환하고, 없으면 null을 반환하는 메서드입니다.
  • Current: 현재의 인스턴스를 반환하는 속성입니다.
  • Instance: Singleton 객체에 접근하기 위한 속성으로, 처음 호출될 때 인스턴스가 없다면 씬에서 해당 타입의 컴포넌트를 찾거나 새로운 게임오브젝트에 컴포넌트를 추가하여 인스턴스화합니다.
  • Awake() 메서드와 InitializeSingleton() 메서드는 싱글톤 객체의 초기화 과정을 담당합니다. Awake() 메서드는 MonoBehavior의 Awake() 함수와 유사하게 동작하며, _instance 변수에 자기 자신(this)을 할당하여 초기화합니다.

이렇게 구현된 싱글톤 패턴은 MonoBehaviour 기반으로 작동하기 때문에 Unity 엔진과 함께 사용할 수 있습니다.

2단계: 기본 싱글톤 클래스에서 상속

GameManager 클래스

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor.SearchService;
using UnityEngine;
using UnityEngine.SceneManagement;

public class GameManager : SingleTon<GameManager>
{
    private DateTime _sessionStartTime;
    private DateTime _sessionEndTime;

    private void Start()
    {
        //TODO:
        //- 플레이어 데이터 불러오기
        //- 저장된 데이터가 없으면 플레이어를 등록 화면으로 리디렉션
        //- 백엔드 호출하여 일일 도전 과제 및 보상 가져오기

        _sessionStartTime = DateTime.Now;
        Debug.Log("게임 세션 시작 시간 @: " + DateTime.Now);
    }

    private void OnApplicationQuit()
    {
        _sessionEndTime = DateTime.Now;
        TimeSpan timeDifference = _sessionEndTime.Subtract(_sessionStartTime);
        Debug.Log("게임 세션 종료 시간 @: " + DateTime.Now);
        Debug.Log("게임 세션 지속 시간: " + timeDifference);
    }

    private void OnGUI()
    {
        if (GUI.Button(new Rect(10, 120, 200, 50), $"{SceneManager.GetActiveScene().name} 다음 장면"))
        {
            if (SceneManager.GetActiveScene().buildIndex < SceneManager.sceneCount)
            {
                SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
            }
            else
            {
                SceneManager.LoadScene(0);
            }
        }
    }
}

이 코드는 다음을 수행합니다:

  • Start 메서드에서는 게임이 시작될 때 해야 할 일을 주석으로 설명하고, 게임 세션 시작 시간을 현재 시간으로 기록하며, 이를 로그에 남깁니다.
  • OnApplicationQuit 메서드에서는 게임이 종료될 때 해야 할 일을 주석으로 설명하고, 게임 세션 종료 시간을 현재 시간으로 기록하며, 게임 세션의 지속 시간을 계산하고 로그에 남깁니다.
  • OnGUI 메서드에서는 GUI 버튼을 생성하고, 버튼이 클릭되면 현재 장면을 다음 장면으로 전환하거나, 모든 장면을 순환하려면 첫 번째 장면으로 돌아갑니다. 이 기능을 로그에 남깁니다.

이 GameManager 클래스는 게임 세션 및 장면 전환과 관련된 기본적인 기능을 처리하기 위한 코드입니다.

 

SingletonExample 클래스

using UnityEngine;


namespace Chapter.Singleton
{
    public class SingletonExample : SingleTon<SingletonExample>
    {
        // 이 클래스는 싱글톤 패턴을 사용하므로 하나의 인스턴스만 존재합니다.

        // 여기에 싱글톤 특정 속성 및 메서드를 추가하세요.
        private static int _score; // 게임의 점수를 저장하는 변수입니다.

        public static int Score { get { return _score; } private set { _score = value; } } // 게임의 점수를 읽고 쓸 수 있는 속성입니다.

        public void IncreaseScore(int points)
        {
            Score += points; // 점수를 증가시키는 메서드입니다.
        }
    }
}

이 코드는 다음을 수행합니다:

  • SingletonExample클래스는싱글톤패턴을사용하여생성된객체가하나뿐임을보장합니다.
  • score 변수는 게임의 현재 점수를 저장하는 데 사용됩니다. score는 내부에서만 변경 가능합니다.
  • Score 속성은 score 변수를 외부에서 읽을 수 있도록 제공하며, private set으로 설정하여 외부에서는 score 값을 직접 설정할 수 없습니다.
  • IncreaseScore 메서드는 게임 점수를 증가시키는 데 사용됩니다. 이 메서드를 호출하면 현재 점수가 지정된 포인트만큼 증가합니다.

이 클래스는 게임에서 현재 점수를 관리하기 위한 싱글톤 패턴의 예시입니다.

3단계: 코드에서 싱글톤 사용

다음과 같이 코드 어디에서나 싱글톤 인스턴스에 액세스할 수 있습니다.

 

GameController 클래스

using Chapter.Singleton;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class GameController : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        // Singleton 인스턴스에 접근
        SingletonExample.Instance.IncreaseScore(10);
        Debug.Log("현재 점수: " + SingletonExample.Score);
    }

    private void OnGUI()
    {
        // 점수 텍스트를 위한 GUIStyle 생성
        GUIStyle style = new GUIStyle();
        style.fontSize = 24;
        style.normal.textColor = Color.white;

        // 점수 표시 위치 및 크기 정의
        Rect scoreRect = new Rect(10, 10, 200, 50); // 이 값을 필요에 따라 조절

        // 현재 점수 표시
        GUI.Label(scoreRect, "점수: " + SingletonExample.Score, style);

        // 버튼 클릭 감지 및 점수 증가
        if (GUI.Button(new Rect(10, 70, 200, 50), "점수 5 점 증가"))
        {
            SingletonExample.Instance.IncreaseScore(5);
        }
    }
}

이 코드는 다음과 같은 작업을 수행합니다:

  • Start메서드에서는게임이 시작될 때 SingletonExample의 인스턴스에 접근하여 점수를 10 증가시키고, 현재 점수를 로그로 출력합니다.
  • OnGUI메서드에서는 GUI 스타일을 만들어서 점수 텍스트의 모양을 설정합니다. 그런 다음, 점수를 표시할 위치와 크기를 scoreRect로 정의하고, 이를 사용하여 현재 점수를 화면에 표시합니다.
  • 버튼 클릭을 감지하고, "점수 5 점 증가" 버튼이 클릭되면 SingletonExample 인스턴스의 IncreaseScore 메서드를 호출하여 점수를 5 증가시킵니다.

이 GameController 클래스는 게임 내에서 점수를 관리하고 표시하는 데 사용됩니다.

3단계: 결과 확인하기

SingleTon 패턴

Singleton 패턴의 장단점

Unity 싱글턴 패턴에는 고유한 장단점이 있습니다. Unity 게임 개발에서 싱글톤 패턴을 사용할지 여부를 결정할 때 이러한 요소를 고려하는 것이 중요합니다.

싱글턴 패턴의 장점

  1. 전역 접근성: 싱글톤은 특정 클래스나 구성 요소에 대한 전역 액세스 지점을 제공합니다. 즉, 코드베이스의 어느 곳에서나 필수 게임 개체나 데이터에 쉽게 액세스하고 관리할 수 있습니다. 이는 게임의 여러 부분 간의 통신을 단순화합니다.
  2. 리소스 효율성: Unity 싱글톤은 게임 수명 주기 전반에 걸쳐 클래스의 인스턴스가 하나만 존재하도록 보장합니다. 이는 관리자나 복잡한 데이터 구조와 같이 생성하는 데 리소스를 많이 사용하는 개체에 특히 유용할 수 있습니다. 동일한 인스턴스를 재사용하면 메모리와 CPU 리소스가 절약됩니다.
  3. 간소화된 초기화: 싱글톤에는 한 번만 발생해야 하는 초기화 논리가 포함되는 경우가 많습니다. 싱글톤을 사용하면 이 초기화 코드가 정확히 한 번 실행되도록 보장하여 동일한 리소스를 초기화하려고 하는 여러 인스턴스에서 발생할 수 있는 잠재적인 문제를 방지할 수 있습니다.
  4. 더 쉬워진 코드 유지 관리: 싱글톤 패턴은 더욱 깔끔하고 체계적인 코드 구조를 촉진합니다. 글로벌 게임 요소 관리를 중앙 집중화하여 코드베이스를 보다 쉽게 ​​유지 관리하고 이해하기 쉽게 만듭니다.

싱글턴 패턴의 단점

  1. 글로벌 상태: 글로벌 접근성이 유리할 수도 있지만 관리하기 어려울 수 있는 글로벌 상태로 이어질 수도 있습니다. 싱글톤에 대한 변경 사항은 게임의 여러 부분에 영향을 미칠 수 있으며 잠재적으로 숨겨진 종속성을 도입하고 디버깅 및 테스트를 더욱 복잡하게 만들 수 있습니다.
  2. 긴밀한 결합: 싱글톤은 코드의 여러 부분 간의 긴밀한 결합으로 이어질 수 있으므로 특정 구성 요소를 독립적으로 리팩터링하거나 수정하기가 더 어려워집니다. 이로 인해 유연성이 떨어지고 모듈성이 떨어지는 코드베이스가 발생할 수 있습니다.
  3. 테스트 과제: 싱글톤 인스턴스는 테스트를 위해 모의 개체로 교체하기 어려울 수 있습니다. 게임이 싱글톤에 크게 의존하는 경우 단위 테스트를 효과적으로 작성하는 능력이 저하될 수 있습니다.
  4. 남용: 모든 것에 싱글톤 패턴을 사용하고 싶지만 신중하게 사용하는 것이 중요합니다. 싱글톤을 과도하게 사용하면 코드베이스가 복잡해지고 유지 관리가 어려워질 수 있습니다. 일부 개체와 구성 요소는 전역적일 필요가 없으며 이를 싱글톤으로 강제하는 것은 비생산적일 수 있습니다.
  5. 초기화 순서: Unity에서는 객체가 초기화되는 순서가 중요할 수 있습니다. 서로 종속된 싱글톤이 여러 개 있는 경우 초기화 순서를 관리하는 것이 복잡한 작업이 될 수 있습니다.

마무리

결론적으로, Unity 싱글턴 패턴은 글로벌 게임 요소를 효율적으로 관리하는 데 유용한 도구가 될 수 있지만 주의 깊게 신중하게 사용해야 합니다. 싱글톤 구현 여부를 결정하기 전에 게임의 특정 요구 사항을 고려하고 장단점을 비교해보세요. 많은 경우 제어 및 리소스 관리를 중앙 집중화하는 데는 좋은 선택이지만 모든 경우에 적용할 수 있는 일률적인 솔루션은 아닙니다.

반응형