상세 컨텐츠

본문 제목

#9. 싱글톤(Singleton)

유니티 학습/세미나 자료

by 남민우_ 2024. 7. 31. 00:42

본문

키워드

메모리 상 단 하나만 존재하는 객체

언제, 어디서든 접근 가능한 오브젝트 제작 시 사용하는 디자인 패턴

 + 디자인패턴이란, 프로그래머들 사이에서 공유되는 코딩 방향성/구조

 

1. 예제 실습

싱글톤 객체를 만들어서 cube 들을 관리하게 해보자.

 

코드

public class SingleTon : MonoBehaviour
{
    public string name;

    void Start()
    {
        Debug.Log("내 이름은 : " + name);
    }
}

먼저 해당 코드를 작성해서 모든 cube 객체에 할당해준다.

각 개체에 이름까지 정해준 모습

 

결과

 

아직까지는 싱글톤 패턴이 적용된 모습이 아니다.

 

그럼 이제 여기서, cube 중 하나를 cube의 우두머리, 'BigCube' 로 지정하고 다른 cube 들이 BigCube가 누구인지 알게 해주고 싶다고 해보자.

여기서는 지난 시간에 배웠던 'Static' 의 개념을 활용할 수 있다.

 

Cube1을 BigCube로 지정한다면 BigCube 데이터에 Cube1을 할당하고, Cube2와 Cube3은 BigCube에 접근하여 정보를 알 수 있도록 한다.

이 때 Cube1이 싱글톤이 적용된 객체가 될 것이다.

 

 + 접근에 대해서는,

싱글톤이 적용된 객체를 알기 위해 모든 객체를  돌아가며 검사하는 것이 아니라, Cube Class 에서 BigCube에 대한 데이터에 접근하는 것이 더 효율적이다.

 

 

이제 BigCube를 직접 지정할 수 있도록 코드를 수정해보자.

코드 수정

    public string name;

    public static SingleTon BigCube;
    public bool isBigCube;

		//Start보다 빠른 시점에 정보를 갖게 하기 위해 Awake() 함수 사용
    void Awake() 
    {
        if(isBigCube)
        {
            BigCube = this;
        }

        Debug.Log("내 이름은 : " + name);

    }
    void Start()
    {
		Debug.Log("BigCube 는 : " + BigCube);
	}

BigCube로 지정하려는 객체에 isBigCube 를 true 로 체크한다

 

실행 결과

서로 다른 세 오브젝트(cube1, cube2, cube3) 가 서로의 정보를 모르는 채로 작동하면서도,

BigCube가 누구인지는( = 싱글톤의 정보) 알고 있는 상황이 만들어진다.

 

이렇게 기본적인 Singleton 활용 예시를 진행해보았다.

 

2. 싱글톤이란?

다시 말하지만, 싱글톤 패턴은 주로 하나만 존재하는 객체에 사용한다.

 ex) GameManager, ScoreManager 등

 

이번에는 점수를 관리하는 ScoreManager를 싱글톤을 활용해 만들어보려고 한다.

 

코드 1

//스코어를 관리하는 Manager Class
public class ScoreManager : MonoBehaviour
{
    private int score = 0;

    public int GetScore() { return score; }

    public void AddScore(int s_Score)
    {
        score += s_Score;
    }
}

 

코드 2

//스코어를 더해주는 Adder 클래스
public class ScoreAdder : MonoBehaviour
{
    public ScoreManager scoreManager;

    // Update is called once per frame
    void Update()
    {
        if(Input.GetMouseButtonDown(0))
        {
            scoreManager.AddScore(5);
            Debug.Log(scoreManager.GetScore());
        }
    }
}

 

코드 3

//스코어를 빼주는 Subtrator 클래스
public class ScoreSubtrator : MonoBehaviour
{
    public ScoreManager scoreManager;

    // Update is called once per frame
    void Update()
    {
        if(Input.GetMouseButtonDown(1))
        {
            scoreManager.AddScore(-2);
            Debug.Log(scoreManager.GetScore());
        }
    }
}

 

에디터 설정

ScoreManager 객체를 ScoreAdder/Subtrator 스크립트에 할당한다

 

실행 결과

좌클릭을 할 때 Adder가 작동, 우클릭을 할 때 Subtrator가 작동한다

의도한 대로 좌클릭을 할 때마다 +5점 씩, 우클릭을 할 때마다 -2점씩 적용된다.

 

하지만 우리가 게임을 만들 때, 점수와 같은 데이터에 접근해야 하는 객체들이 많을 때가 있다.

예시 상황으로 Adder가 2개, Subtrator가 2개라고 가정한다.

이러한 경우에도 일일이 ScoreManager 객체를 Inspector 창을 통해 객체할당을 해주어야 할까? 과정이 너무 번거롭다.

어차피 Manager 객체는 하나만 존재할 건데 더욱 간편하게 할 방법이 없을까?

 

이런 상황에서 싱글톤 패턴을 사용한다.

 

