GameDevelop/Unity팀프로젝트

[TeamProject2_2025.05.14] 오브젝트 풀링/적 시스템 직접 구현(구현편)

도도돋치 2025. 5. 14. 00:08
Contents 접기
728x90

구현 목표

설계편에서 정의한 내용을 바탕으로,

Unity에서 오브젝트 풀링 시스템과 적 생성 시스템을 직접 구현

 

  • 효율적인 리소스 관리: 오브젝트 풀링
  • 적 몬스터 스폰: 다양한 적 패턴 관리
  • IPoolable 인터페이스: 오브젝트 상태 관리
  • PoolManager 싱글톤: 풀 통합 관리

 

 

✅ IPoolable 인터페이스

public interface IPoolable
{
    void OnSpawned();    // 오브젝트가 풀에서 나올 때 호출
    void OnDespawned();  // 오브젝트가 풀로 돌아갈 때 호출
}
오브젝트 풀에 들어가는 모든 오브젝트는
OnSpawned(), OnDespawned() 메서드를 구현해
초기화와 정리 작업을 할 수 있다.

 

 

✅ ObjectPool 클래스

Object Pool 전체 흐름
[초기화]
1. ObjectPooL<T> 생성 시 프리과 개수 받음
2. Instantiate()로 T 오브젝트들을 생성해서 Queue에 보관
3. 모두 SetActive(false) 상태로 비활성화

[꺼내기]
4. Get() 호출 시 -> Queue에서 꺼내고 SetActive(true)
5. OnSpawned() 호출 (초기화 콜백)

[반환하기]
6. Return() 호출 시 - SetActive(false)
7. OnDespawned () 호출
8. 다시 Queue에 넣음
Object Pool 구현
public class ObjectPool<T> where T : MonoBehaviour, IPoolable
{
    private Queue<T> pool = new Queue<T>();
    private T _prefab;
    private Transform _parent;

    public ObjectPool(T prefab, int size, Transform parent = null)
    {
        _prefab = prefab;
        _parent = parent;

        for (int i = 0; i < size; i++)
        {
            T obj = Object.Instantiate(_prefab, _parent);
            obj.gameObject.SetActive(false);
            pool.Enqueue(obj);
        }
    }

    public T Get()
    {
        if (pool.Count == 0)
        {
            T objTemp = Object.Instantiate(_prefab, _parent);
            objTemp.gameObject.SetActive(true);
            return objTemp;
        }
        T obj = pool.Dequeue();
        obj.gameObject.SetActive(true);
        obj.OnSpawned();
        return obj;
    }

    public void Return(T obj)
    {
        obj.OnDespawned();
        obj.gameObject.SetActive(false);
        pool.Enqueue(obj);
    }
}

 

  • 제네릭 타입 T를 사용해 모든 풀링 오브젝트에 대응할 수 있다.
  • 풀에 오브젝트가 부족하면 동적 생성도 지원한다.

 

✅ PoolManager 클래스 (싱글톤)

PoolManager 전체 흐름
[초기화]
1. PooLManager는 싱글톤으로 존재하며 게임 시작 시 생성됨
2. CreatePooLcT>(key, prefab, count, parent) 호출로 풀 생성
3. 각 key마다 objectPooL<T>를 생성해서 Dictionary에 저장

[꺼내기]
4. Get<T>(key) 호출 시 -> 해당 key의 0bjectPooL<T>에서 Get()
5. 오브젝트를 꺼내고 SetActive(true)
6. OnSpawned() 호출 (초기화 콜백)

[반환하기]
7. Return<T>(key, obj) 호출 시 -> SetActive(false)
8. OnDespawned() 호출
9. 다시 해당 ObjectPooL<T>에 obj를 Enqueue
PoolManager 구현
public class PoolManager : Singleton<PoolManager>
{
    private Dictionary<string, object> pools = new();

    protected override void Awake()
    {
        base.Awake();
        
        // 적 오브젝트 풀 생성 예시
        CreatePool<Boss>("Boss", _bossPrefab, 10, _bossParent);
        CreatePool<MoveEnemy>("MoveEnemy", _movePrefab, 20, _enemyParent);
    }

    public void CreatePool<T>(string key, T prefab, int count, Transform parent) where T : MonoBehaviour, IPoolable
    {
        var pool = new ObjectPool<T>(prefab, count, parent);
        pools.Add(key, pool);
    }

    public T Get<T>(string key) where T : MonoBehaviour, IPoolable
    {
        return ((ObjectPool<T>)pools[key]).Get();
    }

    public void Return<T>(string key, T obj) where T : MonoBehaviour, IPoolable
    {
        ((ObjectPool<T>)pools[key]).Return(obj);
    }
}
  • 풀 생성: CreatePool()
  • 오브젝트 꺼내오기: Get()
  • 오브젝트 반환: Return()
모든 풀은 Dictionary로 관리되어,
다양한 적/탄환 종류를 쉽게 추가하고 관리할 수 있다.

 

 

적 스폰 로직

// 몬스터 생성
MoveEnemy enemy = PoolManager.Instance.Get<MoveEnemy>("MoveEnemy");
enemy.transform.position = spawnPoint.position;

// 몬스터 제거
PoolManager.Instance.Return("MoveEnemy", enemy);

 

필요한 순간 풀에서 꺼내서 위치를 잡고, 다 쓰면 풀에 다시 반납하는 방식

 

 

코드 흐름

게임 시작
↓
PoolManager가 오브젝트 풀 생성
↓
필요 시 ObjectPool에서 Get() 호출
↓
사용 후 Return() 호출로 재사용
↓
Room 입장 시 적 스폰, 체력 관리, FSM 패턴 적용

 

 

📖 결과

항목 결과
오브젝트 생성/제거 비용 감소
메모리 사용 최적화
게임 퍼포먼스 개선 (프레임 드랍 방지)
스폰 시스템 방마다 몬스터 랜덤 등장

 

 

 

💡 직접 해보며 느낀 점

  • 풀 크기(초기 생성 수)를 상황에 맞게 조정하는 것도 중요했다.
  • IPoolable 패턴 덕분에 다양한 오브젝트를 깔끔하게 관리할 수 있었다.
  • 오브젝트 풀에 오브젝트가 부족한 경우를 처음에 고려하지 않았는데 이후 그러한 경우를 알게되어 동적 생성을 추가하였다. 구현시에는 다양한 예외상황을 생각하고 구현해야 하는 것 같다.
  • 특히, 로그라이크 같은 적/탄환 스폰이 많은 게임에서는 오브젝트 풀링은 선택이 아니라 필수라고 느꼈다.
728x90