Unity Term Book
고급 기법

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.PoolObjectPool<T>, LinkedPool<T>, GenericPool<T> 를 제공합니다——내장, 손수 큐 불필요. 콜백: onCreate, onGet, onRelease, onDestroy(풀이 가득 찰 때).

전형적 용도: 총알, 히트/폭발 FX, 떠다니는 데미지 숫자, 적/NPC 스폰, 발소리 AudioSource, UI 토스트, 파티클 인스턴스. 분당 1회 미만 스폰이면 풀링을 건너뛰세요——관리 오버헤드가 이득보다 큽니다.

풀 동작 방식

Get()
Pool→ dequeue inactive object → SetActive(true) → returnGame Object
Release()
Game Object→ Reset state → SetActive(false) → enqueue back to poolPool
Pool full?
Release()풀이 가득 참 (countAll == maxSize) → Destroy(object) 또는 풀 확장🗑 Destroy

실습 단계

1

IPoolable 인터페이스 만들기

OnGet()(풀에서 꺼낼 때 상태 리셋)과 OnRelease()(반환 전 정리).

2

ObjectPool 만들기

new ObjectPool<T>(onCreate, onGet, onRelease, onDestroy, collectionCheck, defaultCapacity, maxSize) — 2021+ 내장.

3

pool.Get() / pool.Release() 사용

Instantiate를 pool.Get()으로, Destroy를 pool.Release()로 대체. onGet에서 위치/속도/상태를 리셋.

4

로드 시 풀 사전 워밍

게임 시작 시 N개를 스폰했다가 즉시 반환해, 빈 풀에서 첫 프레임이 스파이크나지 않게 합니다.

5

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 콜백에서 완전히 리셋