오늘은 기존의 CS 공부와 프로그래머스 문제 풀이에서 벗어나, 전일 언급했던 오브젝트 풀링에 대해서 공부하고, 이를 직접 프로젝트에서 적용해 보았습니다.
오브젝트 풀링은 게임이나 앱에서 성능을 최적화 시키기 위해 자주 사용되는 디자인 패턴입니다. 이는, 자주 생성되고 파괴되는 오브젝트를 미리 생성하여 관리하고, 사용이 끝났으면 Destory 하는 것이 아닌 Pool에 반환합니다. 또한 다시 필요할때도 인스턴스로 생성하는것이 아닌 Pool에서 가져오는 형태로 구성됩니다.
유니티에서는 이러한 오브젝트 풀링을 적, 탄환, 아이템, 이벤트 등과 같이 자주 생성되고 삭제되는 객체들에게 사용합니다.
먼저 PoolManager를 작성해 Pool 클래스를 만들고, 이를 생성하고, 오브젝트를 추가하고 가져오고 돌려놓는 기능을 작성합니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PoolManager
{
#region Pool
class Pool
{
public GameObject Original { get; private set; }
public Transform Root { get; set; }
Stack<Poolable> _poolStack = new Stack<Poolable>();
public void Init(GameObject original, int count = 5)
{
Original = original;
Root = new GameObject().transform;
Root.name = $"{original.name}_Root";
for(int i =0; i < count; i++)
{
Push(Create());
}
}
Poolable Create()
{
GameObject go = Object.Instantiate(Original);
go.name = Original.name;
return go.GetOrAddComponent<Poolable>();
}
public void Push(Poolable poolable)
{
if (poolable == null) return;
poolable.transform.parent = Root;
poolable.gameObject.SetActive(false);
poolable.isUsing = false;
_poolStack.Push(poolable);
}
public Poolable Pop(Transform parent)
{
Poolable poolable;
if (_poolStack.Count > 0)
poolable = _poolStack.Pop();
else
poolable = Create();
poolable.gameObject.SetActive(true);
//DontDestroyOnLoad해제
if (parent == null)
poolable.transform.parent = Managers.Scene.CurrentScene.transform;
poolable.transform.parent = parent;
poolable.isUsing = true;
return poolable;
}
}
#endregion
//풀링 오브젝트들의 부모
Transform _root;
Dictionary<string, Pool> _pool = new Dictionary<string, Pool>();
public void Init()
{
if(_root == null)
{
_root = new GameObject { name = "@Pool_Root" }.transform;
Object.DontDestroyOnLoad(_root);
}
}
public void CreatePool(GameObject original, int count = 5)
{
if (_pool.ContainsKey(original.name))
{
AddToPool(original.name, count);
return;
}
Pool pool = new Pool();
pool.Init(original,count);
pool.Root.parent = _root;
_pool.Add(original.name, pool);
}
public void Push(Poolable poolable)
{
string name = poolable.gameObject.name;
// 예외 케이스
if (_pool.ContainsKey(name) == false)
{
GameObject.Destroy(poolable.gameObject);
return;
}
_pool[name].Push(poolable);
}
public Poolable Pop(GameObject original, Transform parent = null)
{
if (_pool.ContainsKey(original.name) == false)
CreatePool(original);
return _pool[original.name].Pop(parent);
}
public GameObject GetOriginal(string name)
{
if (_pool.ContainsKey(name) == false)
return null;
return _pool[name].Original;
}
public void Clear()
{
foreach (Transform child in _root)
GameObject.Destroy(child.gameObject);
_pool.Clear();
}
public void AddToPool(string name, int count)
{
if (!_pool.ContainsKey(name))
{
Debug.LogWarning($"풀링 오브젝트 {name}이(가) 존재하지 않습니다.");
return;
}
Pool pool = _pool[name];
for (int i = 0; i < count; i++)
{
pool.Push(pool.Pop(null)); // 새로운 오브젝트를 생성 후 다시 풀에 넣음
}
}
}
이렇게 딕셔너리 형태로 구성된 pool 에는 PoolAble 스크립트를 포함한 객체가 등록되게 되고, 객체는 각각의 Root_OriginalName 밑으로 복속됩니다.
구체적인 외부에서의 사용 흐름은 아래와 같습니다.
적용된 영상은 유튜브 링크로 등록 해두었습니다.