보통 유니티에서 프리팹을 불러오거나 삭제시킬 때는 Instantiate(), Destroy()메서드를 사용한다. 간단한 게임의 경우에는 상관없지만 만약 생성하고 삭제시키는 오브젝트가 많이 사용되는 규모의 프로젝트라면 메모리 사용량이 늘어나 성능 저하의 원인이 될 수 있다. (ex. FPS게임의 탄환 등)
이를 해결하기 위해 생긴 개념이 오브젝트 풀링아다.
"게임이 실행되기 전에 정해진 양의 게임 오브젝트를 생성하고 필요한 게임 오브젝트를 단순히 활성화/비활성화하여 효과적으로 게임 오브젝트를 재활용하고 절대 파괴하지 않는 방식으로 작동한다"
풀링을 할 개체인지 아닌지를 구별해줄 컴포넌트용 스크립트 생성
-> Poolable.cs을 부착하고 있는 오브젝트는 풀링 대상이라는 의미!
+3가지 추가 사항
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<GameObject>(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
Dictionary<string, Pool> _pool = new Dictionary<string, Pool>();
Transform _root;
public void Init()
{
if (_root == null)
{
_root = new GameObject { name = "@Pool_Root" }.transform; //풀링 대기실
Object.DontDestroyOnLoad(_root);
}
}
private void CreatePool(GameObject original, int count = 5)
{
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 GameObject Instantiate(string path, Transform parent = null)
{
GameObject original = Load<GameObject>($"Prefabs/{path}");
if(original == null)
{
Debug.Log($"Failed to load prefab : {path}");
return null;
}
//탐색_풀링 대상 오브젝트일 경우
if (original.GetComponent<Poolable>() != null)
return Managers.Pool.Pop(original, parent).gameObject;
//풀링 대상이 아닐경우_카메라, UI 등등
GameObject go = Object.Instantiate(original, parent);
go.name = original.name;
return go;
}
public void Destroy(GameObject go)
{
if(go == null)
return;
//풀링 대상이라면 PoolManager에 위탁
Poolable poolable = go.GetComponent<Poolable>();
if(poolable != null)
{
Managers.Pool.Push(poolable);
return;
}
//풀링 대상이 아닐경우
Object.Destroy(go);
}
ㄴ풀링의 경우 특수하기 때문에 최대한 마지막에 Clear()해주는 것이 좋다.
💡 풀링할 대상인 UnityChan에 Poolable.cs 컴포넌트를 추가해준다.
LoginScene.cs에 유니티짱 2개 생성
유니티짱 생성 후 바로 해제
📄참고자료
[인프런] c#과 유니티로 만드는 MMORPG 게임 개발 시리즈_3. 유니티 엔진
Unity Learn_Object Pooling