Object Pooling은 게임 오브젝트를 만들어 놓고, 필요할 때마다 활성화 시켜서 사용하는 기법이다.
필요할 때마다 활성화 시키는 것이 아닌 새로 만들고, 없애는 과정이 자주 발생하다 보면 게임 성능에 문제를 미칠 수 있다.
따라서 딕셔너리와 같은 자료구조에 오브젝트를 저장하고 필요할 때마다 꺼내서 사용한다.
게임오브젝트에는 SetActive라는 함수가 있는데, 인자로 false를 넣게 되면 유니티 엔진에서 비활성화 시킨 상태로 변하게 된다.
이 오브젝트는 자주 소환 되어 Object Pooling이 필요할 것 같을 때, 프리팹에 구분할 수 있는 컴포넌트를 붙여주어 자동으로 판단 할 수 있게 한다. 따라서 Poolable이라는 빈 C# 스크립트를 컴포넌트로 붙여줘서 판단할 수 있게 해준다.
PoolManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static UnityEngine.UI.Image;
public class PoolManager
{
class Pool
{ // 오브젝트 풀링을 위한 클래스를 선언해준다.
public GameObject Original { get; private set; }
// Instantiate을 할 때, 필요한 Original
public Transform Root { get; set; }
// 모든 오브젝트 풀링을 관리할 Root 객체
Stack<Poolable> _poolStack = new Stack<Poolable>();
// 오브젝트 풀링한 객체를 저장할 스택
public void Init(GameObject original, int count = 5)
{ // count만큼의 original을 Instantiate을 하여 스택에 저장한다.
Original = original;
Root = new GameObject().transform;
Root.name = $"{original.name}_Root";
for (int i = 0; i < count; i++)
{
Push(Create());
}
}
Poolable Create()
{ // Instantiate을 하여 객체를 생성
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;
}
}
Dictionary<string, Pool> _pool = new Dictionary<string, Pool>();
// 위에서 만든 Pool 클래스를 저장할 딕셔너리
Transform _root;
// 모든 Object Pooling 객체를 관리하는 부모 객체
public void Init()
{ // 부모 객체가 존재하지 않을 시 생성
if (_root == null)
{
_root = new GameObject { name = "@Pool_Root" }.transform;
Object.DontDestroyOnLoad(_root);
}
}
public void Push(Poolable poolable)
{ // 오브젝트를 관리하는 딕셔너리에 넣어주는 함수
string name = poolable.gameObject.name;
if (_pool.ContainsKey(name) == false)
{ // 만약 CreatePool을 하지 않고 넣어주려고 하면 객체를 없애고 반환
GameObject.Destroy(poolable.gameObject);
return;
}
_pool[name].Push(poolable);
// 해당하는 키에 객체를 넣어준다
}
public void CreatePool(GameObject original, int Count = 5)
{ // Pooling할 객체를 딕셔너리 안에 새로 저장해준다.
Pool pool = new Pool();
pool.Init(original, Count);
pool.Root.parent = _root;
_pool.Add(original.name, pool);
}
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();
}
}
위의 Pool Manager에서 Poolable이라는 새로운 클래스를 사용하는 것을 볼 수 있는데, 기존의 Resource Manager를 통해 Instantiate을 하거나 Destroy를 할 때 처럼 작동을 한다면 생각하는 로직과는 다른 결과가 나올 것이다.
따라서 Resource Manager도 수정을 해주어야한다.
아래는 수정 전 코드이다.
[UNITY] ResourceManager
using System.Collections; using System.Collections.Generic; using UnityEngine; public class ResourceManager { public T Load(string path) where T : Object { return Resources.Load(path); } public GameObject Instantiate(string path, Transform parent = null) {
mainsdev.tistory.com
아래는 변경한 코드이다.
ResourceManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
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 = Managers.Pool.GetOriginal(name);
// 만약 오브젝트 풀링을 하던 객체이면
// T 타입으로 Original을 반환해준다.
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)
{
Debug.Log($"Failed to load prefab : {path}");
return null;
}
// 만약 Poolable이 붙어있다면
if (original.GetComponent<Poolable>() != null)
return Managers.Pool.Pop(original, parent).gameObject;
//관리 딕셔너리에서 Pop을 해준 뒤 반환한다.
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>();
// 만약 삭제하려는 오브젝트가 Poolable이 붙어있다면
if(poolable != null)
{
Managers.Pool.Push(poolable);
// 관리 딕셔너리에 다시 넣는다.
return;
}
Object.Destroy(go);
}
}
'UNITY' 카테고리의 다른 글
UNITY - DataManager (0) | 2024.01.23 |
---|---|
UNITY - Coroutine (0) | 2024.01.23 |
UNITY - SoundManager (0) | 2024.01.22 |
UNITY - UI 자동화 - 게임 UI 구현 (0) | 2024.01.22 |
UNITY - Scene (1) | 2024.01.12 |