Unity 오브젝트 풀링 코드
2025. 4. 9. 00:52ㆍ유니티 unity
예전부터 오브젝트 풀링이 필요할 때마다 그때그때 스크립트를 새로 만들어 쓰곤 했는데,
이번엔 아예 재사용 가능한 형태로 하나 만들어두자는 생각으로 정리해봤습니다.
제네릭으로 만들어서 어떤 Unity 오브젝트든 쓸 수 있고,
이름 기반으로 관리되기 때문에 다양한 오브젝트를 풀 하나로 처리할 수 있습니다.
private readonly Func<string, T> _factory;
private readonly Action<T> _onGet;
private readonly Action<T> _onReturn;
로 액션을 미리 받아서 처리하도록 만들었습니다.
https://github.com/wolstar415/UnityObjectPool
더보기
코드
using System;
using System.Collections.Generic;
public class UnityObjectPool<T> where T : UnityEngine.Object
{
#region Inner Classes
protected class ObjectPoolEntry
{
public Queue<T> Instances = new Queue<T>();
public string Name;
}
#endregion
#region Fields
private readonly Dictionary<string, ObjectPoolEntry> _pathPool = new Dictionary<string, ObjectPoolEntry>();
private readonly Dictionary<T, ObjectPoolEntry> _activeInstances = new Dictionary<T, ObjectPoolEntry>();
private readonly Func<string, T> _factory;
private readonly Action<T> _onGet;
private readonly Action<T> _onReturn;
private readonly Action<T> _onClear;
#endregion
#region Constructors
public UnityObjectPool(Func<string, T> factory, Action<T> onGet = null, Action<T> onReturn = null, Action<T> onClear = null)
{
_factory = factory;
_onGet = onGet;
_onReturn = onReturn;
_onClear = onClear;
}
#endregion
#region Public Methods
public T Get(string name)
{
return GetObject(name);
}
public void Release(T instance)
{
if (_activeInstances.TryGetValue(instance, out ObjectPoolEntry pool))
{
_onReturn?.Invoke(instance);
pool.Instances.Enqueue(instance);
_activeInstances.Remove(instance);
}
}
public void ReleaseAll()
{
var activeInstances = new List<T>(_activeInstances.Keys);
foreach (T instance in activeInstances)
{
Release(instance);
}
}
public void Clear()
{
ReleaseAll();
if (_onClear != null)
{
foreach (var entry in _pathPool)
{
ObjectPoolEntry pool = entry.Value;
while (pool.Instances.Count > 0)
{
T obj = pool.Instances.Dequeue();
if (obj != null)
{
_onClear?.Invoke(obj);
}
}
}
}
_pathPool.Clear();
_activeInstances.Clear();
}
public void PrewarmPool(string prefabName, int count, Action<T> onInstanceCreated = null)
{
if (!_pathPool.TryGetValue(prefabName, out ObjectPoolEntry pool))
{
pool = new ObjectPoolEntry { Name = prefabName };
_pathPool.Add(prefabName, pool);
}
for (int i = 0; i < count; i++)
{
T instance = _factory(prefabName);
if (instance != null)
{
onInstanceCreated?.Invoke(instance);
pool.Instances.Enqueue(instance);
}
}
}
public int GetInactiveCount(string prefabName)
{
if (_pathPool.TryGetValue(prefabName, out ObjectPoolEntry pool))
{
return pool.Instances.Count;
}
return 0;
}
public int GetActiveCount()
{
return _activeInstances.Count;
}
public int GetTotalCount(string prefabName)
{
int inactive = GetInactiveCount(prefabName);
int active = 0;
foreach (var activePool in _activeInstances.Values)
{
if (activePool.Name == prefabName)
{
active++;
}
}
return inactive + active;
}
#endregion
#region Private Methods
private T GetObject(string prefabName)
{
if (!_pathPool.TryGetValue(prefabName, out ObjectPoolEntry pool))
{
pool = new ObjectPoolEntry { Name = prefabName };
_pathPool.Add(prefabName, pool);
}
return GetObject(pool);
}
private T GetObject(ObjectPoolEntry pool)
{
T obj = default(T);
if (pool.Instances.Count > 0)
{
obj = pool.Instances.Dequeue();
}
else
{
obj = _factory(pool.Name);
}
_onGet?.Invoke(obj);
_activeInstances[obj] = pool;
return obj;
}
#endregion
}
사용 예시:
// 오브젝트 풀 생성
var pool = new ObjectPool<GameObject>(
name => GameObject.Instantiate(Resources.Load<GameObject>(name)),
obj => obj.SetActive(true),
obj => obj.SetActive(false)
);
// 객체 꺼내기
GameObject bullet = pool.Get("Bullet");
// 사용 후 반환
pool.Release(bullet);
// 한꺼번에 반환
pool.ReleaseAll();
// 미리 10개 생성해서 풀에 저장
pool.PrewarmPool("Bullet", 10);
// 현재 풀 상태 확인
int active = pool.GetActiveCount();
int inactive = pool.GetInactiveCount("Bullet");
int total = pool.GetTotalCount("Bullet");
// 풀 초기화 및 제거
pool.Clear();
제가 실제로 썼던 방식
using UnityEngine;
public class GameObjectPool
{
#region Fields
UnityObjectPool<GameObject> _pool;
Transform _poolContainer;
Transform _objectContainer;
#endregion
#region Constructor
public GameObjectPool(Transform parent)
{
_pool = new UnityObjectPool<GameObject>(
(x) =>
{
return GameObject.Instantiate(ResoureManager.Load<GameObject>(x));
});
var pool = new GameObject("ObjectPool").transform;
pool.SetParent(parent, false);
var poolContainerGo = new GameObject("ObjectReadyPool");
poolContainerGo.transform.SetParent(pool, false);
_poolContainer = poolContainerGo.transform;
var objectContainerGo = new GameObject("ObjectActivePool");
objectContainerGo.transform.SetParent(pool, false);
_objectContainer = objectContainerGo.transform;
}
#endregion
#region Public Methods
public GameObject GetFromPool(string name, Transform parent = null)
{
if (parent == null)
parent = _objectContainer;
var go = _pool.Get(name);
go.transform.SetParent(parent, false);
go.SetActive(true);
return go;
}
public void ReturnToPool(GameObject go)
{
_pool.Release(go);
go.SetActive(false);
go.transform.SetParent(_poolContainer, false);
}
public void DestroyPoolRoot()
{
_poolContainer.transform.parent.Destroy();
}
public void ResetContainers()
{
_pool.Clear();
var poolTm = _poolContainer.parent.transform;
_poolContainer.Destroy();
_objectContainer.Destroy();
var readyPoolGo = new GameObject("ObjectReadyPool");
readyPoolGo.transform.SetParent(poolTm, false);
_poolContainer = readyPoolGo.transform;
var activePoolGo = new GameObject("ObjectActivePool");
activePoolGo.transform.SetParent(poolTm, false);
_objectContainer = activePoolGo.transform;
}
#endregion
}