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
}