
게임 시작부터 끝까지 사라지지 않는 데이터 관리 시스템이다. 그리고 쉽게 접근이 가능하다
클래스 & 관리자 역할에 적합함static을 사용해 정적 메모리에 할당되므로, 싱글톤 객체가 많을수록 가용 메모리가 적어진다단일 책임 원칙을 위반게임매니저(게임시작,게임중지,게임종료 등), 씬매니저, 사운드매니저, 리소스매니저, 파일매니저 등using UnityEngine.SceneManagement;
static으로 이루어진 함수들을 제공한다. 우리는 이 중 LoadScene() 함수를 사용할 것이다public class SceneChanger : MonoBehaviour
{
//LoadScene 함수의 오버로딩 중, int와 string
public void Load(int sceneNumber)
{
SceneManager.LoadScene(sceneNumber);
}
public void Load(string sceneName)
{
SceneManager.LoadScene(sceneName);
}
}
public class TestGameManager : MonoBehaviour
{
// 씬체인저 인스턴스를 가진다
public TestSceneChanger Scene { get; private set; }
// 스코어는 전역에서 접근 가능
[field: SerializeField] public int Score { get; set; }
// 게임매니저 인스턴스는 static으로 선언
//다른 객체에서 참조 없이 사용 가능하게 만든다
public static TestGameManager Instance { get; private set; }
// 게임매니저 초기화 작업
private void Awake()
{
Init();
}
private void Init()
{
SetSingleton();
// gameObject.이 생략되어 있다
// 게임 오브젝트에는 TestSceneChanger스크립트를 컴포넌트로 추가한다
Scene = GetComponent<TestSceneChanger>();
}
private void SetSingleton()
{
// 이미 인스턴스가 있을 경우,
// 현재 이 인스턴스 파괴
if (Instance != null)
{
Destroy(gameObject);
}
// 없을 경우 생성
else
{
// 현재 이것을 인스턴스로 지정
Instance = this;
// 다른 씬으로 전환 되어도 삭제 되지 않는다
DontDestroyOnLoad(Instance);
}
}
}
A키를 입력 시, 점수 증가1, 2 로 씬을 전환 가능public class TestObject : MonoBehaviour
{
private void Update()
{
if(Input.GetKeyDown(KeyCode.A))
{
// 별도로 GameManager 인스턴스를 참조하는
//변수 생성 없이 접근 및 변경 가능
GameManager.Instance.Score++;
}
if(Input.GetKeyDown(KeyCode.Alpha1))
{
GameManager.Instance.Scene.Load(0);
}
else if(Input.GetKeyDown(KeyCode.Alpha2))
{
GameManager.Instance.Scene.Load(1);
}
}
}

DontDestroyOnLoad()로 삭제가 막히지 않는다면, 다음 씬으로 변경 시 게임매니저가 존재하지 않아 참조 해야 할 인스턴스가 없다

static으로 전역에서 하나의 인스턴스로 존재하게 한다




