유니티

[유니티] 디자인 패턴 : 오브젝트 풀링 (Object Pooling)

디버그러 2025. 2. 15. 11:46

 

오브젝트 풀링

 

게임에서 오브젝트를 사용할 때마다 새로 생성(instantiate)하고,

삭제(Destroy)하면 성능 저하가 발생한다.

이를 해결하는 방법 중 하나가 오브젝트 풀링이다.

 

오브젝트 풀링은 오브젝트를 필요할 때마다 생성하는 것이 아니라,

미리 생성해 두고 필요할 때 가져와 사용하는 방식이다.

사용이 끝난 오브젝트는 삭제하지 않고 다시 풀로 반환하여 재사용할 수 있다.

 

이 패턴은 게임 플레이 전에 특정 순간에 필요한 모든 오브젝트를 미리 인스턴스화해야 한다.

풀은 로딩 화면과 같이 플레이어가 끊김 현상을 알아차리지 못할 적절한 시기에 활성화한다.

 

 

 

원리

 

초기화 단계

  • 게임이 시작될 때 미리 여러 개의 오브젝트를 생성해서 풀에 저장

 

사용 단계

  • 오브젝트가 필요할 때 새로 생성하는 대신 풀에서 하나를 꺼내 사용

 

반환 단계

  • 사용이 끝난 오브젝트는 삭제하지 않고 풀로 되돌려서 나중에 다시 사용

 

 

 

장점

 

1. 성능 최적화

  • 새 오브젝트를 생성할 때 CPU가 메모리를 할당하고 해제하는 작업을 줄일 수 있어 CPU 부담이 적다.

 

2. 메모리 관리 최적화

  • 오브젝트를 반복해서 생성하고 삭제할 때 발생하는 가비지 컬렉션(GC)을 최소화하여 게임이 부드럽게 실행

 

3. 프레임 저하 방지

  • 게임 도중 많은 오브젝트 생성 (ex. 총알)으로 인한 CPU, 메모리 부담을 줄여 프레임 저하 방지

 

 

 

사용 용도

 

▶ 빠르게 생성 또는 삭제되는 오브젝트 (ex. 총알, 이펙트, 몬스터)

▶ 게임 내에서 반복적으로 사용되는 오브젝트 (ex. UI 요소, NPC, 파편 효과)

 

using System.Collections.Generic;
using UnityEngine;

public class ObjectPool : SingleTon<ObjectPool>
{
    private Dictionary<int, Queue<GameObject>> pooledObjects;
    private Dictionary<GameObject, int> instancePrefabKey;

    protected override void Awake()
    {
        base.Awake();
        pooledObjects = new Dictionary<int, Queue<GameObject>>();
        instancePrefabKey = new Dictionary<GameObject, int>();
    }

    /// <summary>
    /// 오브젝트 풀을 초기화하여 미리 생성
    /// </summary>
    public void InitializeObject(GameObject prefab, Transform poolContainer, int amount)
    {
        int key = prefab.GetInstanceID();

        if (!pooledObjects.ContainsKey(key))
        {
            pooledObjects[key] = new Queue<GameObject>();
        }

        for (int i = 0; i < amount; i++)
        {
            GameObject obj = Instantiate(prefab, poolContainer);
            obj.SetActive(false);
            pooledObjects[key].Enqueue(obj);
            instancePrefabKey[obj] = key;
        }
    }

    /// <summary>
    /// 특정 오브젝트를 풀에서 가져오기
    /// </summary>
    public GameObject GetFromPool(GameObject prefab, Vector3 position)
    {
        int key = prefab.GetInstanceID();

        if (pooledObjects.ContainsKey(key) && pooledObjects[key].Count > 0)
        {
            GameObject obj = pooledObjects[key].Dequeue();
            obj.SetActive(true);
            obj.transform.position = position;
            return obj;
        }
        else
        {
            Debug.Log($"풀이 비어 있어 새로 생성합니다.");

            // 풀이 비어있으면 새로운 오브젝트 생성
            GameObject newFruit = Instantiate(prefab, transform);
            newFruit.SetActive(true);
            newFruit.transform.position = position;
            instancePrefabKey[newFruit] = key;
            return newFruit;
        }
    }

    /// <summary>
    /// 사용이 끝난 오브젝트를 다시 풀에 반환
    /// </summary>
    public void ReturnToPool(GameObject prefab)
    {
        if (!instancePrefabKey.ContainsKey(prefab))
        {
            Debug.Log($"[Object Pool] {prefab.name} 반환할 수 없습니다.");
            return;
        }

        int key = instancePrefabKey[prefab];

        prefab.SetActive(false);

        pooledObjects[key].Enqueue(prefab);
    }
}