22년도에 Pool System에 대해서 언급한적이 있습니다.
Pooling에 관한 내용은 아래 포스팅에 작성했으니 참고하시면 되겠습니다.
이때 소개한 Pool의 경우 하나의 오브젝트에 대해서만 관리할 수 있다는 단점이 있었습니다.
이번 시간에는 Dictionary를 활용해서 하나의 오브젝트에 대해서만 관리하는 것이 아닌 2개 이상의 오브젝트를 관리할 수 있는 방법과 활용 방법에 대해 알아보겠습니다.
제목에서도 알 수 있다시피 제네릭을 이용해서 작성되었으므로 제네릭에 대해 기초가 부족하시면 아래 포스팅을 통해 학습하고 와주세요.
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;
}
}
}
}
사용 방법
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();
}
}
}
코드 지적은 언제나 환영입니다.
감사합니다.