코드 수정 1 : ScoreManager

//ScoreManager 수정

public static ScoreManager instance;

void Awake()
{
   //자기 자신 할당
   instance = this;
}

 

코드 수정 2 : Adder/Subtrator

//ScoreAdder&ScoreSubtrator 수정

//  public ScoreManager scoreManager; <- 삭제

scoreManager => ScoreManager.instance // 수정

이렇게 코드를 수정할 경워, Inspector 창에서 따로 객체를 할당하는 과정을 거치지 않고 코드 상에서 바로 접근 가능하다.

 

실행 결과

점수를 더하고 빼는 과정이 정상적으로 동작

정상적으로 작동하지만,

이 방법은 매우 원시적이라는 문제가 있다.

만약 Manager객체가 없거나, 2개 이상이라면 대처할 방법이 없다.

 

그럼 예상되는 문제 상황들을 가정해보고 하나씩 해결해보기로 하자.

 

 2-1. Manager 가 할당되지 않을 경우

찾는다.

 

코드 1

//ScoreManager 추가

public static ScoreManager GetInstance()
{
   if(instance == null)
   {
      instance = FindObjectOfType<ScoreManager>();
   }

	 return instance;
}

 

코드 2

//ScoreAdder&ScoreSubtrator 수정

	ScoreManager.instance.AddScore() 
	// 등 instance 부분을
	-> ScoreManager.GetInstance().AddScore()
	// 로 대체

 

ScoreManager 에 GetInstance 함수를 만들어 instance를 반환하는 동작을 하게 하는데, 이 과정에서 만약 instance가 null 값이라면 ( = Manager 가 instance에 할당되지 않았다면 ) "FindObjectOfType" 를 통해 ScoreManager 객체를 찾게 한다.

 

실행 결과

정상적으로 작동한다.

 

 2-2. 프로젝트에 Manager 자체가 없을 경우

만든다.

 

코드

public static ScoreManager GetInstance()
{
    if(instance == null)
    {
        instance = FindObjectOfType<ScoreManager>();

        if(instance == null) //Find 를 해도 ScoreManager가 없는 경우
        {
            GameObject container = new GameObject("ScoreManager");
            instance = container.AddComponent<ScoreManager>();
        }
     }

		return instance;
}

 + GameObject container = new GameObject(" ~ ");

 : 새로운 게임 오브젝트를 생성하는 코드.

코드 상에서는 container 라는 이름의 변수로 인식하게 되고, 프로젝트 상에서는 (" ~ ") 안에 해당하는 이름으로 생성된다.

new GameObject 코드는 빈 게임 오브젝트를 생성할 때 쓰여, 일반적으로 형체를 가진 객체를 생성할 때는 Instatntiate() 가 더 알맞다.

 

 + instance = container.AddComponent<ScoreManager>();

 : 새롭게 만든 container 라는 객체 안에 ScoreManager 스크립트를 컴포넌트로 추가하고 이러한 container 객체를 바로 instance에 할당하는 과정

 

실행 결과

게임 플레이 후 ScoreManager가 생성된다

이처럼 Manager 객체가 프로젝트 상에 없더라도 그 상태를 인식하고, 새로운 ScoreManager 객체를 생성한다.

코드 상으로 추가했던 ScoreManager 스크립트까지 정상적으로 컴포넌트로 추가된 모습

 

 

 2-3. Manager가 2개 이상일 경우

하나만 남기고 없앤다.

 

코드

//ScoreManager 수정

private void Start()
    {
        if(instance != null) //인스턴스가 이미 존재할 때
        {
            if(instance != this) //존재하는 인스턴스가 내가 아닐 때
            {
                Destroy(gameObject); //나 자신을 파괴한다
            }
        }
    }

 

일종의 안전장치 역할로서 존재하는 코드로,

정상적으로 Manager가 1개만 만들어진다면 별다른 동작이 시행되지 않는 코드이다.

 

이 코드는

1. 인스턴스가 이미 프로젝트 상에 존재하는지 검사

2. 프로젝트 상에 있는 인스턴스가 자기 자신인지 검사

3. 만약 자기 자신이 아니라면, 자신은 첫번째 이후로 만들어진 객체이므로 스스로를 삭제

 => 프로젝트 상에 단 하나만의 객체를 남기기 위해

 

의 3가지  과정을 거쳐 동작한다.

 

3. 정리

싱글톤 패턴은

1. Static을 사용해서 이루어진다.

2. 단 하나만 존재하는 객체를 만들 때 유용하다.

3. 언제 어디서든 접근 가능한 객체를 만드는데 사용한다.

'유니티 학습 > 세미나 자료' 카테고리의 다른 글

#11. 레이캐스트(RayCast)  (0) 2024.08.01
#10. 코루틴(Coroutine)  (0) 2024.07.31
#8. 리스트(List)  (0) 2024.07.30
#7. 정적 변수/함수(Static)  (0) 2024.07.29
#6. 오버로드(Overload)  (0) 2024.07.28

관련글 더보기