상세 컨텐츠

본문 제목

싱글톤 패턴(Singleton Patterns)을 사용하여 오브젝트 풀링 구현하기 [Unity | 유니티]

Unity3D(C#)

by 겐주 2020. 7. 29. 10:24

본문

 

유니티로 개발을 하시다 보면 많은 오브젝트들이 자주 접근해야 하는 스크립트가 있기 마련인데요,

 

이럴 때 마다 해당 스크립트에 하나하나 선언해주기가 굉장히 비효율적이고 귀찮습니다.

 

게임 옵션이나 하나 밖에 없는 자원들을 컨트롤할 때는 싱글톤 패턴(Singleton Patterns)을 자주 사용하는데, 유니티에서 이 기능을 사용하기는 정말 간단합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
 
public class ObjectManager : MonoBehaviour
{
 
    public static ObjectManager instance;
 
    private void Awake()
    {
        if (instance != null)
            Destroy(this);
        else
            instance = this;
        DontDestroyOnLoad(this);
    }
 
    
}
cs

 

저는 ObjectManager.cs 라는 스크립트를 만들고 이 ObjectManager를 static으로 선언한 뒤 instance = this; 로 자기 자신을 참조하게 하였는데, 이 것이 싱글톤 패턴입니다.

 

또한 DontDestroyOnLoad(this);를 사용하여 씬 이동 간에 ObjectManager.cs가 들어 있는 오브젝트가 사라지지 않게 설정하였습니다.

 

 

그 이유를 설명드리자면

 

저는 이 ObjectManager.cs를 싱글톤 패턴으로 ObjectManager라는 오브젝트 안에 넣고 그 자식 오브젝트를 관리하는 역할 (오브젝트 풀링)로 사용하기 위해서인데요, 이렇게 사용하는 이유는 간단합니다.

 

유니티에서 오브젝트를 생성/파괴하는 instantiate/Destroy는 효율이 굉장히 떨어지기 때문입니다.

 

그래서 자주 사용하면서도 자주 생성/파괴가 이루어지는 오브젝트들을 이 ObjectManager에 담아두고 꺼내 썼다가 다시 담아 넣는 방식인 오브젝트 풀링으로 사용하여 효율 관리를 하는 것입니다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
 
public class ObjectManager : MonoBehaviour
{
 
    public static ObjectManager instance;
    
    [SerializeField] // 인스펙터에 해당 변수를 보여줌
    List<GameObject> m_poolObject = new List<GameObject>(); // 오브젝트 풀에 보관되는 오브젝트
 
    private void Awake()
    {
        if (instance != null)
            Destroy(this);
        else
            instance = this;
        DontDestroyOnLoad(this);
    }
 
    public GameObject GetObjFromPool(int id, Transform t) // Pool => Scene
    {
        GameObject obj = m_poolObject[id];
 
        obj.transform.parent = t;
        obj.transform.position = t.position;
        obj.SetActive(true);
        return obj;
    }
    public void BackObjToPool(int id) // Scene => Pool (id로 호출)
    {
        GameObject obj = m_poolObject[id];
        obj.transform.parent = this.transform;
        obj.SetActive(false);
    }
    public void BackObjToPool(GameObject go) // Scene => Pool (GameObject로 호출)
    {
        int index = m_poolObject.FindIndex(x => x == go);
        GameObject obj = m_poolObject[index];
        obj.transform.parent = this.transform;
        obj.SetActive(false);
    }
}
cs

 

저는 GameObject의 리스트를 만들어서 다른 스크립트가 이 GameObject를 가져가고 돌려받을 수 있게 코드를 짜 봤습니다.

 

다른 스크립트에서 오브젝트 풀의 게임오브젝트를 얻고 싶다 할 때는

 

 

1
ObjectManager.instance.GetObjFromPool( 내가 얻고자 하는 오브젝트 배열 int, 부모가 될 Transform)
cs

 

이런 식으로 접근을 하면 되겠죠?

 

그리고 public void BackObjToPool(int id)는 id로 오브젝트를 찾아서 풀로 다시 보내는 역할이고,

public void BackObjToPool(GameObject go)는 해당 오브젝트를 직접 풀로 다시 보내는 역할이니 오브젝트 풀에 접근하는 스크립트의 형식에 따라 유동적으로 사용할 수 있게 짜 봤습니다.

 

 

그럼 적용을 한 번 해 볼까요?

 

 

일단 첫 번 째로 실행되는 씬에 오브젝트 매니저를 넣고 자식으로 꺼내 쓸 오브젝트를 생성합니다.

 

 

그다음 오브젝트 매니저의 poolObject에 해당 자식 오브젝트들을 넣습니다.

 

 

해당 씬에 GetObjFromPool, BackObjToPool 함수를 원하시는 조건으로 넣으신 뒤 호출하시기만 하시면 됩니다.

 

처음

 

 

Cube (index = 0) 를 풀링에서 꺼내 옴

 

 

다시 오브젝트 풀로 반환

 

 

해당 기능을 사용하면 오브젝트 생성/파괴 시 버벅임 없이 매우 매끈하게 돌아가는 것을 보실 수 있으실겁니다.

 

하지만 이 오브젝트 풀에 과도하게 오브젝트가 들어가있으면 이 오브젝트들을 런타임 내내 담고 있기 때문에 메모리 문제가 발생할 수도 있으니 너무 많은 양을 담지 않거나 상황에 맞게 풀을 갱신해 주는 것도 좋은 방법일 것 같습니다.

 

오브젝트 풀이라는 개념을 보고 따로 예제를 참조 하지 않고 만든 기능이라 효율성이 다른 오브젝트 풀보다 떨어질 수도 있고 코딩에 허점이 많을 수 있으니 참고해주시고 그런 부분들이 있다면 언제든지 태클 걸어주시면 저 같은 초보 개발자에게 많은 힘이 될 것 같습니다!

 

 

댓글 영역