유니티 오브젝트 풀(Object Pool) 매니저를 제네릭 타입으로 제작해보았다

22년도에 Pool System에 대해서 언급한적이 있습니다. 

Pooling에 관한 내용은 아래 포스팅에 작성했으니 참고하시면 되겠습니다.

 

이때 소개한 Pool의 경우 하나의 오브젝트에 대해서만 관리할 수 있다는 단점이 있었습니다.

 

Unity Pooling System (최적화 구웃!)

Pooling이란? Unity의 풀링 시스템은 런타임 중에 게임 오브젝트를 효율적으로 관리하고 재사용하는 데 사용되는 기술로, 특히 총알, 적, 파티클과 같은 오브젝트를 자주 생성하고 소멸해야 할 때

wlsdn629.tistory.com

 

이번 시간에는 Dictionary를 활용해서 하나의 오브젝트에 대해서만 관리하는 것이 아닌 2개 이상의 오브젝트를 관리할 수 있는 방법과 활용 방법에 대해 알아보겠습니다.

 

제목에서도 알 수 있다시피 제네릭을 이용해서 작성되었으므로 제네릭에 대해 기초가 부족하시면 아래 포스팅을 통해 학습하고 와주세요.

 

유니티에서의 제네릭 프로그래밍: where 키워드의 활용

where 키워드란? 제네릭 프로그래밍에서 중요한 역할을 하며, 제네릭 타입에 대한 제약 조건을 지정할 때 사용됩니다.  where키워드를 사용함으로써 컴파일 타임에 타입의 특정 특성을 보장할 수

wlsdn629.tistory.com

 


Pool Manager<T>

앗! 보기 전에 특정 에셋이 없으면 에러가 뜰겁니다. 그러므로 이 Pool System을 사용하기 위해서는 아래 에셋을 다운받고 시작해주세요!

 

 

using System;
using System.Collections.Generic;
using AYellowpaper.SerializedCollections;
using UnityEngine;

namespace Ludens
{
    [Serializable]
    public struct PoolData<T> where T : MonoBehaviour
    {
        public T Prefab;
        public int InitSpawnCount;
    }
    
    /// <summary>
    /// 각종 Pool의 Base가 되는 Manager 
    /// </summary>
    /// <typeparam name="T">MonoBehaviour를 상속받는 타입</typeparam>
    public abstract class PoolManager<T> : MonoBehaviour where T : MonoBehaviour
    {
        [SerializedDictionary("Pool Name", "Pool Data")]
        public SerializedDictionary<string, PoolData<T>> Pool = new();
        
        protected Dictionary<string, Queue<T>> activatedPool = new();
        
        protected void InitPool()
        {
            foreach (var o in Pool)
            {
                if (!activatedPool.ContainsKey(o.Key))
                {
                    Queue<T> objectPool = new Queue<T>();

                    for (int i = 0; i < o.Value.InitSpawnCount; i++)
                    {
                        var obj = CreatePooledObject(o.Value.Prefab, o.Key);
                        objectPool.Enqueue(obj);
                    }

                    activatedPool[o.Key] = objectPool;
                }
            }
        }

        protected abstract T CreatePooledObject(T prefab, string poolName);
        
        public T Get(string poolObjectName)
        {
            T effect = null;

            if (activatedPool.TryGetValue(poolObjectName, out var objectPool))
            {
                effect = objectPool.Count > 0 ? objectPool.Dequeue() 
                    : CreatePooledObject(Pool[poolObjectName].Prefab, poolObjectName);
                
                effect.gameObject.SetActive(true);
            }

            return effect;
        }
        
        public void Return(string effectPoolName, T objectToReturn)
        {
            objectToReturn.gameObject.SetActive(false);
            activatedPool[effectPoolName].Enqueue(objectToReturn);
        }
    }
}

 

CreatePooledObject함수의 경우 Pool Manager를 상속받는 Manager에서 처리하도록 만들었습니다.

