개념
Object Pooling이란 최적화를 하는 방법 중 하나이다. 주로 생성(Instantiate)과 소멸(Destroy)을 반복하는 오브젝트에 적용한다. 이는 힙 영역에 빈번한 메모리의 할당과 해제가 일어나는 것을 막아줘서, Garbage Collector의 부담을 줄인다.
적용 방식
Pool Manager에 모든 Instantiate를 위임한다. (Spawner와 같은 개별 객체가 Instantiate를 하기 위해선 public static PoolManager instance;처럼 선언했다는 가정 하에, PoolManager.instance.MyInstantiate(~~~)처럼, 반드시 PoolManager를 거쳐가야 한다)
MyInstantiate()는:
1. 만일 화면 내에 Instantiate를 하고 싶은 객체가 SetActive(false) 상태로 존재한다면? 그걸 SetActive(true)한다.
2. 만일 화면 내에 Instantiate를 하고 싶은 객체가 없다면? Instantiate()한다.
여기에서 핵심은,
원래는 Destroy(gameObject) 후 Instantiate를 함으로써 Instantiate와 Destroy를 반복하며 객체를 사용했다면,
Object Pooling에서는, 원래는 Destroy()를 할 부분에 SetActive(false)를 넣어서 오브젝트를 재사용한다는 것이다.
Pool Manager
using UnityEngine;
using System.Collections.Generic;
public class PoolManager : MonoBehaviour
{
public static PoolManager instance;
private List<GameObject>[] pools; // 한 칸에 리스트 한 개를 담는 2차원 배열
[SerializeField] private GameObject[] all_prefabs;
private void Awake()
{
instance = this;
pools = new List<GameObject>[all_prefabs.Length];
for (int i = 0; i < pools.Length; i++) // prefab의 종류만큼 list를 만든다.
{
pools[i] = new List<GameObject>();
}
}
// about "index"
// Bird(0), Net(1)...
public GameObject Get(int index)
{
GameObject select = null;
for (int i = 0; i < pools[index].Count; i++)
{
if (pools[index][i].activeSelf == false)
{
select = pools[index][i];
select.SetActive(true);
break;
}
}
if (select == null)
{
select = Instantiate(all_prefabs[index], transform);
pools[index].Add(select);
}
return select;
}
public GameObject Get(int index, Vector2 position) // overloading, 특정 위치에 스폰해야 하는 경우
{
GameObject select = null;
foreach (GameObject item in pools[index])
{
if (item.activeSelf == false)
{
select = item;
select.transform.position = position;
select.SetActive(true);
break;
}
}
if (select == null)
{
select = Instantiate(all_prefabs[index]);
select.transform.position = position;
pools[index].Add(select);
}
return select;
}
}
public static PoolManager instance;는 다른 코드에서도 PoolManager를 자유롭게 참조하기 위함이다. 예를 들어 Spawner 코드에서...
Spawner
using UnityEngine;
using System.Collections;
public class Spawner : MonoBehaviour
{
private int level;
private void Awake()
{
level = 1;
}
private void Start()
{
StartCoroutine(SpawnEnemy());
}
IEnumerator SpawnEnemy()
{
while (true)
{
PoolManager.instance.Get(Random.Range(0, level));
yield return new WaitForSeconds(1f);
}
}
}
이런 식으로 쉽게 사용할 수 있다. 여기에선 Get이 MyInstantiate와 같은 역할을 하고 있다. (참고로 0은 Bird 객체를 나타낸다) 그럼 Object Pooling을 적용한 객체는 어떤 식으로 구성되어 있는지 보자.
Bird - Enemy 클래스 상속
using UnityEngine;
public class Bird : Enemy
{
private Rigidbody2D rb;
private float speed;
private int lane;
private void Awake()
{
// Enemy class
max_health = 5;
// my class
rb = GetComponent<Rigidbody2D>();
speed = -5f;
// lane (매번 OnEnable마다 랜덤으로 할당)
}
protected override void Start()
{
base.Start();
lane = Random.Range(-1, 2); // -1 ~ 1
transform.position = new Vector2(10, lane * 3.5f);
rb.linearVelocity = new Vector2(speed, 0);
}
private void OnEnable()
{
Start(); // 이해하기 쉽도록 이렇게 짬. 익숙해지면 Start를 지울 것 같다!
}
private void Update()
{
if (transform.position.x < -15f)
{
gameObject.SetActive(false);
}
}
}
이 코드를 보면 화면 밖으로 날아갔을 시 SetActive(false)를 하고, SetActive(true)가 된 순간을 OnEnable()을 통해 포착하여 초기화를 해주고 있는 것을 볼 수 있다. (참고로 Enemy class를 상속하고 있다)
Enemy (참고용)
using UnityEngine;
public class Enemy : MonoBehaviour
{
protected float health;
protected float max_health;
[SerializeField] private GameObject[] effect_particle;
protected virtual void Start()
{
InitStat();
}
private void InitStat()
{
health = max_health;
}
public void TakeDamage(float damage)
{
if (health - damage > 0)
{
GenerateEffect("Hit");
health -= damage;
}
else
{
Die();
}
}
private void Die()
{
GenerateEffect("Die");
gameObject.SetActive(false);
}
private void GenerateEffect(string effect_type)
{
if (effect_type == "Hit")
{
for (int i = 0; i < 10; i++) { GameObject temp = Instantiate(effect_particle[0], transform.position, transform.rotation); }
}
else if (effect_type == "Die")
{
for (int i = 0; i < 20; i++) { GameObject temp = Instantiate(effect_particle[1], transform.position, transform.rotation); }
}
}
}