이번에는 디자인 패턴 중에 Object Pool Pattern에 대해서 정리해보려고 합니다.
저번에 3가지 디자인 패턴을 알아보았는데 이번 패턴도 게임에서 상당히 많이 사용됩니다.
Object Pool Pattern은 자주 사용하는 오브젝트를 미리 생성해두고,
필요할 때 꺼내 쓰고 다시 반납하는 방식의 패턴입니다.
즉, 사용한 오브젝트를 재활용하는 기법이라고 생각하시면 됩니다.
오브젝트의 생성과 파괴가 반복적으로 일어날 경우에는
프레임 드랍이나 GC에 인한 메모리 단편화 증상이 생길 수 있다고 했었죠?
Object Pool Pattern은 단순한 활성화와 비활성화 작업이기 때문에
생성과 파괴보다 속도가 빠르고 메모리 단편화 증상을 예방할 수 있습니다.
하지만 미사용중에도 메모리 공간을 차지하고 있어서
필요한 크기와 양을 설정해서 사용하는 것 중요합니다.
Object Pool Pattern이 상당히 많이 사용되는 부분은 총입니다.
FPS 게임의 경우 많은 탄환을 사용하기 때문에 프레임 드랍을 피하려면
Object Pool을 필수로 구현할 수 밖에 없습니다.
ReturnToPool을 만들어 반납하는 즉시 비활성화 시킵니다.
using UnityEngine;
public class Bullet : MonoBehaviour
{
public float speed = 10f;
void Update()
{
transform.Translate(Vector3.forward * speed * Time.deltaTime);
}
public void Init(Vector3 position)
{
transform.position = position;
gameObject.SetActive(true);
}
public void ReturnToPool() // 총알을 다시 풀에 반납할 때 비활성화함.
{
gameObject.SetActive(false);
}
}
Object Pool Pattern은 보통 먼저 만들어진 오브젝트를 먼저 꺼내 쓰고,
사용 후 다시 뒤에 넣는 방식인 큐를 많이 사용합니다.
선입선출 방식이라 직관적이고 쉽기도 하고요.
물론 스택, 리스트, 배열로도 구현할 수 있습니다!
스택의 경우는 후입선출 구조로 사용한 오브젝트를 바로 다시 꺼낼 수 있어
짧은 시간 간격으로 재사용되는 경우에 좋겠죠.
아무래도 가장 최근에 반납한 총알을 다시 쓰는 게 빠르고 효율적이기 때문입니다.
예를 들면 Dodge 게임에서 스택을 사용하면 유리하겠죠
일단 밑에는 큐를 사용해서 구현한 코드입니다.
using System.Collections.Generic;
using UnityEngine;
public class BulletPool : MonoBehaviour
{
public static BulletPool Instance; // 싱글톤으로 전역 접근이 가능
public GameObject bulletPrefab;
public int poolSize = 10; // 처음에 몇 개 생성할지 정하는 것
private Queue<GameObject> pool = new Queue<GameObject>(); // 총알들을 보관(큐 사용)
void Awake() // 시작하면 총알 프리팹을 poolSize만큼 미리 생성하고 비활성화한 뒤, 큐에 저장
{
Instance = this;
for (int i = 0; i < poolSize; i++)
{
GameObject bullet = Instantiate(bulletPrefab);
bullet.SetActive(false);
pool.Enqueue(bullet);
}
}
public GameObject GetBullet(Vector3 position)
{
// 큐에 남아있는 게 있다면 꺼냄 (Dequeue()).
GameObject bullet = pool.Count > 0 ? pool.Dequeue() : Instantiate(bulletPrefab);
bullet.GetComponent<Bullet>().Init(position);
return bullet;
}
public void ReturnBullet(GameObject bullet) // 총알을 다시 풀에 반납
{
// ReturnToPool()로 비활성화
bullet.GetComponent<Bullet>().ReturnToPool();
// 큐에 다시 넣기 (Enqueue()).
pool.Enqueue(bullet);
}
}
Gun은 총알을 발사하고 되돌리는 부분입니다.
코루틴을 시작하여 2초 기다린 후, 총알을 풀로 돌려보내는 거죠.
using UnityEngine;
public class Gun : MonoBehaviour
{
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
GameObject bullet = BulletPool.Instance.GetBullet(transform.position);
// StartCoroutine()으로 2초 뒤에 총알을 다시 풀로 돌려보냄.
StartCoroutine(ReturnBulletAfterTime(bullet, 2f));
}
}
private IEnumerator ReturnBulletAfterTime(GameObject bullet, float delay)
{
// 코루틴으로 2초 기다린 후, 총알을 풀로 돌려보냄.
yield return new WaitForSeconds(delay);
BulletPool.Instance.ReturnBullet(bullet);
}
}