강의, 책/[Unity] C#과 유니티로 만드는 MMORPG 게임 개발 시리즈

Section 10. Object Pooling - Pool Manager

hye3193 2024. 2. 3. 23:04

오브젝트를 재사용 하기 위해 pool을 사용한다(소규모 게임에서는 굳이 필요 없으나, 규모가 커지고 성능을 높여야 할 때 유용)

풀링할 객체 vs 안 할 객체 구분법 : 특정 컴포넌트의 존재 여부로 판단한다

 

Assets > Scripts > Managers > Poolable.cs

public class Poolable : MonoBehaviour
{
    public bool isUsing;
}

객체를 구별하는 용도의 컴포넌트로 사용한다

 

Assets > Scripts > Managers > PoolManager.cs (Resource Manager를 보조하는 역할로 사용)

public class PoolManager
{
    class Pool
    {
        public GameObject Original { get; private set; }
        public Transform Root { get; set; }
        
        Stack<Poolable> _poolStack = new Stack<Poolable>();
        
        public void Init(GameObject original, int count = 5)
        {
            Original = original;
            Root = new GameObject().transform;
            Root.name = $"{original.name}_Root";
            
            for (int i = 0; i < count; i++)
                Push(Create());
        }
        
        Poolable Create()
        {
            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);
            
            // DontDestoryOnLoad 해제 용도
            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>();
    Transform _root;
    
    public void Init()
    {
        if (_root == null)
        {
            _root = new GameObject { name = "@Pool_Root" }.transform;
            Object.DontDestroyOnLoad(_root);
        }
    }
    
    public void CreatePool(GameObject original)
    {
        Pool pool = new Pool();
        pool.Init(original, count);
        pool.Root.parent = _root;
        
        _pool.Add(original.name, pool);
    }
    
    public void Push(Poolable poolable)
    {
        string name = poolable.gameObject.name;
        if (_pool.ContainsKey(name) == false)
        {
            GameObject.Destory(poolable.gameObject);
            return;
        }
                
        _pool[name].Push(poolable);
    }
    
    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.Destory(child.gameObject);
        
        _pool.Clear();
    }
}

 

#region Pool
// 코드
#endregion

+) 위 명령어로 쉽게 접었다 폈다 할 수 있다

 

매니저 스크립트에 연결 후, Init 함수와 Clear 함수도 각각 연결해준다

PoolManager _pool = new PoolManager;
public static PoolManager Pool { get { return Instance._pool; } }

 

Pool Manager에서 작성하였던 함수들을 Resource Manager에서 아래와 같이 추가해준다

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);
            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;
        }
        
        if (original.GetComponent<Poolable>() != null)
            return Managers.Pool.Pop(original, parent).gameObject;
        
        GameObject go = Object.Instantiate(original, parent);
        go.name = original.name;
        
        return go;
    }
    
    public void Destory(GameObject go)
    {
        if (go == null)
            return;
        
        Poolable poolable = go.GetComponent<Poolable>();
        if (poolable != null)
        {
            Managers.Pool.Push(poolable);
            return;
        }
        
        Object.Destory(go);
    }
}