PoolManager Refactoring
기존에 설계한 PoolManager에서 보완이 필요하다고 피드백 받은 부분을 개선하였다.
기존 PoolManager 설계와 관련된 내용은 아래 링크에서 자세히 확인할 수 있다.
[ 기존 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());
}
}'프로젝트 기록' 카테고리의 다른 글
| [트러블슈팅] 버튼 이벤트 인덱스 오류 (0) | 2025.10.01 |
|---|---|
| [HighRoWin] 상속과 인터페이스를 활용한 리팩토링 (0) | 2025.05.25 |
| [Warning-of-the-Monsters] 인간 상태 머신 설계 (0) | 2025.05.24 |
| UIManager 설계 (0) | 2025.05.24 |