Posted in: Unity, 메서드 & 팁 정리

Singleton | 오직 하나만 존재하는 게임 오브젝트 싱글톤

디자인 패턴에 대한 간단한 설명


디자인 패턴이란 프로그래머들 사이에서 공유되는 어떠한 코드를 작성하는 방향입니다.

그리고 싱글톤은?


게임이나 메모리상에 단 하나만 존재하고 언제 어디서든 접근 가능한 오브젝트를 만들 때 사용되는 패턴입니다.

예제로 알아보는 원리


한 집에 3명이 살고있습니다. 이중에 한 명이 집주인입니다. 각자 집주인을 알고있다는 상황을 만들어봅시다.

public class Bloger : MonoBehaviour
{
    public string humanName;
    public bool isOwner;

    void Update()
    {
        Debug.Log("My name is " + humanName);
    }
}

게임 오브젝트 3개를 만들었습니다. 이름은 각각 Angle, Minsu, Suji입니다. 세 오브젝트에 스크립트를 붙여주고 이름을 넣었습니다.

Angle 오브젝트만 isOwner 체크했습니다. Angle이 집주인이라는 얘기죠. 하지만 isOwner 체크했다고 다른 오브젝트들이 집주인이 누구인지 알아낼 수 있을까요?

현재 상황에서는 Angle 본인만 집주인이 누구인지 아는 상황이겠죠. 왜냐하면 Angle 게임 오브젝트가 가지고 있는 스크립트를 다른 게임 오브젝트가 공유하고 있지 않기 때문입니다.

public class Bloger : MonoBehaviour
{
    public static Bloger owner;

    public string humanName;
    public bool isOwner;

    void Start()
    {
        if(isOwner)
        {
            owner = this;
        }
    }

    void Update()
    {
        Debug.Log("My name is " + humanName + " Owner is " + owner.name);
    }
}

이제 집주인이 누구인지 알려주기 위해 스크립트를 수정하겠습니다.

코드 설명을 하자면 public static Bloger owner는 스태틱 변수로 만들었기 때문에 해당 스크립트를 가지는 모든 오브젝트는 owner 하나의 변수만 가집니다.

그리고 Update 함수에서 Debug.Log를 통해 각 게임 오브젝트가 알고 있는 onwer의 이름을 출력해보겠습니다.

각자 Angle을 Owner라고 알고있군요. static 형식의 변수는 같은 스크립트를 가진 게임 오브젝트들이 아무리 많아도 하나의 변수만 가집니다. 위 상황으로 보자면 Bloger 스크립트를 컴포넌트한 게임 오브젝트가 100개가 넘어도 static 변수는 하나인거죠.

하지만 엄밀히 말하면 이것이 싱글톤은 아닙니다. 이런식으로 돌아간다는 걸 보여주는 예제입니다.

싱글톤이 되기 위한 조건


싱글톤은 주로 GameManager, MonsterManager, 와 같은 단 하나만 존재하고 다른 게임 오브젝트가 손쉽게 쓸 수 있는 오브젝트를 만들 때 사용합니다.

싱글톤으로 만든 스크립트가 Scenes 상에 무조건 하나 존재한다는 확증이 있어야 합니다.

만약에 싱글톤에 아직 오브젝트가 할당되지 않았다면 반드시 찾아주어야 하고 오브젝트가 2개 이상 존재한다면 하나만 남기고 파괴해 주어야 합니다.

그리고 싱글톤의 특징 중 하나는 사용하려고 할 때 만들어집니다. 처음에는 없는 상태로 있다가 사용하려고 할 때 만든다는 말입니다. 이것을 지연 생성이라고 합니다.

위 조건에 부합하는 싱글톤을 만들어보자


public class SingleTone : MonoBehaviour
{
    public static SingleTone instance;
    
    void Awake()
    {
        instance = this;
    }
}

static 변수로 만들어서 게임상에 단 하나만 존재하는 변수로 활용할 수 있습니다.

그리고 Awake 함수(Start 함수보다 더 빨리 실행되는 함수)로 게임이 실행되자마자 자기 자신을 변수에 넣어줍니다. 이렇게 되면 다른 오브젝트가 검색을 통해 싱글톤 변수를 찾을 필요 없이 바로 사용할 수 있습니다.

