프로젝트 기록

PoolManager 설계

binary는 호남선 2025. 5. 24. 19:12

PoolManager Refactoring

기존에 설계한 PoolManager에서 보완이 필요하다고 피드백 받은 부분을 개선하였다.

기존 PoolManager 설계와 관련된 내용은 아래 링크에서 자세히 확인할 수 있다.

https://lbtm.tistory.com/79

 

[ 기존 PoolManager 문제점 ]

- GameObject 타입으로 반환하여 각 클래스에서 SpawnFromPool로 풀에서 가져온 후에 추가로 GetComponent 하는 과정이 필요함

- 확장 풀이 아니므로 범용적인 사용 어려움

- 시작 시 사용하지 않아도 설정된 모든 오브젝트 풀을 미리 준비

 

[ 문제점 보완 ]

- T 타입으로 반환하여 따로 GetComponent 하지 않아도 해당 타입 클래스의 메서드 바로 사용 가능

- Unity 내장 ObjectPool을 활용해 확장 풀로 변경, 사용자의 입력에 따라 유연하게 확장할 수 있도록 개선, 비정상적인 생성 막기 위해 최대 풀 사이즈 제한

- 오브젝트를 스폰할 때 풀 설정 정보에서 오브젝트 풀을 생성하고 가져옴

 

코드 비교

이전 코드

CreatePool

- Queue로 직접 만든 ObjectPool 사용

- 첫 생성 시 최대 사이즈로 생성되며 확장 불가

using System.Collections.Generic;
using UnityEngine;

public class ObjectPool : MonoBehaviour
{
    public class Pool
    {
        public string tag;
        public GameObject prefab;
        public int size;
    }
    
    private Dictionary<string, Queue<GameObject>> _poolDictionary;  // 각 풀을 관리하는 딕셔너리

    public void Initialize(Pool pool)
    {
        if (_poolDictionary == null)
        {
            _poolDictionary = new Dictionary<string, Queue<GameObject>>();
        }

        // 풀 딕셔너리에 이미 해당 태그와 일치하는 풀 있으면 리턴(중복 풀 생성 방지)
        if (_poolDictionary.ContainsKey(pool.tag))
        {
            Debug.Log($"Pool with tag {pool.tag} already exists.");
            return;
        }
        
        Queue<GameObject> objectPool = new Queue<GameObject>();
        for (int i = 0; i < pool.size; i++)
        {
            if (pool.prefab != null)
            {
                // 게임오브젝트 프리팹에서 생성하고 비활성화
                GameObject obj = Instantiate(pool.prefab, transform);
                obj.name = pool.tag;
                // obj.name = pool.Tag + i;
                obj.SetActive(false);
                objectPool.Enqueue(obj);    // 큐 구조의 오브젝트풀에 생성된 게임오브젝트 추가
            }
        }
        
        _poolDictionary.Add(pool.tag, objectPool);  // 새로 만든 풀을 풀 딕셔너리에 추가
    }
}
private void CreatePool(string tag, GameObject prefab, int size)
{
    // 풀 딕셔너리에 이미 해당 태그와 일치하는 풀 있으면 리턴(중복 풀 생성 방지)
    if (_pools.ContainsKey(tag))
    {
        Debug.Log($"Pool with tag {tag} already exists.");
        return;
    }

    /* 계층 구조 생성하여 정리 */
    // PoolManager
    // - Pool_XXX
    // -- GameObject(Clone)
    // -- GameObject(Clone)...
    GameObject poolObject = new GameObject($"Pool_{tag}");  // 풀 관리할 빈 게임오브젝트 생성하고 태그로 이름 구별
    poolObject.transform.SetParent(transform);  // PoolManager의 자식으로 설정

    // Inspector에서 받아온 설정 정보 기반으로 새로운 오브젝트 풀 생성
    ObjectPool objectPool = poolObject.AddComponent<ObjectPool>();
    objectPool.Initialize(new ObjectPool.Pool
    {
        tag = tag,
        prefab = prefab,
        size = size
    });

    _pools.Add(tag, objectPool);    // 풀 딕셔너리에 새로운 오브젝트 풀 추가
}

SpawnFromPool

GameObject 타입으로 반환