DontDestroyOnLoad로 달리 분리되는 것을 볼 수 있다
public class TestSingletonBehaviour<T> :MonoBehaviour where T : MonoBehaviour
{
private static T instance;
public static T Instance
{
get
{
if(instance == null)
{
// 프로퍼티를 사용, 인스턴스 호출 시
// 싱글톤이 초기화가 안되어 있으면
// 모든 씬에서 해당 타입을 찾아 초기화
// FindObjectOfType은 유니티 문서 설명에 나와있듯이 느린 함수다
instance = FindObjectOfType<T>();
DontDestroyOnLoad(instance.gameObject);
}
return instance;
}
}
protected void SetSingleton()
{
// 인스턴스가 이미 존재하고, 현재의 인스턴스가 아닐 경우
if(instance != null && instance != this)
{
Destroy(gameObject);
}
else
{
// 없을 경우 해당 컴포넌트를 추가하고 파괴 불가능하게 방지
instance = GetComponent<T>();
DontDestroyOnLoad(gameObject);
}
}
}
SingletonBehaviour 를 상속받아서 간략화 할 수 있다public class TestGameManager : TestSingletonBehaviour<TestGameManager>
{
public TestSceneChanger Scene { get; private set; }
[field: SerializeField] public int Score { get; set; }
private void Awake()
{
Init();
}
private void Init()
{
SetSingleton();
Scene = GetComponent<TestSceneChanger>();
}
}
private로 설정static 선언GetInstance() 함수를 구성. 반환 값이 싱글톤 인스턴스GetInstance() 함수에서 단일 인스턴스가 없을 경우, 인스턴스를 생성하여 정적 변수에 참조GetInstance() 함수에서 단일 인스턴스가 있을 경우, 정적 변수에 참조된 인스턴스를 반환MonoBehaviour를 상속받지 않는 싱글톤public class Singleton
{
private static Singleton instance;
public static Singleton GetInstance()
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
private Singleton() { }
}
게임 오브젝트에 컴포넌트로 추가할 싱글톤을 작성한다public class Singleton : MonoBehaviour
{
private static Singleton instance;
public static Singleton GetInstance()
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
public int score;
// 기본 생성자를 private로 놓아야 외부에서 맘대로 만들 수 없다
private Singleton() { }
}
DataManager에 포함할 수 있다. 씬에는 이 정보를 가지고 동작하는 플레이어 인형을 만들게 된다인벤토리와 장비 탈착 기능을 따로 만든다면, DataManager에 포함시키는것도 좋다public class DataManager
{
// 따로 떨어져있는 싱글톤들의 정보들을 담는 싱글톤으로 DataManager를 쓸 수 있다
private PlayerStat playerStat;
public PlayerStat PlayerStat { get{ return playerStat;} }
private Inventory inventory;
public Inventory Inventory { get{ return inventory;} }
private Equipment equipment;
public Equipment Equipment { get {return equipment;} }
// DataManager 인스턴스가 생성 될 때, 자동으로 값들을 업데이트 할 수 있게 한다
private void Awake()
{
stat = DataManager.GetInstance().PlayerStat;
}
}
public class Tester
{
public void Test()
{
// 싱글톤 생성자를 private로 해야 외부에서의 생성을 막는다
//Singleton num1 = new Singleton();
// 처음에 쓸 때는 인스턴스가 없으면 인스턴스를 만든다. 있으면 있는 애를 가져온다
//Singleton.GetInstance() = 반환형이 Singleton 인스턴스다
// 그래서 .score를 이어 붙일 수 있다
Singleton.GetInstance().score = 10;
Singleton.GetInstance().score++;
Singleton.GetInstance().score = 0;
}
}

드래그&드롭으로 만들 수 있다보니, 하나 이상의 인스턴스를 만드는 것을 막지 못한다Destroy를 활용한다public class GameManager : MonoBehaviour
{
// 스태틱으로 만들어야 하나만 존재 가능
private static GameManager instance;
public int score;
// 유니티에서 Awake는 생성자와 동일한 기능을 한다
private void Awake()
{
CreateInstance();
}
private void CreateInstance()
{
// 인스턴스가 없으면 새로 만든다
if(instance == null)
{
instance = this;
}
else
{
// 이미 인스턴스를 가진 게임 오브젝트가 존재한다는 소리다
// 현재 이 컴포넌트를 부착한 게임오브젝트를 삭제한다
Debug.Log("게임 매니저 인스턴스가 이미 존재합니다.");
Destroy(gameObject);
}
}
}
주의
MonoBehaviour를 상속받는 컴포넌트는static으로 쓸 수 없다

알아두기
GameManager(3)만 남은 이유는 유니티에서 Awake()는 랜덤하게 순서가 진행되서 그렇다. 실행 할 때마다 살아남는 오브젝트가 달라진다
- 해당 설정에서 스크립트 실행 순서를 지정해 줄 수 있으나, 권장하지 않는다
public static GameManager Instance { get { return instance; } }
GameManager 스크립트에서 인스턴스의 프로퍼티를 설정한다Tank 스크립트에 아래와 같이 추가한다shooter.Fire();
// 인스턴스 참조 변수 필요없이, 바로 접근 가능
GameManager.Instance.score++;
if(died)
{
Die();
}
private void Die()
{
GameManager.Instance.score += 10;
}
GameManager 스크립트에서 Instance의 프로퍼티를 수정한다private static GameManager instance;
public static GameManager Instance
{
get
{
// 만약, 하나도 안만들었을 경우를 위한 보험
if (instance == null)
{
GameObject gameObject = new GameObject("GameManager");
instance = gameObject.AddComponent<GameManager>();
}
return instance;
}
}
TIP
- 게임 오브젝트를 생성 하는 데
Instantiate로 만들 수도 있지만,new GameObject("이름")으로 만들 수도 있다
Instance를 호출 할 때, 인스턴스가 없을 경우 생성하도록 한다.게으른 초기화다// 씬 메니저 네임스페이스를 추가해야 된다
using UnityEngine.SceneManagement;
public class SceneChanger : MonoBehaviour
{
private void Update()
{
if (Input.GetKeyDown(KeyCode.Alpha0))
{
// 1. 씬 번호를 적는 법
SceneManager.LoadScene(0);
// 2. 씬 이름을 적는 법
SceneManager.LoadScene("TitleScene");
}
else if (Input.GetKeyDown(KeyCode.Alpha1))
{
SceneManager.LoadScene("SampleScene");
}
}
}
게임을 빌드할 때, 원하는 씬만 넣을 수 있다. 하이라키에서 씬을 드래그&드롭 하면 된다
씬 전환을 위해서 필수로 해줘야 하는 작업이다

