Unity - Object Pool Pattern : 오브젝트 풀

2025. 11. 12. 17:02·Unity

Object Pool Pattern : 오브젝트 풀

오브젝트 풀은 쉽게 말해 한 번 생성한 객체를 재사용하는 설계 패턴이다.

 

유니티의 Object들은 기본적으로 Transform 을 포함한 여러 컴포넌트들을 보유하고 있어 생성시에 약간의 부하가 있다.

또한 오브젝트가 파괴 될 때도 GC(Garbage Collector) 에 의한 부하가 발생하여, 많은 수의 오브젝트가 생성 및 파괴가 발생하면서 성능 저하를 유발할 수 있다.

 

이것을 방지할 수 있는 것이 바로 오브젝트 풀이다.

생성 / 파괴가 아닌 활성 / 비활성화 과정을 통해 한번 생성한 오브젝트를 재활용하여 이러한 부하를 줄여주는 기법이다.

 

동작 원리는 매우 간단하다.

  • 초기화 : 풀을 초기화 혹은 초기 풀 사이즈에 맞게 미리 생성
  • 할당 : 풀에서 꺼내 사용
  • 반납 : 사용 후 비활성화 처리 하며 풀에 다시 넣음

 

이 과정을 통해 GC 발생을 감소시키고 CPU 부하를 완화하여 성능 안정화를 얻을 수 있어 최적화에 도움이 된다.

 

또한 메모리적 측면에서도 오브젝트 풀은 중요한 역할을 한다.

일반적으로 오브젝트의 생성 / 파괴가 자주 발생할 경우, 메모리의 사용 패턴이 불규칙해지면서 메모리 단편화가 발생할 수 있기 때문이다.

오브젝트 풀을 통해 방지할 수 있는 "메모리 단편화" 와 "메모리 누수" 에 대해서도 간략히 알아보자.

 

메모리 단편화 (Memory Fragmentation)

메모리 단편화란, 메모리에 여유가 있음에도 불구하고 큰 메모리가 들어갈 수 있는 연속적인 공간을 확보할 하지 못하게 되는 비효율적인 상태를 말한다.

이는 메모리의 할당/해제가 자주 발생하면서 공간이 조각나면서 발생하게 된다.

 

메모리 단편화는 또 두가지로 나눌 수 있다.

  • 외부 단편화 : 여러 작은 빈 공간이 여기저기 흩어져 있어 큰 메모리를 할당할 수 없음
  • 내부 단편화 : 할당받은 블록 내부에서 실제로 사용하지 않는 공간이 남아 낭비되고 있음

 

메모리 누수 (Memory Leak)

더 이상 사용하지 않는 메모리를 해제하지 않아 프로그램에 계속 메모리를 차지한 채로 남는 현상이다.

오브젝트를 더이상 쓰지 않지만 Destroy나 null 처리가 되지 않으면 어딘가에서 참조가 계속 남아 Heap에 남기 때문에 GC가 제거하지 못하므로써 발생하게 된다.

결과적으로 메모리 사용량이 계속 증가하다가 OutOfMemoryException 또는 시스템 부하에 의한 성능 저하를 유발하게 된다.

이는 장시간 실행되는 게임이나 서버에는 치명적인 문제가 될 수 있다.

 


Object Pool 의 대표적인 사용 예시

그럼 이런 오브젝트 풀은 어디에 사용하면 좋을까?

우선 게임에서 당장 생각나는 몇가지 경우의 수가 있다.

  • 많은 수의 탄환
  • 많은 수의 유닛
  • 파티클 등의 이펙트
  • 많은 수의 UI (체력바나 데미지 표현하는 플로팅 텍스트 등)
  • 맵 오브젝트나 리소스

이 외에도 생성/파괴가 잦거나 많은 수의 오브젝트를 관리해야하는 상황에서 고려할만 하다.

 

 


Object Pool 구현

오브젝트 풀에 대해 알아보았으니 배열, List, Queue, Stack 등을 이용해 직접 구현해보도록 하자.

 

우선 List 기반으로 쉽게 구현을 고려해볼 수 있을 것이다.

using System.Collections.Generic;
using UnityEngine;

public class BulletPool : MonoBehaviour
{
    [Header("Fire Settings")]
    [SerializeField] private GameObject _bulletPrefab;
    [SerializeField] private int _initPoolSize = 10;
    [SerializeField] private float _bulletSpeed = 50f;

    private List<GameObject> _bulletPool = new List<GameObject>();

    void Awake()
    {
        // 초기 풀 생성
        for (int i = 0; i < _initPoolSize; i++)
        {
            GameObject newBullet = Instantiate(_bulletPrefab);
            newBullet.SetActive(false);
            _bulletPool.Add(newBullet);
        }
    }

