Unity Chapter 5-7. C# 프로그래밍 [중급] (1/2) : 싱글톤

Date:     Updated:

카테고리:

태그:

인프런에 있는 이제민님의 레트로의 유니티 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명의 닌자가 닌자왕이 누군지를 알게 하고 싶고 닌자 왕이 단 한명만 존재하게 하고 싶다면

  1. 첫번째 방법 : 다른 스크립트에서 이 Ninja스크립트가 붙은 3개의 오브젝트들을 순회하여 isKing값을 검사하여 True인 닌자왕 오브젝트를 찾아낸다.
  2. ✔ 두번째 방법 : 닌자 킹을 위한 단 하나의 변수를 만든다. 👉🏻 이게 바로 싱글톤 개념
    • 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가 없는 상황에 해당함.(이 경우는 밑에 “지연 생성” 챕터에서 필기)
    • instance =FindObjectOfType<ScoreManager>();
      • 씬 상의 모든 오브젝트들을 뒤져서 직접 ScoreManager 오브젝트를 찾아서 instance에 리턴해준다.
        • 그럼 이제 ScoreAdder, ScoreSubtractor 오브젝트에서 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"); // 빈 게임 오브젝트 생성할때만 가능. 일반적으로는 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”라는 이름의 빈 오브젝트를 만든다.
      • 오브젝트 생성하는 방법
        1. Instantiate
        2. new GameObject 👉🏻 빈 오브젝트만 가능하다.
  • instance = container.AddComponent<ScoreManager>();
    • AddComponent : 컴포넌트를 붙여줌과 동시에 리턴한다.
      • ScoreManager 스크립트를 container가 참조하는 오브젝트에 붙여준다.
  • 게임을 실행했을땐 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;
    }
}


🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기

Unity Lesson 1 카테고리 내 다른 글 보러가기

댓글 남기기