이러고 나서 예시 1번 때와 같이 각 씬에 씬 매니저 게임 오브젝트를 추가하고, 숫자 키 0, 1을 누르면 각각의 씬으로 전환 된다
static도 전역인데, 싱글톤을 또 만들어서 사용하는 걸까? 싱글톤은 static과 달리 삭제하고 싶을 때 삭제할 수 있기 때문이다static과 다르게 싱글톤은 인스턴스를 삭제할 수 있다public static void ReleaseInstance()
{
if(instance != null)
{
Destroy(instance.gameObject);
instance = null;
}
}
클론, 영화 미키17처럼 매 장면마다 기존의 플레이어는 삭제하고 새로운 씬에서 플레이어 정보를 불러와서 새로 만든다. 싱글톤으로 구현할 경우 Level1에서 Level2로 갔는데, 다시 Level1 에 갈 경우 자신의 복제를 만나게 된다. 타이틀이나 게임오버 스크린에 필요없는 경우도 이유다싱글톤을 만들고, 그 정보를 기준으로 플레이어를 씬마다 삭제, 생성한다게임 매니저에 플레이어를 가지고 있으면 안된다.// 게임매니저 싱글톤
public TankPlayer tank;
// 몬스턱가 죽었을 때, 플레이어에게 경험치를 준다 해보자
// 몬스터 컴포넌트
// 죽었을때
GameManager.Instance.tank.exp += 10;
tank가 Missing이 된다. 그러고 다시 인게임으로 들어가면 tank는 기존의 것이 아니라 다른 tank가 된다. 경험치도 초기화됨. 이는 참조가 풀리기 때문이다. 씬을 나갔다 들어왔다 할때 게임 오브젝트를 전부 삭제하고 다시 만들다 보니 생기는 문제다

ID가 변경 되는 것을 확인할 수 있다. 서로 다른 객체가 된다private void Start()
{
// 씬이 로드됨과 동시에 게임매니저에 해당 정보를 담는다
GameManager.Instance.tank = this;
}
// 몬스터 컴포넌트
OnDestroy()
{
GameManager.Instance.tank.exp += 10;
}
게임 매니저가 몬스터보다 먼저 삭제될 수 있어서 조심해야된다!싱글톤도 결국 게임 오브젝트다. 몬스터가 죽을 때, 게임 오브젝트가 없을 경우 다시 만들어서 불러온다!해당 어트리뷰트를 추가하고 함수를 작성하면, Awake() 보다 먼저 수행되어 초기화 순서를 정할 수 있다
게임매니저 등의 오브젝트를 Awake() 보다 먼저 생성하게 하고 싶을 경우 쓸 수 있는 방법이다

MonoBehaviour를 상속받지 않는다. static으로 선언public static class Manager
{
// 이것과 같다 : GameManager Game => { get { return GameManager.GetInstance(); } }
public static GameManager Game => GameManager.GetInstance();
// Awake() 전에 해당 함수가 수행된다
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Initialize()
{
GameManager.CreateInstance();
}
}
GameManager 뿐만 아니라, 여러 싱글톤들 또한 같은 방식으로 추가할 수 있다. 싱글톤들을 관리하는 싱글톤인 셈이다
게임매니저를 프리팹화 해두고, Resource 폴더를 만들어 안에 넣어둔다
프리팹을 만들고 불러올 수 있다리소스 폴더는 Asset 폴더 어디든 존재하면, 인식한다!public static void CreateInstance()
{
if(instance == null)
{
GameManager prefab = Resources.Load<GameManager>("GameManager");
instance = Instance(prefab);
DontDestroyOnLoad(instance.gameObject);
}
}
Manager로 모든 싱글톤에 접근 가능하게 바꾸었으니, 수정해본다// 몬스터 컴포넌트
OnDestroy()
{
// 기존 스크립트
// GameManager.Instance.tank.exp += 10;
// 이렇게 쓸 수 있다
Manager.Game.score += 10;
}
Game 뿐만 아니라, Scene,UI,File 등 여러가지로 쉽게 접근 가능하게 바뀌었다프리팹으로 만들어진 게임 매니저에 씬의 게임 오브젝트를 드래그&드롭으로 참조하는 실수를 방지할 수 있다. 무조건 스크립트로 참조하는 기능을 구현해야 한다

하이라키 창의 Tank를 프리팹의 Tank 변수에 드래그&드롭할 수 없다참고
- 싱글톤 패턴
- 유니티 - 프로그래밍패턴 : 싱글톤
- Head First Design Pattern : Singleton