DevDino

[게임 디자인 패턴] 오브젝트 풀 패턴(Object Pool Pattern) 본문

Unity/디자인 패턴

[게임 디자인 패턴] 오브젝트 풀 패턴(Object Pool Pattern)

데브다이노 2026. 7. 15. 13:50

오브젝트 풀 패턴(Object Pool Pattern)

게임을 개발하다 보면 총알, 적 캐릭터, 파티클 이펙트처럼 동일한 오브젝트를 짧은 시간 동안 수없이 생성하고 파괴해야 할 때가 있습니다.

 

필요할 때마다 InstantiateDestroy 함수를 호출하여 구현할 수는 있지만, 객체의 생성과 파괴가 빈번해질수록 메모리에 부하가 걸려 게임에 렉(프레임 드랍)이 발생하게 됩니다.

 

이런 성능 저하 문제에서 벗어나, 미리 만들어둔 오브젝트를 재활용하여 성능을 끌어올려주는 오브젝트 풀(Object Pool) 패턴에 대해 알아보겠습니다. 특히 유니티에서 기본적으로 제공하는 내장 API를 활용해 아주 쉽고 안전하게 구현하는 방법을 소개합니다.

 

오브젝트 풀 패턴(Object Pool Pattern)이란?

오브젝트 풀 패턴은 필요한 객체를 미리 여러 개 생성하여 창고(Pool)에 보관해 두고, 필요할 때마다 꺼내 쓰며, 사용이 끝나면 파괴하는 대신 다시 창고로 반납하여 재사용하는 생성 디자인 패턴입니다. 객체 생성 및 메모리 해제에 드는 막대한 비용을 최소화하는 게임 최적화의 핵심 기법입니다.

 

유니티 내장 API(UnityEngine.Pool)의 장점
과거에는 개발자들이 큐나 리스트를 이용해 직접 풀 매니저를 구현해야 했지만, 이제는 유니티 내장 API를 사용하는 것이 표준입니다.

 

구현 예제

내장 API인 ObjectPool<T>을 사용하기 위해서는 다음 4가지 핵심 동작만 정의해 주면 됩니다.

  1. Create: 풀이 비어있을 때 새로운 객체를 어떻게 생성할 것인가?
  2. OnGet: 풀에서 객체를 꺼내올 때 어떤 처리를 할 것인가?
  3. OnRelease: 풀로 객체를 반납할 때 어떤 처리를 할 것인가?
  4. OnDestroy: 풀의 최대 용량을 초과해 반납될 때 객체를 어떻게 완전히 파괴할 것인가?

 

1. 풀링 대상(총알) 스크립트 작성

먼저 총알 객체입니다. 총알은 수명이 다하면 스스로를 파괴하는 대신, 자신을 관리하는 풀로 돌아가야 합니다.

using UnityEngine;
using UnityEngine.Pool;

public class Bullet : MonoBehaviour
{
    // 자신을 관리하는 내장 오브젝트 풀의 참조
    private IObjectPool<Bullet> managedPool;

    // 풀을 설정해주는 초기화 메서드
    public void SetPool(IObjectPool<Bullet> pool)
    {
        managedPool = pool;
    }

    void OnEnable()
    {
        // 활성화되면 2초 뒤에 스스로 풀로 반납되도록 설정합니다.
        Invoke("DisableAndReturn", 2f);
    }

    private void DisableAndReturn()
    {
        // Destroy(gameObject) 대신 풀의 Release 메서드를 호출합니다.
        managedPool?.Release(this);
    }

    void OnDisable()
    {
        CancelInvoke(); // 비활성화 시 대기 중인 반납 로직 취소
    }
}

 

2. 무기 및 풀 관리자(Client) 스크립트 작성

무기 스크립트 안에서 ObjectPool을 직접 생성하고 규칙을 정의합니다.

using UnityEngine;
using UnityEngine.Pool;

public class Weapon : MonoBehaviour
{
    public Bullet bulletPrefab; // 발사할 총알 프리팹
    public Transform firePoint; // 발사 위치

    // 유니티 내장 오브젝트 풀
    private IObjectPool<Bullet> bulletPool;

    void Awake()
    {
        // 오브젝트 풀 초기화 (4가지 핵심 동작 정의)
        bulletPool = new ObjectPool<Bullet>(
            createFunc: CreateBullet,         // 1. 생성 로직
            actionOnGet: OnGetBullet,         // 2. 대여 로직
            actionOnRelease: OnReleaseBullet, // 3. 반납 로직
            actionOnDestroy: OnDestroyBullet, // 4. 파괴 로직
            collectionCheck: true,            // 중복 반납 방지 검사 여부
            defaultCapacity: 10,              // 기본 생성 개수
            maxSize: 20                       // 최대 보관 개수 (넘치면 파괴)
        );
    }

    void Update()
    {
        // 마우스 왼쪽 버튼을 누르면 발사
        if (Input.GetMouseButtonDown(0))
        {
            Fire();
        }
    }

    void Fire()
    {
        // Instantiate 대신 풀에서 총알을 꺼내옵니다.
        Bullet bullet = bulletPool.Get();

        // 위치와 회전값 설정
        bullet.transform.position = firePoint.position;
        bullet.transform.rotation = firePoint.rotation;
    }

    // --- 풀링 규칙(Delegates) 정의 부분 ---

    private Bullet CreateBullet()
    {
        // 프리팹을 복제하고, 총알에게 풀의 참조를 넘겨줍니다.
        Bullet bullet = Instantiate(bulletPrefab);
        bullet.SetPool(bulletPool);
        return bullet;
    }

    private void OnGetBullet(Bullet bullet)
    {
        bullet.gameObject.SetActive(true); // 꺼낼 때 활성화
    }

    private void OnReleaseBullet(Bullet bullet)
    {
        bullet.gameObject.SetActive(false); // 반납할 때 비활성화
    }

    private void OnDestroyBullet(Bullet bullet)
    {
        Destroy(bullet.gameObject); // 풀이 꽉 찼을 때는 진짜로 파괴
    }
}

 

무기 클래스에서 직접 Instantiate를 호출하지 않고, bulletPool.Get()을 통해 아주 깔끔하게 객체를 가져옵니다. 반납 역시 총알이 자신의 역할을 다하면 managedPool.Release(this)를 호출해 스스로 창고로 돌아갑니다.


과거처럼 직접 자료구조를 만들고 예외 처리를 하던 번거로움 없이, UnityEngine.Pool을 활용하면 이처럼 훨씬 직관적이고 안전하게 풀링 시스템을 구축할 수 있습니다.


총알, 파티클 이펙트, 데미지 텍스트 팝업 등 짧은 시간에 대량으로 생성되고 사라지는 모든 오브젝트에 널리 쓰이는 기법입니다.