PART 3 · 강의 3/3

동적 스폰과 파괴

MassSpawner와 프로그래밍 방식으로 엔티티를 생성/파괴하는 방법을 학습합니다

01

MassSpawner Actor

레벨에 배치하여 엔티티를 자동 생성하는 스포너

AMassSpawner는 레벨에 배치하는 Actor로, 지정된 Config에 따라 엔티티를 대량 생성합니다. 가장 간편한 스폰 방법입니다.

MassSpawner 설정 // 레벨에 AMassSpawner 배치 후 디테일 패널: // // ┌─────────────────────────────────────────┐ // │ MassSpawner Details │ // │ │ // │ Entity Types: │ // │ [0] Entity Config: DA_Zombie │ // │ Spawn Count: 5000 │ // │ [1] Entity Config: DA_Civilian │ // │ Spawn Count: 3000 │ // │ │ // │ Spawn Data Generators: │ // │ [0] UMassEntityDistanceSpawnGenerator │ // │ SpreadRadius: 5000.0 │ // │ MinDistance: 100.0 │ // │ │ // │ Auto Spawn On Begin Play: true │ // └─────────────────────────────────────────┘
Spawn Data Generator

스폰 위치를 결정하는 Generator를 설정합니다. 기본 제공되는 UMassEntityDistanceSpawnGenerator는 반경 내에 균일하게 분산시킵니다. 커스텀 Generator를 만들면 도로 위, 빌딩 내부 등 원하는 패턴으로 배치할 수 있습니다.

02

코드에서 엔티티 생성

C++에서 프로그래밍 방식으로 엔티티를 동적 생성

