빠른 흐름
GameObject item = pool[index];
item.SetActive(true);기본 흐름
풀링이 필요한 이유
Instantiate와 Destroy를 매번 호출하면 오브젝트 생성과 삭제에 따른 메모리 할당·해제가 반복되고, GC(가비지 컬렉터) 수집 타이밍에 프레임 드랍이 발생할 수 있습니다. 오브젝트 풀링은 필요할 때마다 새로 만들지 않고, 미리 생성해 둔 오브젝트를 비활성화했다가 다시 활성화해서 재사용하는 방식입니다. 총알, 폭발 이펙트, 데미지 숫자처럼 짧은 생명주기를 자주 반복하는 대상에 특히 잘 맞습니다.
기본 풀 구현 예제
아래는 비활성 오브젝트를 먼저 찾고, 없으면 새로 생성해 풀에 추가하는 단순한 구현입니다.
[SerializeField] private GameObject bulletPrefab;
private readonly List<GameObject> pool = new();
private GameObject GetBullet()
{
foreach (GameObject item in pool)
{
if (!item.activeSelf)
{
item.SetActive(true);
return item;
}
}
GameObject created = Instantiate(bulletPrefab);
pool.Add(created);
return created;
}반환할 때는 SetActive(false)로 비활성화해 풀에 돌려보내면 됩니다. Unity 2021 이상에서는 UnityEngine.Pool.ObjectPool<T>를 사용하면 더 완성도 높은 구조를 빠르게 구성할 수 있습니다.
상태 초기화와 풀 크기 설계
풀에서 오브젝트를 꺼낼 때 이전 사용 상태가 남아 있으면 재사용 버그가 발생합니다. 위치, 속도, 타이머, 체력 등 오브젝트의 모든 가변 상태를 꺼내는 시점에 초기화해야 합니다. 또한 초기 생성 비용과 런타임 성능을 맞바꾸는 전략이므로, 너무 큰 풀을 무조건 만들면 메모리를 낭비하게 됩니다. 실제 동시 사용 최대치를 기준으로 풀 크기를 결정하는 것이 좋습니다.
체크포인트
| 상황 | 적합한 선택 |
|---|---|
| 총알, 이펙트처럼 짧은 수명으로 자주 반복 | 오브젝트 풀링 |
| 생성 빈도가 낮거나 수명이 긴 오브젝트 | Instantiate / Destroy |
| Unity 2021+, 완성도 높은 풀 구조 | UnityEngine.Pool.ObjectPool<T> |
| 풀에서 꺼낼 때 상태 초기화 | 위치·속도·타이머 등 가변 상태 전부 재설정 |
| 풀 크기 설계 | 실제 동시 사용 최대치 기준으로 결정 |
| 선택 기준 | 풀링이 더 잘 맞는 경우 | 그냥 생성/삭제가 더 단순한 경우 |
|---|---|---|
| 생성 빈도 | 매초 여러 번 반복 생성 | 드물게 생성되고 오래 살아 있음 |
| 상태 복잡도 | 초기화 규칙이 명확함 | 내부 상태가 복잡해 리셋 비용이 큼 |
| 메모리 여유 | 미리 확보할 메모리를 감당 가능 | 미리 쌓아 둘 메모리가 아까움 |
| 운영 난도 | 풀 크기와 반환 시점을 추적 가능 | 수명주기가 단순하고 추적이 쉬움 |
주의할 점
풀에서 꺼낼 때 상태를 초기화하지 않으면 이전 사용 흔적이 남아 재사용 버그가 발생합니다.
// ❌ 상태 초기화 없이 재활성화 — 이전 위치·속도가 그대로 남음
GameObject bullet = GetBullet();
bullet.SetActive(true);
// ✅ 상태를 초기화한 뒤 활성화
GameObject bullet = GetBullet();
bullet.transform.position = firePoint.position;
bullet.GetComponent<Rigidbody>().velocity = Vector3.zero;
bullet.SetActive(true);풀링은 Destroy를 안 쓰는 것만으로 끝나지 않습니다. 반환 누락과 과도한 초기 풀 크기 설정은 메모리 낭비와 오브젝트 고갈을 같이 부릅니다.
// ❌ 동시 사용량 20개인데 500개를 미리 생성
시작 로딩 시간 증가
메모리 상주량 증가
// ❌ 사용 후 반환을 놓침
GetBullet()만 있고 ReturnBullet()이 안정적으로 호출되지 않음
→ 결국 새로 Instantiate가 다시 발생
// ✅ 관찰된 최대 동시 사용량을 기준으로 잡기
평균 동시 사용 12개, 피크 18개
→ 초기 풀 20개 + 부족 시 점진 확장참고 링크
2 sources