무한성이라고 불리던 스테이지를 일찍 탈출한 6명이서 사이드 프로젝트를 진행하기로 했다.
순전히 기능 공부를 위함이고 출시계획같은건 당연하게도 없다.
주제는 3D 뱀파이어 서바이벌이다.
개발 진행상황은 레포지토리 참조
MVP 스팩을 크게 6가지로 나눴다.
스킬 / 캐릭터 / 적 / 시스템(웨이브, 스포너) / 아이템 / UI
나는 그 중 시스템(웨이브, 스포너)를 담당하게 됐다.
기존 언리얼에서 스포너로 액터 생성 시 아래와 같은 형태를 취한다.
- 필요할 때 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이다.
프로젝트 같이하는 조에서 라이브러리를 추천받아서 사용해볼까 했으나
직접 구현을 한번 해보는게 좋을 것 같아서 구현하게됐다.
대충 요런 느낌인데 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);
}