Unity ObjectPool Manager

선비Sunbei·2023년 1월 25일
0

Unity

목록 보기
14/18
post-thumbnail
post-custom-banner

매번 GameObject를 생성하고 지우는 것은 큰 비용을 발생시킨다.
이를 해결하기위해서 Object Pool을 이용한다. Object Pool은 미리 특정 객체를 여러개를 만들어놓고서 필요할 때 미리 만들어놓은 객체를 넘겨주고, 객체를 다쓰면 삭제하지 않고 다시 Object Pool에 넣어놔서 필요할 때 사용한다.

이는 총알같이 만드는데 만들어졌다가 삭제되는게 빈번한 객체에 매우 유용한 최적화가 된다.

Object Pool을 설계해보겠다.
1. Object Pool에서의 Pop은 Resource Manager가 불러오게끔 만든다.
2. Prefab 중에 Poolable Script를 갖고 있는 것만 Object Pooling을 한다.
3. Object Pool은 각 Prefab별로 Pool을 갖고 있는다.
4. Pool은 Original Prefab과 미리 만들어놓은 객체로 갖고있는다.

// PoolManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PoolManager 
{

    #region Pool
    public class Pool
    {
        public GameObject original { get; private set; }
        public Transform Root { get; set; }

        Stack<Poolable> pool;
        public void Init(GameObject original, int count = 10)
        {
            this.original = original;
            pool = new Stack<Poolable>(count);
            Root = new GameObject(original.name + "_Pool").transform;

            for (int i = 0; i < count; i++)
                pool.Push(Create());
        }
        Poolable Create()
        {
            GameObject go = Object.Instantiate(original);
            go.name = original.name;
            return Util.GetOrAddComponent<Poolable>(go);
        }
        public void Push(Poolable poolable)
        {
            if (poolable == null)
                return;
            poolable.transform.parent = Root;
            poolable.gameObject.SetActive(false);
            pool.Push(poolable);
        }

        public Poolable Pop(Transform parent)
        {
            Poolable poolable;
            if (pool.Count > 0)
                poolable = pool.Pop();
            else
                poolable = Create();

            poolable.gameObject.SetActive(true);

            // DontDestroyOnLoad 해제하기.
            if (parent == null)
                poolable.transform.parent = GameObject.FindGameObjectWithTag("MainCamera").transform;

            poolable.transform.parent = parent;

            return poolable;
        }
    }
    #endregion


    Transform mRoot = null;
    Dictionary<string, Pool> pools = new Dictionary<string, Pool>();
    public void Init()
    {
        if(mRoot == null)
        {
            mRoot = (new GameObject("Object Pool")).transform;
            Object.DontDestroyOnLoad(mRoot);
        }
    }

    public void CreatePool(GameObject original, int count = 10)
    {
        Pool pool = new Pool();
        pool.Init(original,count);
        pool.Root.parent = mRoot.transform;
        pools.Add(original.name,pool);
    }

    public void Push(Poolable poolable)
    {
        string name = poolable.name;

        // poolable은 Pop으로 먼저 꺼낼 수 밖에 없는데 Push부터하는 것은 말이 안됨.
        if (pools.ContainsKey(name) == false)
        {
            Object.Destroy(poolable.gameObject);
            return;
        }
        pools[name].Push(poolable);
    }

    public Poolable Pop(GameObject original, Transform parent)
    {
        if (pools.ContainsKey(original.name) == false)
            CreatePool(original);

        return pools[original.name].Pop(parent);
    }

    public GameObject GetOriginal(string name)
    {
        if (pools.ContainsKey(name) == false)
            return null;
        return pools[name].original;
    }

    public void Clear()
    {
        foreach (Transform child in mRoot)
            Object.Destroy(child.gameObject);

        pools.Clear();
    }

}

Poolable은 그냥 이름이 Poolable인 스크립트를 만들어주면 된다.

Resource Manager 설계는 다음과 같다.
1. Load : 이미 Pool에 origin이 있을 경우 그것을 반환하고, 없으면 새로 생성해서 리턴한다.
2. Instantiate : Load 메서드로 origin을 부르고 Poolable 스크립트가 있으면 오브젝트 풀에서 팝한다. Poolable이 없으면 새로 생성해서 리턴한다.
3. Destroy : poolable 스크립트가 있으면 Pool에 push하고, 없으면 삭제한다.

// ResourceManager.cs
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 = Manager.Instance.mPoolManager.GetOriginal(name);
            if (go != null)
                return go as T;
        }
        return Resources.Load<T>(path);
    }

    public GameObject Instantiate(string path, Transform parent)
    {
        GameObject original = Load<GameObject>($"Prefab/{path}");
        if(original == null)
        {
            Debug.Log("Instantiate : No File");
            return null ;
        }
        if (original.GetComponent<Poolable>() != null)
            return Manager.Instance.mPoolManager.Pop(original, parent).gameObject;

        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>();
        if(poolable != null)
        {
            Manager.Instance.mPoolManager.Push(poolable);
            return;
        }
        Object.Destroy(go);
    }
}
post-custom-banner

0개의 댓글