본문 바로가기
개발/Unity

Unity 내장 오브젝트 풀(Object Pool) 사용하기

by DinoDev 2025. 10. 13.
728x90
반응형

게임을 만들다 보면 총알, 이펙트, 몬스터처럼 짧은 시간 동안 반복적으로 생성되고 파괴되는 오브젝트를 자주 다룬다.
이런 경우 단순히 Instantiate()와 Destroy()를 반복하면 성능 저하와 GC(Garbage Collection) 부하가 발생한다.

이 문제를 해결하기 위한 대표적인 패턴이 바로 오브젝트 풀(Object Pool) 이다.

Unity 2021 버전 이후부터는 UnityEngine.Pool 네임스페이스에
내장 풀 시스템이 추가되어, 직접 구현하지 않아도 효율적인 풀을 쉽게 사용할 수 있다.


💡 오브젝트 풀(Object Pool) 이란?

“필요할 때마다 새로 만들지 말고, 미리 만들어둔 오브젝트를 재사용하자.”

오브젝트 풀은 자주 사용되는 오브젝트를 미리 생성해두고 필요할 때 꺼내서 쓰고, 다 쓰면 비활성화 후 다시 보관하는 구조다.

예를 들어, 슈팅 게임에서 총알을 Instantiate()로 계속 만들면

  • 생성 시 메모리 할당
  • 파괴 시 GC 수집
    이 반복되며 프레임 드랍이 발생할 수 있다.

하지만 오브젝트 풀을 사용하면 한 번 만들어진 총알을 재활용하므로 성능이 일정하게 유지된다.


🧩 Unity 내장 풀 시스템 소개

Unity는 UnityEngine.Pool 네임스페이스 아래에 제너릭 기반의 ObjectPool<T> 클래스를 제공한다.

이 클래스를 사용하면 오브젝트 생성, 회수, 삭제 등의 과정을 콜백 기반으로 손쉽게 관리할 수 있다.


🧱 기본 사용 예시

아래는 ObjectPool<GameObject>를 이용해 Enemy를 관리하는 간단한 예시다.

using UnityEngine;
using UnityEngine.Pool;

public class EnemySpawner : MonoBehaviour
{
    [SerializeField] private GameObject enemyPrefab;
    private ObjectPool<GameObject> enemyPool;

    [SerializeField] private int initialPoolSize = 10;
    [SerializeField] private int maxPoolSize = 50;

    void Awake()
    {
        // 오브젝트 풀 초기화
        enemyPool = new ObjectPool<GameObject>(
            createFunc: CreateEnemy,
            actionOnGet: OnTakeFromPool,
            actionOnRelease: OnReturnedToPool,
            actionOnDestroy: OnDestroyPoolObject,
            collectionCheck: false,
            defaultCapacity: initialPoolSize,
            maxSize: maxPoolSize
        );
    }

    // Enemy 오브젝트 생성
    private GameObject CreateEnemy()
    {
        var enemy = Instantiate(enemyPrefab);
        enemy.GetComponent<Enemy>().SetPool(enemyPool);
        return enemy;
    }

    // 풀에서 꺼낼 때 호출
    private void OnTakeFromPool(GameObject enemy)
    {
        enemy.SetActive(true);
    }

    // 풀에 반환될 때 호출
    private void OnReturnedToPool(GameObject enemy)
    {
        enemy.SetActive(false);
    }

    // 풀 크기를 초과해 제거될 때 호출
    private void OnDestroyPoolObject(GameObject enemy)
    {
        Destroy(enemy);
    }

    // Enemy 스폰 및 초기화
    public void SpawnEnemy(Vector3 position)
    {
        var enemy = enemyPool.Get();
        enemy.GetComponent<Enemy>().Init(position);
    }
}
 

👾 Enemy 스크립트 예시

Enemy가 사라질 때 스스로 풀로 돌아가도록 구성한다.

 
using UnityEngine;
using UnityEngine.Pool;

public class Enemy : MonoBehaviour
{
    private ObjectPool<GameObject> pool;
    private int maxHp = 100;
    private int currentHp;

    public void SetPool(ObjectPool<GameObject> objectPool)
    {
        pool = objectPool;
    }

    // 위치와 상태를 초기화하는 함수
    public void Init(Vector3 spawnPosition)
    {
        currentHp = maxHp;
        transform.position = spawnPosition;

        // 추가 초기화: 애니메이션, AI 상태 등
        // Rigidbody 속도 초기화 필요 시 여기서
        var rb = GetComponent<Rigidbody>();
        if (rb != null) rb.velocity = Vector3.zero;
    }

    public void TakeDamage(int damage)
    {
        currentHp -= damage;
        if (currentHp <= 0)
        {
            Die();
        }
    }

    private void Die()
    {
        pool.Release(gameObject);
    }
}

🧠 주요 파라미터 설명

파라미터 설명
createFunc 오브젝트를 새로 생성할 때 호출되는 함수
actionOnGet 풀에서 오브젝트를 꺼낼 때 호출 (예: SetActive(true))
actionOnRelease 오브젝트를 다시 반환할 때 호출 (예: SetActive(false))
actionOnDestroy 풀 크기를 초과해 제거될 때 호출
collectionCheck 중복 반환 감지 여부 (디버깅용, 성능 저하 가능)
defaultCapacity 초기 생성 개수
maxSize 최대 풀 크기 (초과 시 actionOnDestroy 호출)

🚀 장점

직접 구현 불필요
기존에 직접 Queue나 Stack으로 풀을 만들 필요가 없다.

일관된 인터페이스
Get(), Release() 메서드만으로 간단히 제어 가능.

성능 최적화
내부적으로 C++ 레벨에서 관리되어
직접 구현한 C# 풀보다 메모리 관리가 효율적이다.

자동 메모리 관리
최대 크기 초과 시 자동으로 파괴(OnDestroy)가 호출된다.


⚡ 실무 적용 팁

  1. 공통 풀 관리 클래스 작성하기
    여러 종류의 오브젝트를 관리한다면,
    풀 초기화를 통합 관리하는 PoolManager 클래스를 두는 것이 좋다.
  2. 비활성화 시 상태 초기화하기
    오브젝트를 반환할 때(actionOnRelease)
    위치, 체력, 속도 등 상태를 초기화하면 다음 재사용 시 안정적이다.
  3. maxSize 적정선 정하기
    너무 크게 잡으면 메모리 낭비,
    너무 작으면 빈번한 생성/파괴가 다시 발생한다.
    플레이 환경에 맞는 균형 잡힌 크기를 실험적으로 정하자.

🧾 정리

  직접 구현한 풀 Unity 내장 풀
구현 난이도 높음 매우 쉬움
제너릭 지원 직접 구현 필요 기본 지원
자동 관리
성능 좋음 더 안정적
유지보수 어려움 용이함

🧩 결론

  • Unity 2021 이후 버전에서는 내장 오브젝트 풀(ObjectPool) 을 적극 활용하자.
  • Instantiate() / Destroy()의 반복을 줄이면 프레임 안정성이 크게 향상된다.
  • 적, 총알, 이펙트 등 자주 생성·파괴되는 오브젝트에 필수적이다.
  • ObjectPool<T>는 가독성, 안정성, 성능을 모두 잡은 Unity의 정답 같은 기능이다.
728x90
반응형