예제나 개인 프로젝트, 팀 프로젝트를 진행하면서 싱글 톤 구현은 거의 빠진 적이 없다.
싱글 톤에 대해서 정확하게 짚어보고, 상속을 통핸 싱글톤 구현에 코드를 소개한다.
싱글 톤이란 전체에서 단 하나의 인스턴스만 존재하도록 보장하는 디자인 패턴이다.
즉, 어디서든 접근이 가능하고,
인스턴스를 중복 생성할 수 없다.
전역적으로 관리되는 객체에 사용하는 디자인 패턴이다.
그렇다면 왜 게임 개발에서 싱글 톤이 필요할까?
게임은 다른 개발과 달리,
상태 중심적인 구조를 가지고 있다.
달리 말하면 게임은 단순히 버튼 누르고 반응하는 웹/앱과 달리 다음과 같은 복합적인 상태 관리를 요구한다.
즉, "지금 게임이 어떤 상황인지", "무슨 이벤트가 진행 중인지", "무엇을 보여줘야 할지" 등, 여러 가지 시스템이 동시에 상호작용하는 구조다.
그렇기에 언제, 어디서든 접근해야 하는 중앙 집중형 매니저가 필요하다.
만일 이런 매니저들이 여러 개 만들어지면 동기화 문제, 충돌, 상태 꼬임이 생기기 때문에, 하나만 존재하게 강제하는 것이 싱글톤의 역할이다.
Unity에서 싱글 톤 구현은 다음과 같다.
public class GameManager : MonoBehaviour
{
public static GameManager instance; //정적 인스턴스
void Awake()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject); // 씬이 바뀌어도 유지
}
else
{
Destroy(gameObject); // 중복 인스턴스 제거
}
}
}
위 클래스가 붙은 오브젝트가 씬에 존재하거나 생성될 때를 가정해보자.
Unity 내부에서는 Awake() → Start() 순서로 콜백을 호출할 것이다.
Awake() 에서 조건문을 통해
정적(static) 변수 instance가 null이라면, 이 인스턴스가 첫 번째 생성된 싱글톤임을 의미하게 되고 따라서 instance = this; 로 자기 자신을 싱글 톤으로 등록한다.
DontDestroyOnLoad(gameObject); 코드는 간단한 예제 게임에는 생략되지만, 씬이 여러 개인 복잡한 게임에서는 씬 전환 시 오브젝트가 파괴되지 않도록 유지해 준다.
즉, 생명주기를 고정한다.
모든 싱글 톤에 필요한 것은 아니다. 매 씬마다 게임 매니저를 새로 구현하는 상황이나 단일 씬 게임의 경우 필요하지 않은 코드이다.
조건문의 결과가 false가 되어 else 문으로 간다면,
즉 두 번쨰 인스턴스가 생성되면 중복 인스턴스를 제거함으로써
오직 하나의 GameManager만 존재하게 된다.
이러한 싱글 톤 구현을 통해
다른 스크립트에서 다음과 같이 instance 를 통해 싱글 톤 객체를 직접 사용할 수 있다.
GameManager.instance.AddScore();
GameManager.instance.GameOver();
instance는 static 변수이기 때문에 클래스명으로 접근 가능하고
이 방식은 어디서든 접근할 수 있기 때문에 매우 강력하다.
싱글 톤이 필요한 클래스는 보통 그 클래스 내부에서 만든다.
하지만 이를 분리하여 객체지향적으로 상속을 통해 싱글 톤을 구현할 수 있다.
아래는 싱글 톤이 필요한 클래스들에게 상속할 싱글톤 클래스이다.
using UnityEngine;
public abstract class MonoSingleton<T> : MonoBehaviour where T : MonoSingleton<T>
{
private static T _instance;
public static T Instance
{
get
{
if (_instance) return _instance;
// 씬에서 찾아보고 없으면 생성 (필요 없다면 이 부분 삭제)
_instance = FindObjectOfType<T>();
if (_instance == null)
{
var go = new GameObject(typeof(T).Name);
_instance = go.AddComponent<T>();
}
return _instance;
}
}
protected virtual void Awake()
{
if (_instance != null && _instance != this)
{
Destroy(gameObject); // 중복 방지
return;
}
_instance = (T)this;
// DontDestroyOnLoad(gameObject); // 싱글톤을 씬 전환 시 보존하고 싶다면 주석 해제
}
}
이렇게 만들어 두면, 싱글 톤으로 구현할 클래스에 상속만 하면 된다.