Pool과 Prefab을 연동하여 구현하는 것을 오브젝트 풀이라고 한다.
오브젝트 풀 구현 방법에는 여러 가지가 있는데 오늘은 그중 유니티에 내장되어있는 오브젝트 풀에서 공부했다.
일단 일반적인 방법으로 Prefab 하나에 오브젝트 풀 스크립트를 붙여주는 것인데 이 방법의 단점은 Prefab을 하나 만들 때마다 오브젝트 풀 클래스를 만들어줘야하는 것이다. 그래서 유니티에서 지원하는 풀링을 이용해볼 것이다.
먼저 유니티에서 제공하는 Pool을 만들려면 OnCreate, OnGet, OnRelease, OnDestroy 콜백 함수 네 가지가 필요하다. 이 함수들을 IObjectPool 인터페이스를 상속하는 ObjectPool 클래스에 넣어준다. 제네릭으로 GameObject라는 자료형으로 제한한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;
public class Pool {
private GameObject _prefab; // 어떤 프리팹과 엮일 것인가.
private IObjectPool<GameObject> _pool; // 프리팹들을 담을 풀.
private Transform _root; // 오브젝트가 생성될 위치.
// 읽기 속성 프로퍼티 - 오브젝트가 비어있다면 생성해서 반환.
private Transform Root {
get {
if (_root == null) {
GameObject obj = new() { name = $"[Pool_Root] {_prefab.name}" };
_root = obj.transform;
}
return _root;
}
}
// 생성자 - MonoBehaviour를 상속받고 있지 않기 때문에 생성자로부터 초기화.
public Pool(GameObject prefab) {
this._prefab = prefab;
this._pool = new ObjectPool<GameObject>(OnCreate, OnGet, OnRelease, OnDestroy);
}
public GameObject Pop() { // 스택, 선입후출/후입선출 형식의 자료 꺼내기.
return _pool.Get(); // OnGet 호출.
}
public void Push(GameObject obj) { // 자료 들여보내기.
_pool.Release(obj); // OnRelease 호출.
}
// 유니티에서 제공하는 Pool을 사용하기 위한 콜백 함수.
#region Callbacks
private GameObject OnCreate() {
GameObject obj = GameObject.Instantiate(_prefab); // 오브젝트 생성.
obj.transform.SetParent(Root); // 생성될 위치 등록.
obj.name = _prefab.name; // 프리팹 이름 등록.
// List.Add(obj);
return obj;
}
private void OnGet(GameObject obj) { // Pool이 이미 있는 경우.
obj.SetActive(true);
}
private void OnRelease(GameObject obj) { // Pool을 사용하지 않을 때.
obj.SetActive(false);
}
private void OnDestroy(GameObject obj) { // Pool이 너무 많은 경우 정리.
GameObject.Destroy(obj);
}
#endregion
}
이제 Manager를 통해 Pool을 관리해보자.
public class PoolManager {
// 딕셔너리 생성.
private Dictionary<string, Pool> _pools = new();
public GameObject Pop(GameObject prefab) {
// #1. 풀이 없으면 새로 만든다.
if (_pools.ContainsKey(prefab.name) == false) {
CreatePool(prefab);
}
// #2. 해당 풀에서 하나 가져온다.
return _pools[prefab.name].Pop();
}
public bool Push(GameObject obj) {
// #1. 풀이 있는지 확인한다.
if (_pools.ContainsKey(obj.name) == false) return false;
// #2. 풀에 게임 오브젝트를 넣는다.
_pools[obj.name].Push(obj);
return true;
}
private void CreatePool(GameObject prefab) { // 유지보수를 위해 Pool 생성을 함수로 만든다.
Pool pool = new(prefab);
_pools.Add(prefab.name, pool);
}
// 씬에 변화가 있을 때 비워주기 위한 함수.
public void Clear() {
_pools.Clear();
}
}
테스트로 프리팹을 넣어 오브젝트 풀을 사용해보자.
public class BaseScene : MonoBehaviour {
private bool _initialized;
void Start() {
Initialize();
}
protected virtual bool Initialize() {
if (_initialized) return false;
// 각종 초기화 함수.
// DataManager 초기화
// GameManager 초기화
_initialized = true;
return true;
}
}
public class GameScene : BaseScene {
public GameObject prefab1;
public GameObject prefab2;
protected override bool Initialize() {
if (!base.Initialize()) return false;
GameObject obj1 = Main.Pool.Pop(prefab1);
GameObject obj2 = Main.Pool.Pop(prefab1);
GameObject obj3 = Main.Pool.Pop(prefab1);
GameObject obj4 = Main.Pool.Pop(prefab1);
GameObject obj5 = Main.Pool.Pop(prefab1);
Main.Pool.Push(obj2);
Main.Pool.Push(obj4);
return true;
}
}
유니티 ObjectPool은 캡슐화가 잘 되어있다. 내부 로직을 몰라도 CallBack 함수 네 가지만 사용해도 ObjectPool을 이용할 수 있기 때문에 협업에 유용하다.
Manager로 관리하는 경우에도 마찬가지이다. Pool 클래스를 몰라도 생성자와 Pop, Push 함수만 알아도 손쉽게 사용할 수 있다.
추가로 그렇기 때문에 협업에서 중요한 점은 내부 로직을 몰라도 함수명만 봐도 어떤 함수인지 알 수 있도록 쉽게 이용할 수 있게 명확히 표기하는 것이 좋다.
'부트캠프 > Study' 카테고리의 다른 글
Asset (0) | 2024.01.22 |
---|---|
<Unity> Item 관리 (0) | 2024.01.12 |
enable과 SetActive의 차이점 (0) | 2023.12.28 |
<프로젝트> Unity 게임 개발 심화 개인과제 (1) | 2023.12.27 |
<Unity> 프로젝트 빌드 하는 방법(Building) (1) | 2023.12.26 |