TIL: Unreal C++ 사이드프로젝트 29일차

박춘팔·4일 전

언리얼 TIL

목록 보기
29/30

누적 학습 시간 : 296시간 34분

📅 2026-05-13

무한성이라고 불리던 스테이지를 일찍 탈출한 6명이서 사이드 프로젝트를 진행하기로 했다.
순전히 기능 공부를 위함이고 출시계획같은건 당연하게도 없다.

프로젝트 진행기간 : 26.05.11 ~ 26.05.21

주제는 3D 뱀파이어 서바이벌이다.
개발 진행상황은 레포지토리 참조

MVP 스팩을 크게 6가지로 나눴다.
스킬 / 캐릭터 / 적 / 시스템(웨이브, 스포너) / 아이템 / UI
나는 그 중 시스템(웨이브, 스포너)를 담당하게 됐다.

Enemy Spawner Pooling

풀링이란?

기존 언리얼에서 스포너로 액터 생성 시 아래와 같은 형태를 취한다.

- 필요할 때 Spawn
- 사용 끝나면 Destroy
- 필요하면 다시 Spawn
- 사용 끝나면 다시 Destroy

풀링 적용 시

- 게임 시작 시 미리 Spawn
- 비활성화 상태로 보관
- 필요할 때 활성화
- 사용 끝나면 Destroy하지 않고 다시 비활성화
- 다음에 재사용

풀링(Object Pooling)은 오브젝트를 필요할 때마다
계속 생성 → 사용 → 제거하지 않고, 미리 여러 개 만들어 둔 뒤 재사용하는 기법

왜 쓰는가?

언리얼에서 SpawnActor는 단순히 메모리에 객체 생성하는 수준이 아니고 생각보다 복잡하다.

- Actor 생성
- Component 생성
- Transform 설정
- Collision 등록
- BeginPlay 호출
- Tick 등록
- Replication 준비
- Physics 등록
- Rendering 등록

반대의 경우로 Destory()시에도 바로 사라지는게 아니라 엔진 라이프 사이클에 따라서 제거 예약, 참조 정리, GC 처리 등이 얽힌다.

그래서 아래와 같은 상황에서는 풀링을 사용하는게 유리하다.

- 총알
- 투사체
- 피격 이펙트
- 폭발 이펙트
- 데미지 숫자UI
- 드랍 아이템
- 몬스터 스폰/웨이브
- 특정 잔해 조각들
- 발자국, 탄피, 흔적들

조심해야하는 포인트

풀링에서 가장 많이 터지는 버그는 이전 상태가 남아있는 것이다.
반환시 반드시 초기화 해야하는 것들이 있는데 총알로 예를 들 경우

- Location
- Rotation
- Velocity
- Direction
- LifeCycle
- Coliision state
- Tick
- Visibility
- Particle State
- Sound State
- Damage Owner
- Instigator
- Target
- Overlap List
- Timer
- Delegate Binding

위 항목들을 반드시 초기화 해줘야한다.
언리얼에서 특히 버그가 많이 발생하는 부분은
Timer, Delegate, Collision, Overlap, Particle이다.

멀티플레이에서 풀링 시 HOST 기준

실제 구현 고민

프로젝트 같이하는 조에서 라이브러리를 추천받아서 사용해볼까 했으나
직접 구현을 한번 해보는게 좋을 것 같아서 구현하게됐다.

C++로 구현구상

대충 요런 느낌인데 Unreal에서는 vector말고 TArray 써야되니까 Unreal버전으로도 작성해보자.

// poolingManager
std::vector<std::unique_ptr<AEnemyBase>> DeactiveEnemyPool;
std::vector<std::unique_ptr<AEnemyBase>> IdleEnemyPool;


int32 EnemyPoolSize = 200;

for(int32 i = 0; i < EnemyPoolSize; i++)
{
	// EnemyClass는 실제 BP클래스
	AEnemyBase* Enemy = GetWorld()->SpawnActor<AEnemyBase>(EnemyClass);
    DeactiveEnemyPool.Add(Enemy);
}


// Enemy Spawner
void AEnemySpawner::SpawnEnemy() 
{
	AEnemyBase* Enemy = nullptr;
    
	// 기존 SpawnActor 대신 Pool 
    Enemy->ActiveFromPool(EnemyData);
}

void AEnemySpawner::ReturnEnemyToPool(AEnemyBase* Enemy)
{
	// 기존 Destory() 대신 Pool
    SpawnedEnemyList.Remove(Enemy);
    Enemy->DeactiveToPool();
    IdleEnemyPool.Add(Enemy);
}
profile
이것 저것 다해보는 삶

0개의 댓글