본문 바로가기
Unreal5/기본개념

[최적화] 오브젝트 풀링

by 위니밍 2025. 8. 2.
반응형

 

간략하게, 총알을 예시로

총알을 미리 Spawn 하여 배열(풀)에 등록하여

쏠땐 set location, visible, collison... 세팅하고

필요없어지면 반대로 set hidden... 등등 해주는것.

 

=> 배열에서 총갯수를 조정가능하고, 재사용하기때문에 새로운 총알을 Spawn 할 필요가 없음.

[언리얼 엔진 최적화] 오브젝트 풀링 (Object Pooling)

총알이 빗발치는 슈팅 게임, 화려한 이펙트가 터지는 액션 게임을 만들다 보면 피할 수 없는 고민이 있습니다. 바로 성능 저하와 끊김(Stutter) 현상입니다. 수많은 오브젝트를 실시간으로 생성(Spawn)하고 파괴(Destroy)하는 과정은 엔진에 상당한 부하를 주게 됩니다.

오늘은 이러한 문제를 해결하고 게임의 퍼포먼스를 한 단계 끌어올릴 수 있는 핵심 최적화 기법, **오브젝트 풀링(Object Pooling)**에 대해 A to Z로 자세히 알아보겠습니다.


1. 오브젝트 풀링이란 무엇이며, 왜 필수적인가요?

오브젝트 풀링이란, 간단히 말해 **'오브젝트 재활용 시스템'**입니다.

게임 플레이 중에 필요할 때마다 오브젝트를 새로 생성하고 파괴하는 대신, 게임 시작 시점에 필요한 만큼의 오브젝트를 미리 만들어 '풀(Pool)'이라는 거대한 저장소에 보관합니다. 그리고 실제 게임에서는 이 풀에서 오브젝트를 '대여'해서 사용하고, 사용이 끝나면 파괴하지 않고 다시 풀에 '반납'하여 재활용하는 방식입니다.

오브젝트 풀링을 사용해야 하는 이유

  • 성능 부하 감소: Spawn과 Destroy는 매우 비용이 높은 작업입니다. 특히 Destroy는 가비지 컬렉션(Garbage Collection)을 유발하여 게임의 순간적인 멈춤 현상, 즉 '스터터'의 주범이 됩니다. 오브젝트 풀링은 이 두 가지 작업을 최소화하여 게임을 매우 부드럽게 만듭니다.
  • 안정적인 메모리 관리: 게임 시작 시 필요한 메모리를 미리 할당해두기 때문에, 플레이 도중 메모리가 급격하게 변동하는 것을 막아줍니다. 이는 예기치 못한 크래시를 방지하고 안정성을 높여줍니다.

C++를 사용하면 월드 서브시스템, 메모리 예약 등 더 효율적인 구현이 가능하지만, 블루프린트만으로도 매우 강력하고 효과적인 오브젝트 풀링 시스템을 구축할 수 있습니다. 이 가이드에서는 블루프린트 기반의 구현 방법을 중심으로 설명합니다.


2. 블루프린트로 오브젝트 풀링 시스템 구축하기 [단계별 가이드]

이제 본격적으로 총알(Bullet)을 재활용하는 오브젝트 풀 시스템을 만들어 보겠습니다.

1단계: 오브젝트 풀 매니저(Object Pool Manager) 생성

매니저는 우리의 오브젝트 풀을 총괄하는 컨트롤 타워입니다.

  1. BP_ObjectPoolManager라는 이름으로 새로운 액터 블루프린트를 생성합니다.
  2. Begin Play 이벤트에서, 우리가 사용할 만큼의 총알 액터를 미리 생성하는 로직을 구현합니다. 예를 들어, 500개의 총알이 필요하다면 For Loop를 사용하여 500번 반복하면서 총알 액터를 스폰합니다.
  3. 생성된 총알 액터들은 Bullet Pool이라는 이름의 액터 배열(Array) 변수에 차곡차곡 저장합니다. 이 배열이 바로 우리의 '오브젝트 풀'이 됩니다.
  4. 초기 로딩 시 약간의 지연이 발생할 수 있으나, 이는 로딩 화면 등에서 처리하면 되므로 실제 게임 플레이에는 영향을 주지 않습니다.

2단계: 재사용될 기본 오브젝트(Base Bullet) 생성

