PART 2 · 강의 2/3

Query와 필터링

FMassEntityQuery로 처리할 엔티티를 정확하게 선택하는 방법을 마스터합니다

01

FMassEntityQuery 기본

Query의 역할과 기본 사용법

FMassEntityQuery는 Processor가 처리할 엔티티를 선택하는 필터입니다. 어떤 Fragment를 필요로 하는지, 어떤 Tag가 있어야/없어야 하는지를 선언적으로 정의합니다.

C++ - Query Requirements void UMyProcessor::ConfigureQueries() { // Fragment 요구사항: 접근 모드 지정 MyQuery.AddRequirement<FTransformFragment>( EMassFragmentAccess::ReadWrite); // 읽기+쓰기 MyQuery.AddRequirement<FVelocityFragment>( EMassFragmentAccess::ReadOnly); // 읽기만 // Tag 요구사항: 존재 여부 지정 MyQuery.AddTagRequirement<FZombieTag>( EMassFragmentPresence::All); // 반드시 있어야 함 MyQuery.AddTagRequirement<FIsDeadTag>( EMassFragmentPresence::None); // 없어야 함 // SharedFragment 요구사항 MyQuery.AddSharedRequirement<FEnemyConfigSharedFragment>( EMassFragmentAccess::ReadOnly); // 서브시스템 요구사항 MyQuery.AddSubsystemRequirement<UMassSignalSubsystem>( EMassFragmentAccess::ReadWrite); // Processor에 등록 MyQuery.RegisterWithProcessor(*this); }
접근 모드 반환 타입 설명
ReadOnly TConstArrayView<T> 읽기만 가능, 병렬 실행에 안전
ReadWrite TArrayView<T> 읽기+쓰기, 배타적 접근 필요
02

EMassFragmentPresence

Fragment/Tag의 존재 조건으로 엔티티 필터링

Presence 의미 사용 예
All 반드시 존재해야 함 FZombieTag::All = 좀비만 처리
None 반드시 없어야 함 FIsDeadTag::None = 살아있는 것만
Optional 있으면 접근, 없어도 매칭 FArmorFragment::Optional
C++ - Optional Fragment 활용 void UDamageProcessor::ConfigureQueries() { DamageQuery.AddRequirement<FHealthFragment>( EMassFragmentAccess::ReadWrite, EMassFragmentPresence::All); // 방어구가 있을 수도 있고 없을 수도 있음 DamageQuery.AddRequirement<FArmorFragment>( EMassFragmentAccess::ReadOnly, EMassFragmentPresence::Optional); DamageQuery.RegisterWithProcessor(*this); } void UDamageProcessor::Execute( FMassEntityManager& EntityManager, FMassExecutionContext& Context) { DamageQuery.ForEachEntityChunk(EntityManager, Context, [](FMassExecutionContext& Ctx) { auto HealthList = Ctx.GetMutableFragmentView<FHealthFragment>(); // Optional Fragment: 존재 여부를 Chunk 단위로 확인 const TConstArrayView<FArmorFragment> ArmorList = Ctx.GetFragmentView<FArmorFragment>(); const bool bHasArmor = (ArmorList.Num() > 0); for (int32 i = 0; i < Ctx.GetNumEntities(); ++i) { float Damage = 10.0f; if (bHasArmor) { Damage *= (1.0f - ArmorList[i].Reduction); } HealthList[i].Current -= Damage; } }); }
03

ForEachEntityChunk 패턴

Chunk 순회의 다양한 패턴과 데이터 접근 방법

C++ - 다양한 데이터 접근 패턴 void UMyProcessor::Execute( FMassEntityManager& EntityManager, FMassExecutionContext& Context) { MyQuery.ForEachEntityChunk(EntityManager, Context, [](FMassExecutionContext& Ctx) { // 1. Mutable Fragment (ReadWrite) auto Transforms = Ctx.GetMutableFragmentView<FTransformFragment>(); // 2. Const Fragment (ReadOnly) const auto Velocities = Ctx.GetFragmentView<FVelocityFragment>(); // 3. SharedFragment 접근 const FEnemyConfigSharedFragment& Config = Ctx.GetSharedFragment<FEnemyConfigSharedFragment>(); // 4. Entity 핸들 배열 const TConstArrayView<FMassEntityHandle> Entities = Ctx.GetEntities(); // 5. 서브시스템 접근 UMassSignalSubsystem& SignalSubsystem = Ctx.GetMutableSubsystemChecked<UMassSignalSubsystem>(); // 6. 델타 타임 const float DT = Ctx.GetDeltaTimeSeconds(); // 7. 엔티티 순회 for (int32 i = 0; i < Ctx.GetNumEntities(); ++i) { // 연속 메모리 인덱스 접근 → 캐시 효율적 Transforms[i].Transform.AddToTranslation( Velocities[i].LinearVelocity * DT); } }); }
Simplified Query API (UE 5.5+)

UE 5.5부터 간소화된 Query API가 도입되었습니다. ForEachEntity 형태로, Chunk를 명시적으로 다루지 않고 엔티티 단위로 직접 접근할 수 있습니다. 단, 내부적으로는 여전히 Chunk 기반이므로 성능 차이는 없습니다.

다중 Query 사용

C++ - 하나의 Processor에서 여러 Query class UCombatProcessor : public UMassProcessor { FMassEntityQuery AttackerQuery; // 공격자 엔티티 FMassEntityQuery DefenderQuery; // 방어자 엔티티 }; void UCombatProcessor::ConfigureQueries() { // 공격자: AttackTag가 있는 엔티티 AttackerQuery.AddRequirement<FTransformFragment>( EMassFragmentAccess::ReadOnly); AttackerQuery.AddTagRequirement<FAttackerTag>( EMassFragmentPresence::All); AttackerQuery.RegisterWithProcessor(*this); // 방어자: DefenderTag가 있는 엔티티 DefenderQuery.AddRequirement<FTransformFragment>( EMassFragmentAccess::ReadOnly); DefenderQuery.AddRequirement<FHealthFragment>( EMassFragmentAccess::ReadWrite); DefenderQuery.AddTagRequirement<FDefenderTag>( EMassFragmentPresence::All); DefenderQuery.RegisterWithProcessor(*this); }
04

EntityView와 외부 접근

Processor 외부에서 엔티티 데이터에 접근하는 방법

Processor 실행 외부에서 특정 엔티티의 데이터에 접근해야 할 때 FMassEntityView를 사용합니다.

C++ - EntityView 사용 // 특정 엔티티의 Fragment에 안전하게 접근 FMassEntityView EntityView(EntityManager, TargetEntity); // Fragment 읽기 const FTransformFragment& Transform = EntityView.GetFragmentData<FTransformFragment>(); // Fragment 수정 FHealthFragment& Health = EntityView.GetFragmentData<FHealthFragment>(); Health.Current -= Damage; // Fragment 존재 여부 확인 if (EntityView.HasTag<FZombieTag>()) { // 좀비 전용 로직 }
EntityView 주의사항

EntityView는 Processor의 배치 처리보다 느립니다. 개별 엔티티 접근이 필요한 상황(UI 표시, 특정 엔티티 조작 등)에서만 사용하세요. 대량 처리에는 반드시 Query와 ForEachEntityChunk를 사용해야 합니다.

SUMMARY

핵심 요약

  • FMassEntityQuery는 Processor가 처리할 엔티티를 선택하는 필터로, Fragment/Tag/SharedFragment/Subsystem 요구사항을 정의한다
  • 접근 모드는 ReadOnly(TConstArrayView)와 ReadWrite(TArrayView)로 구분되며, 병렬 처리 안전성에 영향을 준다
  • EMassFragmentPresence: All(필수), None(제외), Optional(있으면 사용)로 엔티티를 필터링한다
  • ForEachEntityChunk는 Chunk 단위로 콜백을 호출하며, GetFragmentView로 연속 메모리에 접근한다
  • 하나의 Processor에서 여러 Query를 사용하여 다양한 엔티티 그룹을 처리할 수 있다
  • FMassEntityView는 개별 엔티티 접근용으로, 배치 처리보다 느리므로 제한적으로 사용한다
PRACTICE

도전 과제

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

실습 1: FMassEntityQuery 설정

FMassEntityQuery에 AddRequirement로 필수 Fragment, AddTagRequirement로 Tag 필터를 설정하세요. ReadOnly/ReadWrite 접근 모드를 올바르게 지정하고, ForEachEntityChunk로 순회하세요.

실습 2: Optional Fragment 처리

FMassEntityQuery에 EMassFragmentPresence::Optional로 Fragment를 추가하고, 실행 시 해당 Fragment가 있는 경우와 없는 경우를 분기 처리하세요. 예: FShieldFragment가 있으면 대미지 감소, 없으면 그대로 적용.

심화 과제: Tag 기반 필터링 최적화

FMassTagPresence::All, Any, None 조합으로 복잡한 필터링을 구성하세요. 예: FAliveTag 필수 + FStunnedTag 제외 + (FPoisonedTag OR FBurningTag) 조건을 만족하는 엔티티만 쿼리하세요.