Unity 게임 만들기 프로젝트 - PoolManager
- -
1.Pool Manager
Unity 에서 Object 가 필요할 때 마다 Load 로 불러오면 엄청난 과부하와 함께 성능의 하락을 가져올 수 있다.
그래서 필요하거나 동일한 Object의 Pool 을 생성하고 일정 개수를 Pool 이 가지고 있도록 하여 꺼내쓰면 Load 를 반복하는 횟수가 적어져서 성능의 향상을 가져올 수 있다.
# Poolable.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Poolable : MonoBehaviour
{
public bool IsUsing;
}
단순히 Pooling 대상인지 확인할 수 있는 Component, 사용될 수 있으니 IsUsing 이라는 boolean 값도 하나 추가되어 있다.
# PoolManager.cs
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<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);
//DontDestoryOnLoad 방지 .
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);
}
}
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 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();
}
}
내부엔 PoolManager 가 관리할 Pool 이라는 class 가 있다.
이 Pool Class 에는 Original 이라는 원본 객체와 Root 라는 Pool 객체들이 모일 부모 컴포넌트를 가지고 있다.
그리고 _poolStack 이라는 Poolable 타입을 저장하는 Stack 구조가 선언되어 있다.
- Init 함수에선 original 을 입력받아서, original_Root 라는 GameObject 를 만들고 입력받은 Count만큼 생성하여 Create -> Push
- Create 함수는 Original 을 통해 Game Object 를 생성하고 이름을 동일하게 지정해준다 (Clone 제거)
그리고 여기서 생성된 GameObject 는 Pool 대상이기 때문에 Poolable 컴포넌트를 생성 및 연결하고 Poolable 을 반환한다.
- Push 함수는 Poolable 을 확인하여 아니라면 return, 맞다면 Root 의 자식 컴포넌트로 연결, SetActive, IsUsing 을 비활성화
- Pop 함수는 _poolStack 에 존재한다면 꺼내고, 없다면 Create 수행 SetActive 를 true 로 바꿔준다.
DontDestroyLoad 로 한번 가면 넣었다가 꺼낼 때 DDL 로 가기 때문에 Scene 으로 한번 갔다가 가도록 코드가 추가됨
매개변수로 받은 parent 를 부모로 설정하여 객체를 받아온다.
이제 PoolManager 는 위의 Pool 클래스와 매서드를 이용하여 관리하는 방안을 구현한다.
_pool 이라는 딕셔너리는 string key 값과 Pool 객체를 value 로 가지는 Dictionary 이다.
그리고 _root 라는 Transform 이 선언되어 있다.
- Init 함수는 _root 가 지정된 객체가 없다면 @Pool_Root 객체를 생성한다. 그리고 DontDestoryOnLoad 로 설정한다.
이제는 모든 Pool 객체는 @Pool_Root 밑으로 모이도록 설정한다.
- CreatePool 함수는 GameObject 와 Count 를 받고, 새로운 Pool 객체 생성, Pool의 Init 을 수행한다.
그리고 위에서 설정한 _root 를 부모로 설정하면 {original_name}_Root 라는 객체 밑에 5개의 Poolable 객체가 생성되고,
여기서 생성된 객체는 @Pool_Root 밑으로 모이게 된다.
- Push 함수는 Poolable 을 매개변수로 받아서, 해당 GameObject 의 name 을 저장, 만약 _pool 에 없다면 해당 GameObject 를 삭제한다. 존재한다면 Pool 의 Push 를 사용하여 다시 Pooling 하게 된다.
- Pop 함수는 Pool 에 존재한다면 Pop 을 이용해서 꺼낸 다음 객체를 반환하고, 존재하지 않는다면 Pool 을 생성한다.
- GetOriginal 함수는 pool에 없다면 null 반환, 존재한다면 Original 을 반환한다.
- Clear 함수는 모든 pool을 제거한다.
즉 _pool 이라는 Dictionary 는 string, Pool 객체로 이루어지고, Pool 객체 내부엔 Original 객체와 poolStack 을 가지고 있어서,
언제든 꺼내거나 다시 담을 수 있게 해준다.
이제 기존에 ResourceManager 에서 무조건 생성하던 부분이 Pool 에 있다면 꺼내쓰고, 없다면 생성하도록 수정되어야 한다.
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 = Managers.Pool.GetOriginal(name);
if (go != null)
return go as T;
}
return Resources.Load<T>(path);
}
단순히 로드하던 부분이, GameObject 일 때 Pool 에 존재한다면 GetOriginal 을 받아오도록 수정되었다.
Resources.Load 메서드는 부하가 있기 때문에 최소한으로 수행할 수 있도록 Pool에 있는 값을 가져온다.
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;
GameObject go = Object.Instantiate(original, parent);
go.name = original.name;
return go;
}
Poolable 컴포넌트가 없다면 Pool 대상이 없기 때문에 기존과 동일하게 동작. 존재한다면 Pooling 대상 오브젝트이므로,
Pool 의 Pop 메서드를 통해서 GameObject 를 반환해준다. 이 역시도 Instantiate 를 최소화 하여 기존 객체를 재활용한다.
public void Destroy(GameObject go)
{
if(go == null)
return;
// 풀링이 필요하면 풀링 매니저한테 위탁
Poolable poolable = go.GetComponent<Poolable>();
if (poolable != null)
{
Managers.Pool.Push(poolable);
return;
}
Object.Destroy(go);
}
Destroy 도 poolable 이 적용된 오브젝트라면 Push 를 통해 오브젝트를 삭제하지 않고 Pool 에 Push 하여 관리한다.
'⇥ 2D Game > Unity' 카테고리의 다른 글
Unity 게임 만들기 프로젝트 - DataManager (0) | 2024.06.11 |
---|---|
Unity 게임 만들기 프로젝트 - Coroutine (0) | 2024.06.10 |
Unity 게임 만들기 프로젝트 - SoundManager (0) | 2024.06.10 |
Unity 게임 만들기 프로젝트 - SceneManager (0) | 2024.06.10 |
Unity 게임 만들기 프로젝트 - UI Manager, 자동화 (2) | 2024.06.10 |
소중한 공감 감사합니다