Pool Manager는 Base가 되는 Manager입니다.

 

본인 프로젝트에 맞게 수정해서 사용하시면 됩니다! 

 

using UnityEngine;

namespace Ludens
{
    public class EffectPoolManager : PoolManager<PooledObject<ParticleSystem>> 
    {
        public static EffectPoolManager Instance { get; private set; }

        #region Unity

        private void Awake()
        {
            if (Instance == null)
            {
                Instance = this;
                InitPool();
            }
        }
        
        #endregion
        
        protected override PooledObject<ParticleSystem> CreatePooledObject(PooledObject<ParticleSystem> prefab, string poolName)
        {
            PooledObject<ParticleSystem> obj = Instantiate(prefab, Vector3.zero, Quaternion.identity);
            obj.transform.SetParent(transform, false);
            obj.gameObject.SetActive(false);
            obj.Initialize(this, poolName);

            return obj;
        }
        
    }
}

 

Effect Pool Manager는 Particle System을 처리하는 Pool입니다. 따라서 T 에 Particle System을 지정해주었습니다.

 


PooledObject<T>

Pooled Object 스크립트는 Pool Manager에서 사용되는 오브젝트를 뜻합니다.

 

PooledObject 스크립트도 제네릭 타입인 이유는 Pool Manager는 Particle System에만 사용되는게 아닌 Audio System 같은 컴포넌트도 등록 될 수 있으므로 확장성을 고려해서 제네릭 타입으로 제작했습니다

using UnityEngine;

namespace Ludens
{
    public class PooledObject<T> : MonoBehaviour where T : Component
    {
        protected T Component { get; set; }
        
        private PoolManager<PooledObject<T>> poolManager;
        private string poolName;
        protected Coroutine callbackCoroutine;

        public void Initialize(PoolManager<PooledObject<T>> manager, string name)
        {
            Component = GetComponent<T>();
            poolManager = manager;
            poolName = name;
        }

        protected void ReturnToPool()
        {
            poolManager.Return(poolName, this);
        }
    }
}

 

using UnityEngine;

namespace Ludens
{
    [RequireComponent(typeof(ParticleSystem))]
    public class PooledParticleSystem : PooledObject<ParticleSystem>
    {
        private void OnEnable()
        {
            callbackCoroutine = ParticleSystemHelper.RegisterTimedCallback(Component, ReturnToPool, 2f, this);
        }

        private void OnDisable()
        {
            if (callbackCoroutine != null)
            {
                StopCoroutine(callbackCoroutine);
                callbackCoroutine = null;
            }
        }
    }
}

 


사용 방법

Effect Pool Manager를 만들고 초기화
Particle에 Pooled Particle System 등록

 


Bonus : Particle System Helper

using UnityEngine;
using System;
using System.Collections;

namespace Ludens
{
    public static class ParticleSystemHelper
    {
        /// <summary>
        /// 일정 시간이 지난 후 콜백을 호출하는 유틸리티 함수
        /// </summary>
        /// <param name="particleSystem">감시할 파티클 시스템</param>
        /// <param name="callback">시간이 지난 후 호출할 콜백 함수</param>
        /// <param name="duration">파티클 시스템의 지속 시간(초 단위)</param>
        /// <param name="owner">Coroutine을 실행할 MonoBehaviour 객체</param>
        public static Coroutine RegisterTimedCallback(ParticleSystem particleSystem, Action callback, float duration, MonoBehaviour owner)
        {
            return owner.StartCoroutine(TimedCallback(duration, particleSystem, callback));
        }

        private static IEnumerator TimedCallback(float duration, ParticleSystem particleSystem, Action callback)
        {
            yield return new WaitForSeconds(duration);
            if (particleSystem.isPlaying)
            {
                particleSystem.Stop();
            }
            callback?.Invoke();
        }
    }
}

 

 

코드 지적은 언제나 환영입니다.

감사합니다.