싱글톤 패턴은 초보 개발자들이 가장 많이 쓰는 디자인 패턴이 아닐까 싶다.
클래스 구조를 짜다보면 다른 클래스의 함수를 사용해야 할 수도 있고, 전체 클래스들이 공유하는 전역변수가 필요할 수도 있다.
규모가 작은 게임에서는 public으로 변수를 만든 다음 유니티 Inspector에서 드래그 앤 드롭으로 의존관계를 만들수도 있지만,
게임이 점점 복잡해진다면 다른 클래스를 참조하는 변수가 너무 많이 만들어져도 문제이다.
public 변수를 만든거 자체가 메모리를 사용하게 되는 것이고, 만약 클래스를 변경하거나 삭제할 때 일일이 다 바꿔줘야 해서 개발할 때 시간낭비가 될 수도 있다.
클래스 구조에서 공통적으로 사용하는 전역변수나 리소스, 데이터, 아니면 게임 전체를 관장하는 매니저 클래스는 싱글톤으로 따로 빼는게 도움이 될 수도 있다.
싱글톤을 이용하면 임의의 클래스에서 내가 만든 싱글톤 인스턴스를 사용할 수 있다.
유니티에서 싱글톤을 사용하는 방법은 2가지가 있다.
1. 이 싱글톤 클래스가 여느 유니티 c#스크립트처럼 Monobehaviour를 상속받아서 Hierarchy에 존재하게 하는 것.
2. Monobehaviour을 상속받지 않고 Hierarchy에 존재하지 않게 하는 것.
본 예제에서는 게임시작, 일시정지 등 게임의 흐름을 관장하는 GameMgr이라는 클래스를 예시로 들겠다.
1번째 방법(Monobehaviour를 상속받아서 Hierarchy에 존재하게 되는 싱글톤 인스턴스)
using System.Collections; using System.Collections.Generic; using UnityEngine; public class GameMgr : MonoBehaviour { //게임매니저의 인스턴스를 담는 전역변수(static 변수이지만 이해하기 쉽게 전역변수라고 하겠다). //이 게임 내에서 게임매니저 인스턴스는 이 instance에 담긴 녀석만 존재하게 할 것이다. //보안을 위해 private으로. private static GameMgr instance = null; void Awake() { if (null == instance) { //이 클래스 인스턴스가 탄생했을 때 전역변수 instance에 게임매니저 인스턴스가 담겨있지 않다면, 자신을 넣어준다. instance = this; //씬 전환이 되더라도 파괴되지 않게 한다. //gameObject만으로도 이 스크립트가 컴포넌트로서 붙어있는 Hierarchy상의 게임오브젝트라는 뜻이지만, //나는 헷갈림 방지를 위해 this를 붙여주기도 한다. DontDestroyOnLoad(this.gameObject); } else { //만약 씬 이동이 되었는데 그 씬에도 Hierarchy에 GameMgr이 존재할 수도 있다. //그럴 경우엔 이전 씬에서 사용하던 인스턴스를 계속 사용해주는 경우가 많은 것 같다. //그래서 이미 전역변수인 instance에 인스턴스가 존재한다면 자신(새로운 씬의 GameMgr)을 삭제해준다. Destroy(this.gameObject); } } //게임 매니저 인스턴스에 접근할 수 있는 프로퍼티. static이므로 다른 클래스에서 맘껏 호출할 수 있다. public static GameMgr Instance { get { if (null == instance) { return null; } return instance; } } public void InitGame() { } public void PauseGame() { } public void ContinueGame() { } public void RestartGame() { } public void StopGame() { } }
사용 예시 :
using System.Collections; using System.Collections.Generic; using UnityEngine; public class UIMenu : MonoBehaviour { //pause 버튼을 누르면 게임 일시정지 public void OnClickBtnPause() { GameMgr.Instance.PauseGame(); } }
2번째 방법 (Monobehaviour를 상속받지 않아서 Hierarchy에 존재하지 않게 만드는 방법)은 아래와 같다.
using System.Collections; using System.Collections.Generic; using UnityEngine; public class GameMgr { //게임매니저의 인스턴스를 담는 전역변수(static 변수이지만 이해하기 쉽게 전역변수라고 하겠다). //이 게임 내에서 게임매니저 인스턴스는 이 instance에 담긴 녀석만 존재하게 할 것이다. //보안을 위해 private으로. private static GameMgr instance; //게임 매니저 인스턴스에 접근할 수 있는 프로퍼티. static이므로 다른 클래스에서 맘껏 호출할 수 있다. public static GameMgr Instance { get { if(null == instance) { //게임 인스턴스가 없다면 하나 생성해서 넣어준다. instance = new GameMgr(); } return instance; } } //생성자를 하나 만들어줘서 원하는 세팅을 해주면 좋다. public GameMgr() { } public void InitGame() { } public void PauseGame() { } public void ContinueGame() { } public void RestartGame() { } public void StopGame() { } }
사용법은 1번째 방법과 같다.
Monobehaviour를 상속받지 않는 경우의 좋은 점 :
1. 우선 씬 이동시의 신경을 안 써도 된다. 씬 이동을 했을 때 그 씬의 Hierarchy에 같은 싱글톤 클래스가 존재한다면, 기존씬에서 쓰던 인스턴스를 계속 쓸지, 아니면 새로운 씬의 Hierarchy에 있는 인스턴스를 쓸지를 선택해야한다(보통은 기존 씬의 것을 사용하는 것 같다). 하지만 Monobehaviour를 상속받지 않고 메모리상에만 존재하게 한다면 이런 선택의 경우를 고려하지 않아도 돼서 편하다.
2. 현재 상용버전의 유니티 오브젝트라면 모두 갖게 될 Transform 컴포넌트를 안 가져도 되니 쓸데 없는 메모리 점유를 안해도 된다는 것이다(정말 미미한 극세사 도움이겠지만..). 하지만 눈에 안 보이면 헷갈릴 수도 있으니 보통은 1번째 방법을 사용한다.
싱글톤의 문제점 :
하나의 싱글톤에 너무 많은 기능, 너무 많은 데이터를 넣으면, 훗날 프로젝트의 규모가 커졌을 때 절망을 느낄 수 있다. 우선 하나의 클래스가 하나의 일을 한다는 Single Responsibility Principle과, 수정에는 닫히고 확장에는 열려야 한다는 Open-Closed Principle 등의 원칙을 어길 수 있으며, 클래스들과 싱글톤, 그리고 싱글톤이 가지고 있는 클래스 인스턴스들간의 의존도가 복잡해져서 게임 업데이트를 하려면 게임 전체를 갈아엎어야 될 수도 있다. 또한 싱글톤은 게임이 종료되지 않는 한 계속해서 메모리를 점유하고 있으므로, 싱글톤의 남발은 메모리를 비효율적으로 사용하게 한다.
'유니티 > 디자인패턴' 카테고리의 다른 글
유니티 디자인패턴 - 컴포지트 (Unity Design Patterns - Composite) (0) | 2019.08.05 |
---|---|
유니티 디자인패턴 - 커맨드 (Unity Design Patterns - Command) (1) | 2019.08.01 |
유니티 디자인패턴 - 메멘토 (Unity Design Patterns - Memento) (0) | 2019.07.30 |
유니티 디자인패턴 - 팩토리(심플팩토리, 팩토리 메소드, 추상팩토리) (Unity Design Patterns - Factory) (2) | 2019.07.28 |
유니티 디자인패턴 - 옵저버 (Unity Design Patterns - Observer) (0) | 2019.07.17 |