Unity Chapter 5-7. C# 프로그래밍 [중급] (1/2) : 싱글톤
Categories: Unity Lesson 1
인프런에 있는 이제민님의 레트로의 유니티 C# 게임 프로그래밍 에센스 강의를 듣고 정리한 필기입니다. 😀
🌜 [레트로의 유니티 C# 게임 프로그래밍 에센스] 강의 들으러 가기!
Chapter 5. C# 프로그래밍 : 중급 (1/2)
싱글톤
- 디자인 패턴이란
- 프로그래머들 사이에서 공유되는
코드를 작성하는 방향
.
- 프로그래머들 사이에서 공유되는
싱글톤
이란 디자인 패턴 중 하나로서 게임 상이나 메모리 상으로 단 하나만 존재하며 언제 어디서든 사용 가능한 오브젝트를 만들 때 사용되는 패턴
싱글톤 아이디어
📜 Ninja.cs
- 3개의 오브젝트를 만들어준다. (큐브)
- “Ninja Sakura”, “Ninja Sasuke”, “Ninja Naruto”
- 이 📜
Ninja.cs
를 각 오브젝트에 붙여준다. - 각각 슬롯에
ninjaName
을 입력해준다. - Naruto 오브젝트를 단 하나만 존재하는
닌자 왕
으로 설정할 것이기 때문에isKing
에 체크한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Ninja : MonoBehaviour
{
public string ninjaName;
public bool isKing; // 이 오브젝트가 Ninja King 닌자왕인지 체크
void Update()
{
Debug.Log("My Name: " + ninjaName); // 매 프레임마다 자기 자신의 ninjaName 문자열 값을 출력할 것.
}
}
이 3명의 닌자가 닌자왕이 누군지를 알게 하고 싶고 닌자 왕이 단 한명만 존재하게 하고 싶다면
- 첫번째 방법 : 다른 스크립트에서 이 Ninja스크립트가 붙은 3개의 오브젝트들을 순회하여
isKing
값을 검사하여True
인 닌자왕 오브젝트를 찾아낸다. - ✔ 두번째 방법 : 닌자 킹을 위한 단 하나의 변수를 만든다. 👉🏻 이게 바로
싱글톤
개념public static Ninja ninjaKing;
- 이 변수에 모든 Ninja 오브젝트들이 동시에 접근이 가능하며 자기 자신이 닌자 킹인 것을 알게 된 오브젝트가 스스로
ninjaKing = this
하게끔.- 자기 자신을 유일한 static 공간에 집어 넣음으로써 자기 자신을 남들이 바로 접근할 수 있게끔
- 오브젝트들을 순회해서 닌자왕인지 검사할 필요가 없다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Ninja : MonoBehaviour
{
public static Ninja ninjaKing; // 닌자킹 ✨
public string ninjaName;
public bool isKing;
void Start()
{
if(isKing) // 내 isKing 값이 True면
{
ninjaKing = this; // 나 자신을 ninjaKing에 넣는다.
}
}
void Update()
{
Debug.Log("My Name: " + ninjaName + ", Ninja King is" + ninjaKing);
}
}
- 이제 게임을 실행하면 모든 오브젝트가 “각자의 이름, Ninja King is Naruto“를 출력한다.
- 모든 닌자 오브젝트가
ninjaKing
변수를 접근할 수 있다.
싱글톤을 사용해보자
싱글톤은
파일 매니저, 몬스터 매니저, 게임 매니저처럼 단 하나만 존재하는 오브젝트를 손쉽게 쓸 수 있는 필요가 있을때 사용한다. 몬스터 오브젝터는 여러개지만 전체 몬스터를 관리해 줄 오브젝트는 “몬스터 매니저” 단 하나만 있으면 된다. 그럼 몬스터 매니저는 싱글톤
을 사용하면 된다.
📜 ScoreManager.cs
- 점수를 관리해줄 매니저 스크립트.
- Scene 상에서 단 하나만 있으면 된다.
- 😱 오히려 하나가 아니고 두개면 점수가 개별로 기록되서 게임이 망가질 수 있다.
- “Score Manager”라는 빈 오브젝트를 만들어 붙여준다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScoreManager : MonoBehaviour
{
private int score = 0;
public int GetScore()
{
return score;
}
public void AddScore(int newScore)
{
score = score + newScore;
}
}
📜 ScoreAdder.cs
- 스페이스바를 누르면 점수(score)를 증가시키는 스크립트
- Score Manager 오브젝트를 알고 있어야 한다.
- 그래야 제어 가능
public ScoreManager socreManager;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScoreAdder : MonoBehaviour
{
public ScoreManager socreManager;
void Update()
{
if(Input.GetMouseButtonDown(0)) // 마우스 좌클 누르는 순간
{
scoreManager.AddScore(5); // score에 5점을 더한다.
Debug.Log(scoreManager.GetScore()); // score 출력
}
}
}
📜 ScoreSubtractor.cs
- 스페이스바를 누르면 점수(score)를 감소시키는 스크립트
- Score Manager 오브젝트를 알고 있어야 한다.
- 그래야 제어 가능
public ScoreManager socreManager;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScoreSubtractor : MonoBehaviour
{
public ScoreManager socreManager;
void Update()
{
if(Input.GetMouseButtonDown(1)) // 마우스 우클 누르는 순간
{
scoreManager.AddScore(-2); // score에 2점을 빼준다.
Debug.Log(scoreManager.GetScore()); // score 출력
}
}
}
- 점수를 관리하는 ScoreManager는 단 하나여야하지만 사실 게임하면서 Score를 증감시킬 요소들은 정말 많을 수 있다.
- 그러니 컴포넌트들을 여러개 만들어
📜ScoreAdder.cs
와📜ScoreSubtractor.cs
붙여보자 - 그리고
scoreManager
슬롯에 ScoreManager 스크립트가 붙은 단 하나의 오브젝트를 가져와 드래그 앤 드롭 해준다.
- 그러니 컴포넌트들을 여러개 만들어
그러나 오브젝트가 굉장히 많을 땐 일일이 ScoreManager를 드래그 앤 드롭으로 슬롯에 넣어주기가 힘들다. 어차피 ScoreManager는 하나이므로
public ScoreManager socreManager
이렇게 참조할 변수를 따로 만들 필요 없이, 슬롯에서 연결해줄 필요없이, 그냥 `싱글톤`을 사용하여 바로 가져다 쓸 수 있다면 더 편할 것.
싱글톤
: 게임 시작 하자마자(Awake) 유일한 존재인 자기 자신을 모두가 자신의 클래스 이름으로 바로 접근 가능하고 공유할 수 있는 static 변수에 집어넣음
📜 ScoreManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScoreManager : MonoBehaviour
{
public static ScoreManager instance; // 자기 자신을 참조할 static 변수.
private int score = 0;
void Awake()
{
instance = this; // 자기 자신을 static 변수 instance가 참조하게끔
}
public int GetScore()
{
return score;
}
public void AddScore(int newScore)
{
score = score + newScore;
}
}
- public
static
ScoreManager instance;- instance 변수는 static변수고 자기 자신을 참조하게 할 것이다.
- static 변수라 슬롯 연결 없이 이 변수를 사용하는 곳에서 그냥 클래스 이름으로 ScoreManager.instance 로 사용할 수 있다.
void Awake()
- Start 메세지보다 더 빨리 호출되므로 다른 스크립트들의 Start보다 더 빨리 실행되야 하는 내용이 있으면 Awake에 구현하면 된다.
- ScoreManager 오브젝트가 만들어지자마자 실행됨
instance = this;
- ScoreManager 오브젝트(유일함)인 자기 자신을 참조하게끔.
📜 ScoreAdder.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScoreAdder : MonoBehaviour
{
// public ScoreManager socreManager; 이 과정은 이제 필요가 없다. 슬롯에서 연결해줄 필요도 없다. 바로 ScoreManager 이름으로 접근 가능하기 때문에.
void Update()
{
if(Input.GetMouseButtonDown(0)) // 마우스 좌클 누르는 순간
{
ScoreManager.instance.AddScore(5); // score에 5점을 더한다.
Debug.Log(ScoreManager.instance.GetScore()); // score 출력
}
}
}
- 이제 일일이
scoreManager
변수의 슬롯에 ScoreManager 오브젝트를 연결해줄 필요가 없다.- public ScoreManager socreManager; 는 필요가 없음!
- 바로 그냥
ScoreManager
클래스 이름으로 ScoreManager 객체에 접근이 가능하다.- ScoreManager.instance.AddScore(5);
- ScoreManager.instance.GetScore()
ScoreManager는 유일한 오브젝트임과 동시에 다른 오브젝트들에서 많이 가져다 쓰는 오브젝트 이기 때문에 일일이 슬롯에 연결지어 줄 필요 없이 바로 가져다 쓸 수 있도록 static
변수에 ScoreManager 오브젝트를 참조시켜 놓는 것이다. 이를 싱글톤
이라고 한다.
static 함수를 사용한 싱글톤
static 함수를 통해서 자기 자신인 오브젝트를 참조하는 static 변수를 리턴하도록 해보자.
📜 ScoreManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScoreManager : MonoBehaviour
{
public static ScoreManager GetInstance()
{
if (instance == null) // ScoreManager 오브젝트를 만들어 놓고 이 스크립트를 안붙였거나 or ScoreManager 오브젝트가 아예 없는 상태일 때 해당됨
{
instance = FindObjectOfType<ScoreManager>();
}
return instance;
}
private static ScoreManager instance; // private로 바꿈. 이제 바로 위에 구현한 GetInstance로 가져와야 한다.
private int score = 0;
void Awake()
{
instance = this;
}
public int GetScore()
{
return score;
}
public void AddScore(int newScore)
{
score = score + newScore;
}
}
📜 ScoreAdder.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScoreAdder : MonoBehaviour
{
void Update()
{
if(Input.GetMouseButtonDown(0)) // 마우스 좌클 누르는 순간
{
ScoreManager.GetInstance().AddScore(5);
Debug.Log(ScoreManager.GetInstance().GetScore());
}
}
}
- 이제 ScoreManager를 사용하는 쪽에선 “ScoreManager.GetInstance().AddScore(5)” 이렇게 ScoreManager 오브젝트를 사용할 수 있다.
- 아직
instance
에 할당 된게 없다면 씬 상에 있는 모든 오브젝트들을 뒤져서 싱글 오브젝트인ScoreManager
를 찾은 다음instance
에 붙여주도록if (instance == null)
- instance에 참조된 오브젝트가 없다면
- Awake 함수 (
instance = this
)를 못 만난거니까, 즉 이런 상황은 ScoreManager 오브젝트에 아직 이 📜ScoreManager.cs 스크립트를 안붙였거나 or 아예 ScoreManager가 없는 상황에 해당함.(이 경우는 밑에 “지연 생성” 챕터에서 필기)
- Awake 함수 (
- instance에 참조된 오브젝트가 없다면
instance =FindObjectOfType<ScoreManager>();
- 씬 상의 모든 오브젝트들을 뒤져서 직접 ScoreManager 오브젝트를 찾아서 instance에 리턴해준다.
- 그럼 이제 ScoreAdder, ScoreSubtractor 오브젝트에서 static변수인 instance를 통해 ScoreManager를 사용할 수 있다.
- 씬 상의 모든 오브젝트들을 뒤져서 직접 ScoreManager 오브젝트를 찾아서 instance에 리턴해준다.
지연 생성
📜 ScoreManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScoreManager : MonoBehaviour
{
public static ScoreManager GetInstance()
{
if (instance == null)
{
instance =FindObjectOfType<ScoreManager>();
if(instance == null) // 그래도 못찾았다면 만들자
{
GameObject container = new GameObject("Score Manager"); // 빈 게임 오브젝트 생성할때만 가능. 일반적으로는 Instantiate 사용
instance = container.AddComponent<ScoreManager>(); // ScoreManager 컴포넌트(스크립트)를 붙여줌
}
}
return instance;
}
private static ScoreManager instance;
private int score = 0;
void Awake()
{
instance = this;
}
public int GetScore()
{
return score;
}
public void AddScore(int newScore)
{
score = score + newScore;
}
}
싱글톤의 또다른 특징 중 하나 👉🏻 사용 안할 땐 없다가 사용 하려고 할 때, 즉 그 유일한 권좌에 아무도 없는 상황인 그 때 생성됨
if (instance == null)
{
instance =FindObjectOfType<ScoreManager>();
if(instance == null) // 그래도 못찾았다면 만들자
{
GameObject container = new GameObject("Score Manager");
instance = container.AddComponent<ScoreManager>();
}
}
FindObjectOfType<ScoreManager>();
에서도 리턴되는게 없었으면 아예 ScoreManager 오브젝트가 없는 상황.- 그럼 ScoreManager 컴포넌트를 붙여줄 오브젝트를 직접 만들자
GameObject container = new GameObject("Score Manager");
- “Score Manager”라는 이름의 빈 오브젝트를 만든다.
- 오브젝트 생성하는 방법
Instantiate
new GameObject
👉🏻 빈 오브젝트만 가능하다.
- 오브젝트 생성하는 방법
- “Score Manager”라는 이름의 빈 오브젝트를 만든다.
instance = container.AddComponent<ScoreManager>();
- AddComponent : 컴포넌트를 붙여줌과 동시에 리턴한다.
- ScoreManager 스크립트를 container가 참조하는 오브젝트에 붙여준다.
- AddComponent : 컴포넌트를 붙여줌과 동시에 리턴한다.
- 게임을 실행했을땐 ScoreManager 오브젝트가 없지만 마우스 좌클 우클을 누르면 ScoreAdder와 ScoreSubtractor가 실행되면서 GetInstance()를 호출하고 ScoreManager 오브젝트가 없으니 ScoreManager 오브젝트가 생성된다
오브젝트가 2개라면 하나 파괴하기
ScoreManager 오브젝트는 단 하나만 있어야 한다. 두개 이상이면 점수가 개별로 기록되서 게임이 망가질 수 있다. 매니저 오브젝트들은 단 하나만 있어야 하므로 두개라면 다른 하나는 파괴해주는 과정을 Start
함수 안에 넣어주자
이미 누군가가
static
변수인 instance에 할당되있으면 난 첫번째가 아니라 두번째인 것이니 나는 파괴되야 한다.
ScoreManager 스크립트가 두 개의 오브젝트에 붙어 있으면 다른 하나 오브젝트는 파괴해야한다.
📜 ScoreManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScoreManager : MonoBehaviour
{
public static ScoreManager GetInstance()
{
if (instance == null)
{
instance =FindObjectOfType<ScoreManager>();
if(instance == null)
{
GameObject container = new GameObject("Score Manager");
instance = container.AddComponent<ScoreManager>()
}
}
return instance;
}
private static ScoreManager instance;
private int score = 0;
void Start()
{
if(instance != null) // 비어 있지 않은데
{
if(instance != this) // 걔가 내가(오브젝트) 아니면
{
Destroy(gameObject); // 이 스크립트가 붙어있는 오브젝트 파괴 'gameObject' : 이 컴포넌트가 연결된 오브젝트를 뜻함
}
}
}
public int GetScore()
{
return score;
}
public void AddScore(int newScore)
{
score = score + newScore;
}
}
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
Leave a comment