하지만 위 싱글톤은 Scenes 상에 무조건 하나 존재한다는 확증이 없습니다.

instance 변수에 오브젝트가 할당되어 있지 않다면 오브젝트를 찾아주어야 하고 오브젝트가 2개 이상 존재한다면 오브젝트 2개 중에 하나는 파괴해 주어야 하는 코드도 작성해 주어야겠죠.

public class SingleTone : MonoBehaviour
{
    public static SingleTone GetInstance()
    {
        if(instance == null)
        {
            instance = FindObjectOfType<SingleTone>();
        }
        
        return instance;
    }
    
    private static SingleTone instance;
    
}

Awake 함수로 통해서 instance 변수에 오브젝트를 넣어줬었지만 Awake 함수는 지우고 static 함수 GetInstance를 만들어서 instance를 넘겨주게 만들었습니다.

덤으로 만약에 instance가 없다면 FindObjectOfType(Scenes 상에 모든 오브젝트를 뒤져서 <여기 작성된 Type>를 찾아준다)를 사용해서 대입해 주고 리턴해줍니다.

코드를 이렇게 작성하면 다른 스크립트에서 싱글톤을 사용할 때 instance 변수는 private이기 때문에 GetInstance 함수를 통해 싱글톤을 불러와야 합니다.

위 코드는 누군가가 GetInstance 함수를 실행시키면 instance 변수에 오브젝트가 할당되어 있지 않아도 게임 오브젝트들을 뒤져서 할당시켜 주겠죠.

하지만 만약에 게임상에 싱글톤 스크립트를 가진 게임 오브젝트가 존재하지 않는다면 위 코드로는 오류가 생깁니다. 사용하려고 할 때 만들어진다는 싱글톤의 특징 중 하나에 부합하지 않는 코드라는 거죠.

코드를 좀 더 추가해 줍시다.

public class SingleTone : MonoBehaviour
{
    public static SingleTone GetInstance()
    {
        if(instance == null)
        {
            instance = FindObjectOfType<SingleTone>();
            
            if(instance == null)
            {
                GameObject container =
                    new GameObject("SingleTone");
                
                instance = 
                    container.AddComponent<SingleTone>();
            }
        }
        
        return instance;
    }
    
    private static SingleTone instance;
    
}

싱글톤 스크립트를 가진 게임 오브젝트가 존재하지 않는다면 FindObjectOfType 함수로 아무리 뒤져도 instance는 여전히 null(아무것도 없는 상태)일 것입니다.

그러므로 instance가 null이라면 비어있는 게임 오브젝트를 생성해 주어서 AddComponent로 SingleTone 스크립트를 붙였습니다.

그리고 instance에 방금 만든 게임 오브젝트인 container을 대입시키면 싱글톤 게임 오브젝트가 처음부터 게임 세상에 없어도 사용할 수 있습니다.

사용하려고 할 때 만드는 것을 지연 생성이라고 합니다.

아직 싱글톤의 특성을 모두 적용하지 못하였는데 바로 두 개 이상 있을 때 다른 게임 오브젝트를 파괴하는 특성입니다.

다시 코드를 추가해줍시다.

public class SingleTone : MonoBehaviour
{
    public static SingleTone GetInstance()
    {
        if(instance == null)
        {
            instance = FindObjectOfType<SingleTone>();
            
            if(instance == null)
            {
                GameObject container =
                    new GameObject("SingleTone");
                
                instance = 
                    container.AddComponent<SingleTone>();
            }
        }
        
        return instance;
    }
    
    private static SingleTone instance;
    
    void Start()
    {
        if(instance != null)
        {
            if(instance != this)
            {
                Destroy(gameObject);
            }
        }
    }
}

Start 함수를 통해 instance 변수가 null이 아니고 instance 변수에 대입돼있는 게임 오브젝트가 자기 자신(this)이 아니라면 게임 오브젝트를 파괴하는 코드를 작성했습니다.

위 코드를 통해 싱글톤을 구현할 수 있는데 싱글톤을 만드는 방법은 여러 방법이 있습니다. 무조건 위 코드의 방식대로 사용해야 되는 것이 아니라는 말입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다