    private GameObject GetInactiveBullet()
    {
        // 비활성화된 오브젝트를 순차적으로 탐색 (O(n))
        foreach (GameObject bullet in _bulletPool)
        {
            if (!bullet.activeInHierarchy)
            {
                return bullet;
            }
        }

        // 전부 사용 중이라면 새로 생성
        GameObject newBullet = Instantiate(_bulletPrefab);
        newBullet.SetActive(false);
        _bulletPool.Add(newBullet);
        return newBullet;
    }

    private void OnShoot()
    {
        GameObject bullet = GetInactiveBullet(); // 그 외 발사 관련 로직
    }
}

 

 

막상 코드로는 크게 어려운 부분은 없다.

Awake에서 초기화를 거쳐 초기 풀 사이즈만큼 미리 탄환을 만든 뒤 비활성화 하고,

발사 시마다 비활성화된 총알을 풀에서 꺼내 사용한다.

이후 총알이 명중하는 등 사라져야할 때 비활성화 처리해주면 된다.

 

하지만 여기서 Queue를 이용하면 비활성화된 오브젝트를 탐색하는 부분을 뺄 수 있기 때문에 조금 더 최적화 할 수 있다.

 

using System.Collections.Generic;
using UnityEngine;

public class BulletPool : MonoBehaviour
{
    [Header("Fire Settings")]
    [SerializeField] private GameObject _bulletPrefab;
    [SerializeField] private int _initPoolSize = 10;
    [SerializeField] private float _bulletSpeed = 50f;

    private Queue<GameObject> _bulletPool = new Queue<GameObject>();

    void Awake()
    {
        for (int i = 0; i < _initPoolSize; i++)
        {
            CreateNewBullet();
        }
    }

    private GameObject CreateNewBullet()
    {
        GameObject newBullet = Instantiate(_bulletPrefab); // 그냥 생성
        newBullet.SetActive(false); // 만들자마자 비활성화

        _bulletPool.Enqueue(newBullet); // 큐에 추가
        return newBullet;
    }

    private GameObject GetInactiveBullet()
    {
        // 비활성화 된 탄환 탐색 필요 없이 디큐로 쉽게 처리
        if (_bulletPool.Count > 0)
        {
            return _bulletPool.Dequeue();
        }

        return null;
    }

    private void OnShoot()
    {
        GameObject bullet = GetInactiveBullet();

        if (bullet == null)
            bullet = CreateNewBullet(); // 그 외 발사 관련 로직들
    }
    
    public void ReturnBullet(GameObject bullet)
    {
        bullet.SetActive(false); // 비활성화 후
        _bulletPool.Enqueue(bullet); // 큐에 다시 추가
    }
}

 

훨씬 직관적이며 반복문을 제외해 시간 복잡도를 O(n) 에서 O(1)로 개선할 수 있다.

 

 

그리고 여기서 제네릭을 이용해 다양한 곳에서 풀을 사용할 수 있는 형태로도 구성할 수 있다.

using System.Collections.Generic;
using UnityEngine;

public class ObjectPool<T> where T : MonoBehaviour
{
    Queue<T> _pool;
    private T _prefab;

    public ObjectPool(T prefab, int initSize)
    {
        _prefab = prefab;
        _pool = new Queue<T>();

        for (int i = 0; i < initSize; i++)
        {
            T instance = Object.Instantiate(prefab);
            instance.gameObject.SetActive(false);
            _pool.Enqueue(instance);
        }
    }

    public T GetObject()
    {
        if (_pool.Count > 0)
        {
            T obj = _pool.Dequeue();
            obj.gameObject.SetActive(true);
            return obj;
        }

        T newObj = Object.Instantiate(_prefab);
        return newObj;
    }

    public void ReturnObject(T obj)
    {
        _pool.Enqueue(obj);
        obj.gameObject.SetActive(false);
    }
}

 

이 경우 풀은 확실하게 풀링 관련된 로직만 관리하며, 특정 오브젝트가 초기화 될 때의 로직을 잘 분리하여 구성할 필요가 있다.

 

 


Unity 내장 오브젝트 풀 : UnityEngine.Pool

무려 Unity에는 이러한 오브젝트 풀을 쉽게 사용할 수 있도록 제네릭 오브젝트 풀 클래스가 내장되어있다.

이 클래스는 방금 직접 만들어 본 Queue 기반의 풀과 비슷한 구조를 가지고 있지만,

콜백 기반 관리, 최대 용량 제한, 자동 컬렉션 검사 등의 추가 기능이 제공된다.

 

