Object Pooling은 게임 오브젝트를 만들어 놓고, 필요할 때마다 활성화 시켜서 사용하는 기법이다. 

필요할 때마다 활성화 시키는 것이 아닌 새로 만들고, 없애는 과정이 자주 발생하다 보면 게임 성능에 문제를 미칠 수 있다.

따라서 딕셔너리와 같은 자료구조에 오브젝트를 저장하고 필요할 때마다 꺼내서 사용한다.

게임오브젝트에는 SetActive라는 함수가 있는데, 인자로 false를 넣게 되면 유니티 엔진에서 비활성화 시킨 상태로 변하게 된다. 

 

이 오브젝트는 자주 소환 되어 Object Pooling이 필요할 것 같을 때, 프리팹에 구분할 수 있는 컴포넌트를 붙여주어 자동으로 판단 할 수 있게 한다. 따라서 Poolable이라는 빈 C# 스크립트를 컴포넌트로 붙여줘서 판단할 수 있게 해준다.

 

PoolManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static UnityEngine.UI.Image;

public class PoolManager
{

    class Pool
    { // 오브젝트 풀링을 위한 클래스를 선언해준다.
        public GameObject Original { get; private set; }
        // Instantiate을 할 때, 필요한 Original
        public Transform Root { get; set; }
        // 모든 오브젝트 풀링을 관리할 Root 객체

        Stack<Poolable> _poolStack = new Stack<Poolable>();
        // 오브젝트 풀링한 객체를 저장할 스택

        public void Init(GameObject original, int count = 5)
        { // count만큼의 original을 Instantiate을 하여 스택에 저장한다.
            Original = original;
            Root = new GameObject().transform;
            Root.name = $"{original.name}_Root";

            for (int i = 0; i < count; i++)
            {
                Push(Create());
            }
        }

        Poolable Create()
        { // Instantiate을 하여 객체를 생성
            GameObject go = Object.Instantiate<GameObject>(Original);
            go.name = Original.name;
            return go.GetOrAddComponent<Poolable>();
        }

        public void Push(Poolable poolable)
        { // 스택에 저장한다.
            if (poolable == null)
                return;

            poolable.transform.parent = Root;
            poolable.gameObject.SetActive(false);
            poolable.IsUsing = false;

            _poolStack.Push(poolable);
        }

        public Poolable Pop(Transform parent)
        { // 스택에서 불러와서 활성화를 한다
            Poolable poolable;
            if (_poolStack.Count > 0)
                poolable = _poolStack.Pop();
            else
                poolable = Create();

            poolable.gameObject.SetActive(true);

            // DontDestroyOnLoad 해제 용도
            if (parent == null)
                poolable.transform.parent = Managers.Scene.CurrentScene.transform;

            poolable.transform.parent = parent;
            poolable.IsUsing = true;

            return poolable;
        }
    }

    Dictionary<string, Pool> _pool = new Dictionary<string, Pool>();
    // 위에서 만든 Pool 클래스를 저장할 딕셔너리
    Transform _root;
    // 모든 Object Pooling 객체를 관리하는 부모 객체
    public void Init()
    { // 부모 객체가 존재하지 않을 시 생성
        if (_root == null)
        {
            _root = new GameObject { name = "@Pool_Root" }.transform;
            Object.DontDestroyOnLoad(_root);
        }
    }

    public void Push(Poolable poolable)
    { // 오브젝트를 관리하는 딕셔너리에 넣어주는 함수
        string name = poolable.gameObject.name;
        if (_pool.ContainsKey(name) == false)
        { // 만약 CreatePool을 하지 않고 넣어주려고 하면 객체를 없애고 반환
            GameObject.Destroy(poolable.gameObject);
            return;
        }

        _pool[name].Push(poolable);
        // 해당하는 키에 객체를 넣어준다
    }
    public void CreatePool(GameObject original, int Count = 5)
    { // Pooling할 객체를 딕셔너리 안에 새로 저장해준다.
        Pool pool = new Pool();
        pool.Init(original, Count);
        pool.Root.parent = _root;

        _pool.Add(original.name, pool);
    }

    public Poolable Pop(GameObject original, Transform parent = null)
    { // 딕셔너리에서 꺼내온다.
        if (_pool.ContainsKey(original.name) == false)
            CreatePool(original);
        return _pool[original.name].Pop(parent);
    }

    public GameObject GetOriginal(string name)
    { // 원본을 가져온다
        if (_pool.ContainsKey(name) == false)
            return null;
        return _pool[name].Original;
    }
    
    public void Clear()
    { // 관리 객체의 모든 자식을 없애고, 딕셔너리도 클리어한다.
        foreach (Transform child in _root)
            GameObject.Destroy(child.gameObject);

        _pool.Clear();
    }
}

 

위의 Pool Manager에서 Poolable이라는 새로운 클래스를 사용하는 것을 볼 수 있는데, 기존의 Resource Manager를 통해 Instantiate을 하거나 Destroy를 할 때 처럼 작동을 한다면 생각하는 로직과는 다른 결과가 나올 것이다.

따라서 Resource Manager도 수정을 해주어야한다.

 

아래는 수정 전 코드이다.

 

 

[UNITY] ResourceManager

using System.Collections; using System.Collections.Generic; using UnityEngine; public class ResourceManager { public T Load(string path) where T : Object { return Resources.Load(path); } public GameObject Instantiate(string path, Transform parent = null) {

mainsdev.tistory.com

 

아래는 변경한 코드이다.

 

ResourceManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ResourceManager
{
    public T Load<T>(string path) where T : Object
    {
        if (typeof(T) == typeof(GameObject))
        {
            string name = path;
            int index = name.LastIndexOf("/");
            if (index >= 0)
                name = name.Substring(index + 1);

            GameObject go = Managers.Pool.GetOriginal(name);
            // 만약 오브젝트 풀링을 하던 객체이면
            // T 타입으로 Original을 반환해준다.
            if (go != null)
                return go as T;
        }

        return Resources.Load<T>(path);
    }

    public GameObject Instantiate(string path, Transform parent = null)
    {
        
        GameObject original = Load<GameObject>($"Prefabs/{path}");
        if (original == null)
        {
            Debug.Log($"Failed to load prefab : {path}");
            return null;
        }
		
        // 만약 Poolable이 붙어있다면
        if (original.GetComponent<Poolable>() != null)
            return Managers.Pool.Pop(original, parent).gameObject;
            //관리 딕셔너리에서 Pop을 해준 뒤 반환한다.

        GameObject go = Object.Instantiate(original, parent);
        go.name = original.name;

        return go;
    }

    public void Destroy(GameObject go)
    {
        if(go == null)
        {
            return;
        }


        Poolable poolable = go.GetComponent<Poolable>();
        // 만약 삭제하려는 오브젝트가 Poolable이 붙어있다면
        if(poolable != null)
        {
            Managers.Pool.Push(poolable);
            // 관리 딕셔너리에 다시 넣는다.
            return;
        }

        Object.Destroy(go);
    }
}

'UNITY' 카테고리의 다른 글

UNITY - DataManager  (0) 2024.01.23
UNITY - Coroutine  (0) 2024.01.23
UNITY - SoundManager  (0) 2024.01.22
UNITY - UI 자동화 - 게임 UI 구현  (0) 2024.01.22
UNITY - Scene  (1) 2024.01.12

+ Recent posts