실제 게임 월드에서 총알의 역할을 수행할 기본 액터입니다.

  1. BP_BaseBullet라는 이름으로 새로운 액터 블루프린트를 생성합니다. 이 액터는 발사체 컴포넌트(Projectile Movement Component)와 외형을 위한 스태틱 메시 컴포넌트(Static Mesh Component) 등을 가집니다.
  2. 이 액터의 핵심은 **'활성화(Activate)'**와 '비활성화(Deactivate)' 상태를 갖는 것입니다. 이를 위한 커스텀 이벤트를 각각 만들어 줍니다.

3단계: 활성화(Activate) / 비활성화(Deactivate) 로직 구현

  • 비활성화 (Deactivate) 로직:
    • 이 함수는 총알이 풀에 보관될 때의 상태를 정의합니다.
    • Set Actor Hidden In Game 노드를 사용하여 액터를 투명하게 만듭니다.
    • Set Actor Enable Collision 노드를 사용하여 모든 충돌 기능을 끕니다.
    • 발사체 컴포넌트의 Gravity Scale을 0으로, Velocity를 (0,0,0)으로 만들어 움직임을 완전히 멈춥니다.
    • 가장 중요: 비활성화 로직 마지막에는 BP_ObjectPoolManager에게 "나 이제 다시 쉬어도 돼"라고 알려주고, 매니저는 이 총알을 다시 Bullet Pool 배열에 추가하는 로직이 필요합니다.
  • 활성화 (Activate) 로직:
    • 이 함수는 풀에서 총알을 꺼내 사용할 때의 상태를 정의합니다.
    • Set Actor Hidden In Game으로 다시 보이게 만듭니다.
    • Set Actor Enable Collision으로 충돌 기능을 켭니다.
    • 총알을 발사할 위치와 방향을 Set Actor Location and Rotation으로 설정합니다.
    • 발사체의 속도, 데미지, 외형 등 필요한 모든 초기값을 설정합니다.

4단계: 구조체(Struct)를 활용한 깔끔한 데이터 전달

활성화 시 필요한 데이터(위치, 방향, 속도, 데미지, 메시 등)는 매우 많습니다. 이를 깔끔하게 전달하기 위해 **구조체(Structure)**를 사용합니다.

  1. STR_BulletInfo라는 이름의 구조체를 새로 만듭니다.
  2. 이 구조체 안에 총알 활성화에 필요한 모든 변수(Transform, Damage, Speed, Static Mesh 등)를 추가합니다.
  3. 이제 Activate 함수는 이 구조체 변수 하나만 입력으로 받으면 됩니다. 이렇게 하면 코드가 훨씬 깔끔해지고, 나중에 데이터 테이블과 연동하여 수십 종류의 총알을 손쉽게 관리하는 기반이 됩니다.

5단계: 오브젝트 회수 및 재사용 로직

이제 사용이 끝난 총알을 자동으로 풀에 반납하는 시스템을 만듭니다.

  1. BP_BaseBullet 액터에서 On Component Hit 또는 On Actor Begin Overlap 이벤트를 사용합니다.
  2. 총알이 다른 오브젝트와 부딪혔을 때, 스스로 Deactivate 함수를 호출하도록 연결합니다.
  3. Deactivate 함수 내부 로직에 따라, 이 총알은 투명해지고 움직임을 멈춘 뒤 Object Pool Manager의 Bullet Pool 배열로 돌아가게 됩니다.

이제 모든 준비가 끝났습니다. 캐릭터가 총을 쏠 때 Spawn Actor를 호출하는 대신, BP_ObjectPoolManager에서 비활성화된 총알을 하나 가져와 Activate 함수를 호출하기만 하면 됩니다.


3. 주요 고려사항 및 고급 팁

  • 월드 킬 Z (World Kill Z): 맵 밖으로 떨어진 오브젝트는 엔진이 자동으로 파괴합니다. 이를 방지하려면 Deactivate 시 중력을 0으로 설정하여 추락을 막는 것이 중요합니다.
  • 적절한 풀 크기 설정: 동시에 화면에 존재해야 하는 오브젝트의 최대 개수를 신중하게 예측하여 풀의 크기를 정해야 합니다. 풀이 너무 작으면 필요할 때 쓸 총알이 없는 'Pool Empty' 상황이 발생할 수 있습니다.
  • 다양한 오브젝트 타입 관리: 여러 종류의 총알이나 이펙트가 필요하다면, 모든 로직을 담은 하나의 기본 클래스를 상속하여 사용하거나, 타입별로 여러 개의 풀을 만드는 전략을 사용할 수 있습니다.

오브젝트 풀링은 처음에는 조금 복잡해 보일 수 있지만, 한번 시스템을 구축해두면 어떤 프로젝트에서든 재사용 가능합니다.

 

https://www.youtube.com/watch?v=lpF6HEqr9XI

반응형