UE5 Mass Entity — 대규모 엔티티 시뮬레이션 (ECS)
Mass Entity는 UE5의 ECS(Entity Component System) 프레임워크입니다. 전통적인 Actor 기반 접근과 달리 데이터(Fragment)와 로직(Processor)을 분리하고 캐시 친화적 SoA(Structure of Arrays) 레이아웃으로 수천 개 엔티티를 효율적으로 처리합니다.
1. 핵심 개념
섹션 제목: “1. 핵심 개념”Entity — 고유 ID (데이터 없음, 순수 핸들)Fragment — 엔티티에 붙는 데이터 컴포넌트 (USTRUCT)Tag — 데이터 없는 마커 (필터링용)Archetype — Fragment 조합이 동일한 엔티티 묶음Processor — Archetype을 쿼리해 Fragment를 처리하는 시스템Trait — 여러 Fragment/Processor를 묶은 재사용 단위2. Fragment 정의
섹션 제목: “2. Fragment 정의”// Fragment: 순수 데이터 구조체USTRUCT()struct FMassVelocityFragment : public FMassFragment{ GENERATED_BODY() FVector Value = FVector::ZeroVector;};
USTRUCT()struct FMassHealthFragment : public FMassFragment{ GENERATED_BODY() float Current = 100.0f; float Max = 100.0f;};
// Tag: 상태 마커 (데이터 없음)USTRUCT()struct FMassDeadTag : public FMassTag{ GENERATED_BODY()};3. Processor 구현
섹션 제목: “3. Processor 구현”UCLASS()class UMoveProcessor : public UMassProcessor{ GENERATED_BODY()public: UMoveProcessor();
protected: virtual void ConfigureQueries() override; virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override;
private: FMassEntityQuery EntityQuery;};
UMoveProcessor::UMoveProcessor(){ // 이 Processor가 처리할 Phase 지정 ExecutionFlags = (int32)EProcessorExecutionFlags::All; ProcessingPhase = EMassProcessingPhase::PrePhysics;}
void UMoveProcessor::ConfigureQueries(){ // Velocity와 Transform이 모두 있는 엔티티만 쿼리 EntityQuery.AddRequirement<FMassVelocityFragment>( EMassFragmentAccess::ReadOnly); EntityQuery.AddRequirement<FTransformFragment>( EMassFragmentAccess::ReadWrite); EntityQuery.AddTagRequirement<FMassDeadTag>( EMassFragmentPresence::None); // Dead 태그 없는 것만}
void UMoveProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context){ float DeltaTime = Context.GetDeltaTimeSeconds();
EntityQuery.ForEachEntityChunk(EntityManager, Context, [DeltaTime](FMassExecutionContext& Ctx) { // Chunk 단위로 SIMD 친화적 처리 TArrayView<const FMassVelocityFragment> Velocities = Ctx.GetFragmentView<FMassVelocityFragment>(); TArrayView<FTransformFragment> Transforms = Ctx.GetMutableFragmentView<FTransformFragment>();
const int32 Count = Ctx.GetNumEntities(); for (int32 i = 0; i < Count; ++i) { FVector NewLoc = Transforms[i].GetTransform().GetLocation() + Velocities[i].Value * DeltaTime; Transforms[i].GetMutableTransform().SetLocation(NewLoc); } });}4. Trait 정의 — 재사용 가능한 설정 묶음
섹션 제목: “4. Trait 정의 — 재사용 가능한 설정 묶음”UCLASS()class UMovableTrait : public UMassEntityTraitBase{ GENERATED_BODY()public: UPROPERTY(EditAnywhere) float MaxSpeed = 300.0f;
virtual void BuildTemplate( FMassEntityTemplateBuildContext& BuildContext, const UWorld& World) const override { // 이 Trait이 필요한 Fragment 추가 BuildContext.AddFragment<FMassVelocityFragment>(); BuildContext.AddFragment<FTransformFragment>();
// 초기값 설정 FMassVelocityFragment& Vel = BuildContext.AddFragmentWithDefaultValue<FMassVelocityFragment>(); Vel.Value = FVector::ZeroVector; }};5. Entity 생성 및 관리
섹션 제목: “5. Entity 생성 및 관리”// EntityManager를 통한 엔티티 생성void AMySpawner::SpawnCrowd(int32 Count){ UMassEntitySubsystem* EntitySubsystem = World->GetSubsystem<UMassEntitySubsystem>(); FMassEntityManager& EntityManager = EntitySubsystem->GetMutableEntityManager();
// 아키타입 빌드 FMassArchetypeHandle Archetype = EntityManager.CreateArchetype( {FMassVelocityFragment::StaticStruct(), FTransformFragment::StaticStruct()});
// 배치 생성 (효율적) TArray<FMassEntityHandle> Entities; EntityManager.BatchCreateEntities(Archetype, Count, Entities);
// Fragment 초기화 for (auto& Entity : Entities) { FMassVelocityFragment& Vel = EntityManager.GetFragmentDataChecked<FMassVelocityFragment>(Entity); Vel.Value = FMath::VRand() * 200.0f; }}6. Mass + Niagara 시각화
섹션 제목: “6. Mass + Niagara 시각화”Mass Entity는 렌더링이 없으므로 Niagara 또는 ISM(Instanced Static Mesh)으로 시각화합니다.
MassEntity → FTransformFragment 위치 데이터 ↓MassVisualization Processor → ISMComponent 인스턴스 위치 동기화플러그인: MassGameplay, MassRepresentation 활성화 필요
7. 성능 비교
섹션 제목: “7. 성능 비교”| 항목 | Actor 기반 | Mass Entity |
|---|---|---|
| 1만 에이전트 | 불가 (~10fps) | 가능 (60fps+) |
| 메모리 레이아웃 | AoS (캐시 미스) | SoA (캐시 친화적) |
| 틱 오버헤드 | Actor마다 vtable | Chunk 배치 처리 |
| 렌더링 | 개별 메시 | ISM/Niagara |
8. Shared Fragment — 아키타입 공유 데이터
섹션 제목: “8. Shared Fragment — 아키타입 공유 데이터”FMassSharedFragment는 같은 아키타입 내 모든 엔티티가 하나의 인스턴스를 공유합니다. 설정값(최대 속도, 충돌 반경)처럼 엔티티마다 다를 필요 없는 데이터에 적합합니다.
// Shared Fragment: 아키타입 전체가 공유하는 설정 데이터USTRUCT()struct FMassMovementConfigFragment : public FMassSharedFragment{ GENERATED_BODY() float MaxSpeed = 300.f; float AccelRate = 600.f; float AvoidanceRadius = 80.f;};
// Processor에서 Shared Fragment 읽기void UMoveProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context){ EntityQuery.ForEachEntityChunk(EntityManager, Context, [](FMassExecutionContext& Ctx) { // Shared: Chunk 당 하나 (캐시 효율적) const FMassMovementConfigFragment& Config = Ctx.GetConstSharedFragment<FMassMovementConfigFragment>();
TArrayView<FMassVelocityFragment> Velocities = Ctx.GetMutableFragmentView<FMassVelocityFragment>();
for (auto& Vel : Velocities) { Vel.Value = Vel.Value.GetClampedToMaxSize(Config.MaxSpeed); } });}9. Command Buffer — 지연 엔티티 조작
섹션 제목: “9. Command Buffer — 지연 엔티티 조작”Processor 실행 중 엔티티를 직접 생성·삭제하면 Chunk 순회가 무효화됩니다. FMassCommandBuffer로 요청을 지연시킵니다.
void UHealthProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context){ EntityQuery.ForEachEntityChunk(EntityManager, Context, [](FMassExecutionContext& Ctx) { auto Healths = Ctx.GetMutableFragmentView<FMassHealthFragment>(); TArrayView<FMassEntityHandle> Entities = Ctx.GetEntities();
for (int32 i = 0; i < Ctx.GetNumEntities(); ++i) { if (Healths[i].Current <= 0.f) { // 즉시 삭제 대신 명령 버퍼에 추가 Ctx.Defer().AddTag<FMassDeadTag>(Entities[i]); Ctx.Defer().PushCommand<FMassCommandDestroyEntity>(Entities[i]); } } }); // ForEachEntityChunk 종료 후 명령 버퍼 일괄 실행}Mass Entity는 수천~수만 개의 단순한 에이전트(군중, 트래픽, NPC 무리)를 60fps로 시뮬레이션할 수 있는 UE5의 ECS 솔루션입니다. Fragment로 데이터를 정의하고, Processor로 Chunk 단위 배치 처리를 구현하며, Trait으로 재사용 가능한 엔티티 템플릿을 구성하는 것이 핵심 패턴입니다.