개발일기
PoolManager와 ResourceManager 본문
1. Object Pooling
오브젝트 풀링이란 ?
게임 내에서 반복적으로 생성/파괴되는 오브젝트들을 Instantitate/Destroy로 실행하게 된다면, 메모리 할당/해제를 하게 됩니다. 이는 많은 Garbage Collection을 발생시켜 CPU에 부담을 주게 됩니다.
오브젝트 풀링은 오브젝트를 담은 Pool에서 오브젝트를 빌리고 반납하는 것으로 Object들을 미리 생성하여 사용할 땐 활성화 시키고 반대로 사용하지 않을 땐 비활성화 시키는 방법입니다.
2. PoolManager
우선, 오브젝트를 구분해야 합니다. 모든 오브젝트들이 오브젝트 풀링을 사용하는 것이 아니기 때문입니다. 오브젝트 풀링을 사용하는 객체를 구분짓기 위해 아무것도 기능이 없는 Poolable 스크립트를 생성하여 컴포넌트로 삽입해주었습니다.
오브젝트 풀링을 하기 위해 사용되는 Pool을 관리하는 PoolManager를 다음과 같이 작성했습니다.
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);
if (parent == null) // Dondestroyonload 해제
poolable.transform.parent = MasterManager.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 = "PoolRoot" }.transform;
Object.DontDestroyOnLoad(root);
}
}
public void Push(Poolable poolable) // Pool에 넣어둠
{
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 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 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();
}
}
1. PoolManager의 메서드
Init() | 오브젝트를 담아 둘 Pool이 필요하므로 없다면 새로 생성합니다. 오브젝트 풀링도 Scene이 변해도 지속적으로 사용할 것임이 분명하기에 DondestroyOnLoad를 통해 파괴를 방지합니다. 각 Manager의 Init()은 항상 Managers의 Awake에서 한번 실행이 됩니다. |
Pop() | Pool을 담아 둔, Dictionary인 pool에 해당 오브젝트가 없다면 CreatePool을 통해 새로운 Pool을 만들고, 있으면 Pool에서 오브젝트를 가져옵니다. 오브젝트가 필요할 때, ResourceManager의 Instantiate 메서드에서 호출합니다. |
CreatePool() | Pool Class를 생성하고, 해당 풀을 Dictionary인 pool에 삽입합니다. |
GetOriginal() | Pool을 담아둔 Dictionary인 pool에 해당 오브젝트가 있다면 반환해주고 없다면 null을 반환합니다. 이미 사용한 적 있는 오브젝트라면 ResourceManager에서 Resource.Load를 사용하지 않고 찾을 수 있기에 원본을(프리팹) 불러올 때 사용합니다. |
Clear() | 오브젝트 풀을 생성하고 유지하는 것도 비용이 들기 때문에 사용하지 않는 풀의 초기화는 필요합니다. 해당 메서드를 호출하면 Hierarchy의 오브젝트들을 삭제하고 pool을 비워줍니다. |
Push() | Dondestroy에 있는 해당 풀 부모로 이동시킨 후, 스택에 넣고 비활성화 시킴으로써 풀에 반납 합니다. |
2. PoolManager의 Class Pool
Init() | PoolManager의 CreatePool을 통해 호출되며 해당 오브젝트를 담을 Pool을 생성하고 Push(Create())를 호출합니다. |
Create() | Init()에서 초기화 시킨 Original Prefab을 생성합니다. |
Push() | 생성된 오브젝트를 스택에 넣고, 부모를 변경시키고 오브젝트를 비활성화 시킵니다. |
Pop() | 스택의 크기가 0보다 크다면, 쓰지 않는 오브젝트가 존재하기에 해당 오브젝트의 부모를 변경하고 활성화 시킨 후, 스택에서 제거합니다. 반대로 크기가 0이하라면 사용 가능한 오브젝트가 없다는 것을 의미합니다. 따라서 Create()로 생성시켜 반환합니다. |
3. ResourceManager
GameObject를 Load할 때는 ResourceManager를 사용하기 때문에 ResourceManager에서 해당 오브젝트가 오브젝트 풀링을 사용하는 오브젝트 인지 판단하고 그에 맞게 사용해야 합니다. 다음은 ResourceManager 스크립트 입니다.
public class ResourceManager
{
public T Load<T>(string path) where T : Object
{
if (typeof(T) == typeof(GameObject))
{
string name = path;
int index = name.LastIndexOf('/');
if (index >= 0)
name = name.Substring(index + 1);
GameObject go = MasterManager.Pool.GetOriginal(name);
if (go != null)
return go as T;
}
return Resources.Load<T>(path);
}
public GameObject Instantiate(string path, Transform parent =null)
{
GameObject original = Load<GameObject>($"Prefabs/{path}");
if (original == null)
return null;
if (original.GetComponent<Poolable>() != null)
return MasterManager.Pool.Pop(original, parent).gameObject;
GameObject go = Object.Instantiate(original, parent);
go.name = original.name;
return go;
}
public void Destroy(GameObject go)
{
if (go == null)
return;
Poolable poolable = go.GetComponent<Poolable>();
if(poolable!=null)
{
MasterManager.Pool.Push(poolable);
return;
}
Object.Destroy(go);
}
}
'Unity ToyProject > RPG' 카테고리의 다른 글
DataManager (0) | 2023.07.30 |
---|---|
WorldSpace UI (0) | 2023.07.29 |
SoundManager (0) | 2023.07.27 |
QuaterView Camera (0) | 2023.07.26 |
Player 이동 (0) | 2023.07.24 |