C++ - 단일 엔티티 생성 void SpawnSingleEntity(UWorld* World, UMassEntityConfigAsset* Config, FVector Location) { FMassEntityManager& EM = UMassEntitySubsystem::GetEntityManager(World); // Config에서 Template 획득 const FMassEntityTemplate& Template = Config->GetOrCreateEntityTemplate(*World); // 엔티티 생성 FMassEntityHandle Entity = EM.CreateEntity(Template.GetArchetype()); // Template의 초기화 콜백 실행 Template.InitializeEntity(EM, Entity); // 위치 설정 FMassEntityView View(EM, Entity); FTransformFragment& Transform = View.GetFragmentData<FTransformFragment>(); Transform.Transform.SetLocation(Location); }
C++ - 대량 배치 생성 void SpawnBatch(UWorld* World, UMassEntityConfigAsset* Config, int32 Count) { FMassEntityManager& EM = UMassEntitySubsystem::GetEntityManager(World); const FMassEntityTemplate& Template = Config->GetOrCreateEntityTemplate(*World); // 배치 생성: 한 번에 다수의 엔티티 생성 TArray<FMassEntityHandle> Entities; EM.BatchCreateEntities( Template.GetArchetype(), Count, Entities); // 각 엔티티 초기화 for (int32 i = 0; i < Entities.Num(); ++i) { Template.InitializeEntity(EM, Entities[i]); // 위치를 원형으로 배치 FMassEntityView View(EM, Entities[i]); FTransformFragment& Transform = View.GetFragmentData<FTransformFragment>(); float Angle = (2.0f * PI * i) / Count; float Radius = 1000.0f; Transform.Transform.SetLocation(FVector( FMath::Cos(Angle) * Radius, FMath::Sin(Angle) * Radius, 0.0f)); } }
03

엔티티 파괴

안전하게 엔티티를 제거하는 패턴

C++ - 엔티티 파괴 방법 // 방법 1: Processor 내부에서 Defer 파괴 void ULifeTimeProcessor::Execute( FMassEntityManager& EntityManager, FMassExecutionContext& Context) { LifeTimeQuery.ForEachEntityChunk(EntityManager, Context, [](FMassExecutionContext& Ctx) { auto LifeTimes = Ctx.GetMutableFragmentView<FLifeTimeFragment>(); auto Entities = Ctx.GetEntities(); const float DT = Ctx.GetDeltaTimeSeconds(); for (int32 i = 0; i < Ctx.GetNumEntities(); ++i) { LifeTimes[i].ElapsedTime += DT; if (LifeTimes[i].ElapsedTime >= LifeTimes[i].MaxLifeTime) { // Defer를 통한 안전한 파괴 Ctx.Defer().DestroyEntity(Entities[i]); } } }); } // 방법 2: Processor 외부에서 직접 파괴 void DestroyDirectly(FMassEntityManager& EM, FMassEntityHandle Entity) { if (EM.IsEntityValid(Entity)) { EM.DestroyEntity(Entity); } } // 방법 3: 배치 파괴 void BatchDestroy(FMassEntityManager& EM, const TArray<FMassEntityHandle>& Entities) { EM.BatchDestroyEntities(Entities); }
파괴 타이밍 주의

Processor 실행 중에는 반드시 Ctx.Defer().DestroyEntity()를 사용하세요. 직접 EntityManager.DestroyEntity()를 호출하면 현재 순회 중인 Chunk의 데이터가 망가져 크래시가 발생합니다.

04

Observer로 정리 처리

엔티티 생성/파괴 시 자동으로 실행되는 후처리

Observer Processor를 활용하면 엔티티 생성/파괴 시 자동으로 초기화/정리 로직을 실행할 수 있습니다.

C++ - 생성 시 초기화 Observer UCLASS() class UEntityInitObserver : public UMassObserverProcessor { GENERATED_BODY() public: UEntityInitObserver() { // Transform Fragment가 추가될 때 트리거 // (= 새 엔티티가 생성될 때) ObservedType = FTransformFragment::StaticStruct(); Operation = EMassObservedOperation::Add; } }; // 파괴 시 정리 Observer UCLASS() class UEntityCleanupObserver : public UMassObserverProcessor { GENERATED_BODY() public: UEntityCleanupObserver() { // Transform Fragment가 제거될 때 트리거 // (= 엔티티가 파괴될 때) ObservedType = FTransformFragment::StaticStruct(); Operation = EMassObservedOperation::Remove; } protected: virtual void Execute( FMassEntityManager& EntityManager, FMassExecutionContext& Context) override { CleanupQuery.ForEachEntityChunk(EntityManager, Context, [](FMassExecutionContext& Ctx) { auto Transforms = Ctx.GetFragmentView<FTransformFragment>(); for (int32 i = 0; i < Ctx.GetNumEntities(); ++i) { // 파괴 이펙트 스폰, 리소스 해제 등 UE_LOG(LogTemp, Log, TEXT("Entity cleaned up at %s"), *Transforms[i].Transform .GetLocation().ToString()); } }); } };
SUMMARY

핵심 요약

  • AMassSpawner는 레벨에 배치하여 Config 기반으로 대량 엔티티를 자동 생성하는 Actor이다
  • C++ 코드에서 CreateEntity/BatchCreateEntities로 동적 생성하고, InitializeEntity로 초기화한다
  • Processor 내부에서는 반드시 Ctx.Defer().DestroyEntity()로 지연 파괴해야 한다
  • Processor 외부에서는 EntityManager.DestroyEntity()로 직접 파괴할 수 있다
  • Observer Processor로 엔티티 생성/파괴 시 초기화/정리 로직을 자동 실행한다
  • 대량 생성 시 BatchCreateEntities가 개별 CreateEntity보다 효율적이다
PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: 런타임 엔티티 스폰

C++에서 FMassEntityManager::BatchCreateEntities를 사용하여 런타임에 100개의 엔티티를 동적으로 생성하세요. FMassEntityTemplateBuildContext로 Fragment 조합을 지정하고 초기값을 설정하세요.

실습 2: 엔티티 파괴와 Deferred Destruction

Processor에서 조건(HP <= 0)을 만족하는 엔티티를 DestroyEntity로 파괴하세요. Deferred Command로 처리해야 하는 이유를 이해하고, 즉시 파괴 시 발생하는 문제를 확인하세요.

심화 과제: 대량 스폰/디스폰 최적화

매 프레임 수백 개의 엔티티를 스폰/디스폰해야 하는 시나리오(예: 총알)에서 BatchCreateEntities와 BatchDestroyEntities의 성능을 단건 처리와 비교하세요. 오브젝트 풀링 패턴도 구현해보세요.