사용법도 간단하며 코드를 매우 단축시켜준다.

using UnityEngine;
using UnityEngine.Pool;

public class BulletPool : MonoBehaviour
{
    [SerializeField] private Bullet _bulletPrefab;
    private ObjectPool<Bullet> _pool;

    void Awake()
    {
        _pool = new ObjectPool<Bullet>(
            createFunc: () => Instantiate(_bulletPrefab),             // 생성 시
            actionOnGet: (b) => b.gameObject.SetActive(true),         // Get 시
            actionOnRelease: (b) => b.gameObject.SetActive(false),    // Release 시
            actionOnDestroy: (b) => Destroy(b.gameObject),            // Destroy 시
            collectionCheck: false,                                   // 중복 체크 여부
            defaultCapacity: 10,                                      // 초기 풀 크기
            maxSize: 100                                              // 최대 풀 크기
        );
    }

    public void Fire()
    {
        Bullet bullet = _pool.Get();      // 오브젝트 꺼내기
        bullet.Shoot(_pool);              // Bullet 내부에서 Release 호출
    }
}

 

 

주요 메서드

createFunc 풀에 여유가 없을 때 새 객체를 생성하는 함수
actionOnGet Get() 호출 시 수행할 초기화 작업 (활성화 등)
actionOnRelease Release() 호출 시 수행할 정리 작업 (비활성화 등)
actionOnDestroy 풀에서 완전히 제거될 때 실행되는 파괴 함수
collectionCheck 같은 객체를 중복으로 풀에 넣는 실수를 검사
defaultCapacity 초기 풀 크기
maxSize 풀의 최대 크기 제한 (넘으면 자동 파괴)
Get() 오브젝트를 풀에서 가져옴
Release(T obj) 사용이 끝난 오브젝트를 풀에 반납
Clear() 풀을 완전히 비움
CountActive / CountInactive 현재 활성/비활성 오브젝트 개수 확인

 

 

아직 유니티 내장 풀은 익숙하지 않아 조금 헷갈리지만 풀에서 필요한 기능은 거의다 갖추고 있기에 잘 사용하면 유용할 것으로 보인다.

또한 당연스럽겠지만 풀을 어디에 적용할지에 대한 고민도 상황에 따라 유연하게 적용할 수 있도록 하는 것이 중요할 것이다.

단순히 총알이니 풀로 때우자, 가 아닌 풀을 총알을 쏘는 각 오브젝트가 갖게할지, 공용 풀로 관리할지 등등 여러 상황이 있으니 기본 개념을 숙지하고 응용할 수 있도록 하자.

 

 

'Unity' 카테고리의 다른 글

길찾기 알고리즘 : 다익스트라(Dijkstra) 와 에이스타(A*)  (0) 2025.11.20
Unity - 데이터 저장 방식 : PlayerPrefs, JSON, CSV, ScriptableObject  (0) 2025.11.13
Unity - FSM : Finite State Machine - 유한 상태 머신  (0) 2025.11.11
Unity - 6000.2.10f1 - InputSystem 버그  (0) 2025.11.07
Unity 프로젝트 이슈 해결 - 범위내 적 타게팅 최적화  (0) 2025.11.04
'Unity' 카테고리의 다른 글
  • 길찾기 알고리즘 : 다익스트라(Dijkstra) 와 에이스타(A*)
  • Unity - 데이터 저장 방식 : PlayerPrefs, JSON, CSV, ScriptableObject
  • Unity - FSM : Finite State Machine - 유한 상태 머신
  • Unity - 6000.2.10f1 - InputSystem 버그
ellug
ellug
Web / App / Game Dev
  • ellug
    dev.ellug
    ellug
  • 전체
    오늘
    어제
    • 분류 전체보기 (95)
      • Javascript (React, Next) (1)
      • C# (66)
      • Graphics (1)
      • Unity (25)
      • 프로그래머스 (0)
        • Lv.0 (0)
        • Lv.1 (0)
        • Lv.2 (0)
        • Lv.3 (0)
      • AI Agent (0)
      • StableDiffusion (0)
      • ETC (1)
      • 잡담 (0)
      • 유머 (0)
  • 블로그 메뉴

    • 홈
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Queue
    네트워크
    추상 클래스
    inputsystem
    Network
    자료구조
    C#
    function
    큐
    터미널
    unity
    useState
    유니티
    struct
    stack
    최적화
    console
    알고리즘
    Abstract
    게임
    delegate
    list
    firebase
    class
    Photon
    interface
    클래스
    Design Pattern
    스택
    콘솔
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
ellug
Unity - Object Pool Pattern : 오브젝트 풀
상단으로

티스토리툴바