
Singleton Pattern싱글톤 패턴은 씬에 단 하나만 존재하며, 전역적인 접근이 가능한 클래스를 구현할 때 사용되는 디자인 패턴이다. 팀 프로젝트나, 개인 프로젝트에서 매니저 클래스를 구현할 때 자주 사용되었으며, 사실 상 게임을 제작하는 프로그래머 입장에서는 가장 친숙한 패턴이 아닐까 싶다.
Singleton<T>싱글톤 패턴을 적용시킨다고 하면, 미리 제네릭으로 구현한 싱글톤을 상속하도록 하는 경우가 대부분이다. 제네릭으로 구현하는 방식은 사람마다 다르지만, 거의 아래와 비슷한 형식으로 구현된다.
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
private static T _instance;
public static T Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType<T>();
// 해당 코드는 자동 생성 싱글톤 방식으로(디버깅이 어려움)
// 명시적으로 씬에 배치하는 것을 선호하는 경우 굳이 작성하지 않는다.
if (_instance == null && Application.isPlaying)
{
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);
}
}
}
위 코드는 이번 프로젝트에서 제네릭으로 구현한 싱글톤이다. 이렇게 만들어놓으면 상속을 통해 싱글톤의 성질을 가지는 매니저 클래스를 간단하게 만들 수 있다.
public class DataManager : Singleton<DataManager>
{
}
하지만 이번 프로젝트에서 싱글톤 패턴을 사용할 때 문제가 하나 생겼는데, 바로 DontDestroyOnLoad 이다.
DontDestroyOnLoadDontDestroyOnLoad는 씬이 전환되어도 오브젝트를 파괴하지 않고 유지시키고자 할 때 사용한다. 게임 매니저와 같이 여러 씬(모든 씬)에서 공통으로 유지되어야하는 매니저라면 해당 코드를 사용하여 계속 존재하도록 만들 수 있는 것이다.
하지만 만약 특정 씬에서만 전역적인 접근을 위해 사용되는 용도라면 DontDestroyOnLoad 가 필요하지않다. 예를 들면 씬 전용 매니저인 LevelManager나 씬 전용 UI, Utility Class가 대표적이다. 이런 경우는 중복 호출 방지를 위해 씬이 전환될 경우 파괴되는 것이 안전할 것이다.
따라서 싱글톤을 구현할 때 DontDestroyOnLoad 까지 상속받을 것인지에 대한 처리가 함께 되어있으면 좋겠다는 생각을 했고, 간단하게 bool 타입 플래그를 추가하여 구현해보았다.
Singleton<T> with flagpublic class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
private static T _instance;
public static T Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType<T>();
if (_instance == null && Application.isPlaying)
{
var obj = new GameObject(typeof(T).Name);
_instance = obj.AddComponent<T>();
}
}
return _instance;
}
}
// 파괴 여부 옵션 플래그 : 기본값은 true로 설정
protected virtual bool ShouldDontDestroy => true;
protected virtual void Awake()
{
if (_instance == null)
{
_instance = this as T;
if(ShouldDontDestroy)
{
DontDestroyOnLoad(gameObject);
}
}
else if (_instance != this)
{
Destroy(gameObject);
}
}
}
위와 같이 플래그와 함께 구현을 하면 DontDestroyOnLoad 이 필요 없는 클래스의 경우 다음과 같이 작성하면 된다.
public class LevelManager : Singleton<LevelUIManager>
{
protected override bool ShouldDontDestroy => false;
...
}
이러면 특정 씬에서 전역적인 접근을 가지지만, 씬이 전환되면 파괴되도록 만들 수 있다.