Quick Flow
풀의 핵심은 Instantiate를 없애는 것이 아니라 대여한 object는 한 번만 Release하고, 다음 Get 전에 완전한 초기 상태로 만든다는 계약입니다. Unity 6의 ObjectPool<T>는 이 lifecycle callback과 count를 제공합니다.
| 시점 | callback/API | 반드시 할 일 |
|---|---|---|
| pool에 없을 때 | createFunc | prefab instance 생성·owner 연결 |
| 대여 | Get, actionOnGet | 활성화와 spawn state 설정 |
| 반환 | Release, actionOnRelease | listener·velocity·timer·VFX 초기화 후 비활성 |
| max size 초과 반환 | actionOnDestroy | instance 실제 Destroy |
| 종료 | Clear / Dispose | inactive pooled object 정리 |
ObjectPool 설정
collectionCheck: true는 Editor/개발 중 같은 object를 두 번 Release했을 때 오류를 내므로 lifecycle bug를 빨리 찾는 데 유용합니다. defaultCapacity는 시작 시 모두 생성하는 수가 아니라 내부 stack capacity이고, 실제 prewarm 여부는 create policy로 분리해 생각합니다. maxSize는 inactive pool에 보관할 최대 수이며, 넘치는 return object는 destroy callback으로 처리됩니다.
using UnityEngine;
using UnityEngine.Pool;
public sealed class BulletPool : MonoBehaviour
{
[SerializeField] private Bullet bulletPrefab;
private IObjectPool<Bullet> pool;
private void Awake()
{
pool = new ObjectPool<Bullet>(Create, Take, Return, DestroyPooled,
collectionCheck: true, defaultCapacity: 16, maxSize: 64);
}
private Bullet Create()
{
Bullet bullet = Instantiate(bulletPrefab);
bullet.SetPool(pool);
return bullet;
}
private static void Take(Bullet bullet) => bullet.gameObject.SetActive(true);
private static void Return(Bullet bullet) => bullet.gameObject.SetActive(false);
private static void DestroyPooled(Bullet bullet) => Destroy(bullet.gameObject);
}CountActive, CountInactive, CountAll은 pool이 추적하는 대여·보관·총 생성 수를 보여 줍니다. 이를 gameplay telemetry로 쓰려면 double release와 외부 Destroy가 없는 하나의 owner 구조가 먼저여야 합니다.
반환과 reset
OnDisable이 곧 pool return이라는 규칙은 위험합니다. scene unload, 부모 비활성, 외부 destroy에서도 OnDisable이 올 수 있기 때문입니다. 발사체가 lifetime 종료, hit, owner cleanup 중 어느 경로로 끝나는지 한 ReleaseOnce 메서드로 모으고, 이미 반환된 object는 다시 반환하지 않게 합니다.
public sealed class Bullet : MonoBehaviour
{
[SerializeField] private Rigidbody body;
private IObjectPool<Bullet> pool;
private bool released;
public void SetPool(IObjectPool<Bullet> owner) => pool = owner;
public void Launch(Vector3 position, Vector3 velocity)
{
released = false;
transform.position = position;
body.linearVelocity = velocity;
}
public void ReleaseOnce()
{
if (released) return;
released = true;
body.linearVelocity = Vector3.zero;
pool.Release(this);
}
}reset에는 Transform, Rigidbody velocity, particle/trail, coroutine, event subscription, target reference, timer가 포함될 수 있습니다. 무엇을 reset해야 하는지는 prefab이 가진 state에 따라 달라지므로, pooling 전에 lifecycle을 목록화합니다.
자주 틀리는 부분
| 증상 | 원인 | 수정 |
|---|---|---|
| 같은 bullet이 두 번 pool에 들어감 | hit와 timeout이 모두 Release | ReleaseOnce와 collection check 사용 |
| 이전 target·trail·velocity가 남음 | active toggle만 하고 state reset 없음 | Get/Release callback과 object reset contract 작성 |
| pool이 계속 커짐 | 반환 누락 또는 maxSize 없음 | return 경로 계측, maxSize와 overflow destroy 설정 |
Clear 뒤 active bullet도 사라질 것으로 기대 | Clear는 inactive pooled item 정리 | active lease의 owner와 scene 종료 cleanup 별도화 |
| 간헐 생성 object까지 pool | 측정 없이 복잡도를 도입 | allocation·instantiate cost와 동시 사용량을 profile로 확인 |
참고 링크
2 sources