Object Pooling
Object Pooling은 매번 Instantiate/Destroy 대신 인스턴스를 재사용해 GC 스파이크와 끊김을 줄입니다. 총알·FX·적 웨이브에 쓰며 UnityEngine.Pool·사전 워밍·상태 리셋의 기초를 배우는 성능 가이드입니다. 도식과 시뮬레이터로 익힙니다.
상상해 보세요...
커피잔을 쓸 때마다 새로 사서 → 쓰고 → 버리는 대신(Instantiate → 사용 → Destroy), 식당은 접시 풀을 씁니다: 잔 20개를 준비해 두고, 하나 꺼내고, 다시 선반에 둡니다. 더 사지 않고, 버리지 않아——Garbage Collection 제로. Unity에서 GC.Collect()는 피크 타임에 주방에 들이닥치는 청소부——프레임 드롭입니다. 풀링이 그걸 막습니다.
개념 자세히
Instantiate/Destroy 문제: 매번
Instantiate()
는 새 GameObject를 할당하고 컴포넌트 데이터를 복사하며 씬에 등록합니다.
Destroy()
는 메모리를 GC 대상으로 표시합니다. 슈터는 초당 30–50발을 스폰할 수 있어——잦은 GC, 전형적인
끊김(몇 초마다 「갑작스러운 랙」).
Object Pool 패턴: 로드 시 고정 개수를 미리 스폰합니다. 필요할 때 풀에서 꺼냅니다(비활성 상태였음). 끝나면 비활성화해 반환합니다. 메모리는 평탄하고 GC가 트리거되지 않습니다.
Unity 2021+부터
UnityEngine.Pool
이
ObjectPool<T>,
LinkedPool<T>,
GenericPool<T>
를 제공합니다——내장, 손수 큐 불필요. 콜백: onCreate, onGet,
onRelease, onDestroy(풀이 가득 찰 때).
전형적 용도: 총알, 히트/폭발 FX, 떠다니는 데미지 숫자, 적/NPC 스폰, 발소리 AudioSource, UI 토스트, 파티클 인스턴스. 분당 1회 미만 스폰이면 풀링을 건너뛰세요——관리 오버헤드가 이득보다 큽니다.
풀 동작 방식
실습 단계
IPoolable 인터페이스 만들기
OnGet()(풀에서 꺼낼 때 상태 리셋)과 OnRelease()(반환 전 정리).
ObjectPool 만들기
new ObjectPool<T>(onCreate, onGet, onRelease, onDestroy, collectionCheck, defaultCapacity, maxSize) — 2021+ 내장.
pool.Get() / pool.Release() 사용
Instantiate를 pool.Get()으로, Destroy를 pool.Release()로 대체. onGet에서 위치/속도/상태를 리셋.
로드 시 풀 사전 워밍
게임 시작 시 N개를 스폰했다가 즉시 반환해, 빈 풀에서 첫 프레임이 스파이크나지 않게 합니다.
pool.CountAll / pool.CountActive 모니터링
통계를 로그해 풀이 충분한지 봅니다. countActive/countAll이 높으면 defaultCapacity를 올립니다.
인터랙티브 시뮬레이터
Instantiate/Destroy vs Object Pool 비교——Fire를 눌러 GC 차이를 보세요.
Pool: 5/12
Allocations
0
GC Events
0
Active Objects
0
코드 예제
기본Queue로 만든 단순 총알 풀——Unity 패키지 불필요.
using UnityEngine;
using System.Collections.Generic;
public class BulletPool : MonoBehaviour
{
public GameObject bulletPrefab;
public int poolSize = 30;
readonly Queue<GameObject> pool = new Queue<GameObject>();
void Awake()
{
// Pre-warm: create and disable
for (int i = 0; i < poolSize; i++)
{
var go = Instantiate(bulletPrefab);
go.SetActive(false);
go.transform.SetParent(transform);
pool.Enqueue(go);
}
}
public GameObject Get(Vector3 pos, Quaternion rot)
{
var go = pool.Count > 0
? pool.Dequeue()
: Instantiate(bulletPrefab); // Expand if empty
go.transform.SetPositionAndRotation(pos, rot);
go.SetActive(true);
return go;
}
public void Release(GameObject go)
{
go.SetActive(false);
pool.Enqueue(go);
}
}코드 예제
고급UnityEngine.Pool.ObjectPool<T>(Unity 2021+) — 내장 자동 확장과 콜백.
using UnityEngine;
using UnityEngine.Pool;
public class AdvancedBulletPool : MonoBehaviour
{
public Bullet bulletPrefab;
ObjectPool<Bullet> pool;
void Awake()
{
pool = new ObjectPool<Bullet>(
createFunc: () => Instantiate(bulletPrefab, transform),
actionOnGet: b => { b.gameObject.SetActive(true); b.OnGet(); },
actionOnRelease: b => { b.gameObject.SetActive(false); b.OnRelease(); },
actionOnDestroy: b => Destroy(b.gameObject), // When the pool is full
collectionCheck: false, // Skip duplicate checks (production)
defaultCapacity: 20,
maxSize: 50
);
}
public Bullet Spawn(Vector3 pos, Vector3 dir)
{
var bullet = pool.Get();
bullet.transform.position = pos;
bullet.Init(dir, pool); // Bullet calls pool.Release(this) when done
return bullet;
}
public void LogStats()
{
Debug.Log($"Pool: {pool.CountActive} active / {pool.CountAll} total");
}
}📌 빠른 정리
- ▸Pool = 오브젝트 재사용, GC 할당 회피
- ▸로드 시 사전 워밍으로 첫 프레임 스파이크 방지
- ▸UnityEngine.Pool.ObjectPool<T> — 2021+ 내장
- ▸상태는 Update가 아니라 onGet에서 리셋
- ▸collectionCheck=true는 디버그용으로 Editor에서만
⚠️ 흔한 실수
❌ 이미 활성인 오브젝트를 Release(이중 해제)
두 번 반환 → 풀 손상 → 깜빡임 또는 크래시
✅ 개발 중 collectionCheck=true; Release() 전 null 가드
❌ Get 시 상태를 리셋하지 않음
이전 사용의 속도·색·체력이 남음
✅ actionOnGet 콜백에서 완전히 리셋