public GameObject SpawnFromPool(string tag)
{
    // 풀 딕셔너리에 해당 태그와 일치하는 풀이 있는지 확인
    if (!_poolDictionary.ContainsKey(tag))
    {
        Debug.Log($"Pool with tag {tag} doesn't exist.");
        return null;
    }

    GameObject obj = _poolDictionary[tag].Dequeue();    // 풀에서 가장 오래된 오브젝트 가져오기
    _poolDictionary[tag].Enqueue(obj);  // 다시 풀에 넣기(최신 오브젝트로 갱신)
    // 오브젝트 활성화하여 반환
    obj.SetActive(true);
    return obj;
}
// Transform 설정하는 SpawnFromPool
public GameObject SpawnFromPool(string tag, Vector3 position, Quaternion rotation)
{
    // 태그와 일치하는 풀이 있는지 유효성 검사
    if (!_pools.ContainsKey(tag))
    {
        Debug.Log($"Pool with tag {tag} doesn't exist.");
        return null;
    }

    GameObject obj = _pools[tag].SpawnFromPool(tag);    // 풀에서 오브젝트 가져오기
    // Object 있으면 Transform 설정하고 반환
    if (obj != null)
    {
        obj.transform.position = position;
        obj.transform.rotation = rotation;
    }
    return obj;
}

InitializePool

모든 설정된 풀 미리 준비

protected override void Awake()
{
    base.Awake();
    InitializePools();
    DontDestroyOnLoad(gameObject);
}

private void InitializePools()
{
    foreach (var config in _poolConfigs)
    {
        CreatePool(config.tag, config.prefab, config.size);
    }
}

변경된 코드

CreatePool

- Unity ObjectPool 사용

- 첫 생성 시 설정한 사이즈만큼 할당, 이후 설정한 사이즈만큼 확장 가능, 비정상적 확장 방지하여 최대 크기 제한

// 새로운 풀 생성
private void CreatePool<T>(string tag, GameObject prefab, int size) where T : Component
{
    // 풀 딕셔너리에 이미 해당 태그와 일치하는 풀 있으면 리턴(중복 풀 생성 방지)
    if (_pools.ContainsKey(tag))
    {
        //Debug.Log($"Pool with tag {tag} already exists.");
        return;
    }

    // 계층 구조 생성하여 정리
    GameObject poolObject = new GameObject($"Pool_{tag}"); // 풀 관리할 빈 게임오브젝트 생성하고 태그로 이름 구별
    poolObject.transform.SetParent(transform); // PoolManager의 자식으로 설정


    // Inspector에서 받아온 설정 정보 기반으로 새로운 오브젝트 풀 생성
    IObjectPool<T> objectPool = new ObjectPool<T>(
        createFunc: () =>
        {
            GameObject obj = Instantiate(prefab);
            obj.name = tag; // 생성되는 풀링 오브젝트의 이름을 태그명과 동일하게 설정
            obj.transform.SetParent(poolObject.transform);
            return obj.GetComponent<T>();
        },
        actionOnGet: obj => obj.gameObject.SetActive(true),
        actionOnRelease: obj => obj.gameObject.SetActive(false),
        actionOnDestroy: obj => Destroy(obj.gameObject),
        defaultCapacity: size,
        maxSize: 100
    );

    ExpandPool(objectPool, size);    // size만큼 미리 생성

    _pools.Add(tag, objectPool);    // 풀 딕셔너리에 새로운 오브젝트 풀 추가
}

SpawnFromPool

- T 타입으로 반환

- tag와 일치하는 풀 없으면 새로 생성 (외부에서 스폰 요청하는 순간 오브젝트 풀이 생성됨)

- 모든 오브젝트가 사용중이면 size만큼 확장

// 풀에서 T 타입 오브젝트를 가져와 반환
public T SpawnFromPool<T>(string tag) where T : Component
{
    // 태그와 일치하는 풀이 없으면 풀 생성
    if (!_pools.TryGetValue(tag, out var pool))
    {
        foreach (var poolConfig in _poolConfigs)
        {
            if (poolConfig.tag == tag)
            {
                CreatePool<T>(poolConfig.tag, poolConfig.prefab, poolConfig.size);
            }
        }
    }

    // 풀 생성 실패 시 에러메시지 출력 후 null 반환
    if (!_pools.TryGetValue(tag, out pool))
    {
        //Debug.Log($"Pool with tag {tag} cannot be created.");
        return null;
    }

    // 태그와 일치하는 풀이 있으면 
    if (pool is IObjectPool<T> typedPool)
    {
        // 모든 오브젝트가 사용 중이면 풀 확장
        if (typedPool.CountInactive == 0)
        {
            var poolConfig = _poolConfigs.Find(config => config.tag == tag);
            if (poolConfig != null)
            {
                ExpandPool(typedPool, poolConfig.size);
            }
        }

        // 풀에서 오브젝트 가져와 반환
        T obj = typedPool.Get();
        return obj;
    }

    //Debug.Log($"Type Error: Pool with tag {tag} is {typeof(T)}.");
    return null;
}

// 인자로 받은 사이즈만큼 풀을 확장
private void ExpandPool<T>(IObjectPool<T> pool, int size) where T : Component
{
    Stack<T> temp = new Stack<T>();
    for (int i = 0; i < size; i++)
    {
        temp.Push(pool.Get());
    }
    for (int i = 0; i < size; i++)
    {
        pool.Release(temp.Pop());
    }
}