Query와 필터링
FMassEntityQuery로 처리할 엔티티를 정확하게 선택하는 방법을 마스터합니다
FMassEntityQuery 기본
Query의 역할과 기본 사용법
FMassEntityQuery는 Processor가 처리할 엔티티를 선택하는 필터입니다. 어떤 Fragment를 필요로 하는지, 어떤 Tag가 있어야/없어야 하는지를 선언적으로 정의합니다.
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> |
읽기+쓰기, 배타적 접근 필요 |
EMassFragmentPresence
Fragment/Tag의 존재 조건으로 엔티티 필터링
| Presence | 의미 | 사용 예 |
|---|---|---|
All |
반드시 존재해야 함 | FZombieTag::All = 좀비만 처리 |
None |
반드시 없어야 함 | FIsDeadTag::None = 살아있는 것만 |
Optional |
있으면 접근, 없어도 매칭 | FArmorFragment::Optional |
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;
}
});
}
ForEachEntityChunk 패턴
Chunk 순회의 다양한 패턴과 데이터 접근 방법
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);
}
});
}
UE 5.5부터 간소화된 Query API가 도입되었습니다. ForEachEntity 형태로, Chunk를 명시적으로 다루지 않고 엔티티 단위로 직접 접근할 수 있습니다. 단, 내부적으로는 여전히 Chunk 기반이므로 성능 차이는 없습니다.
다중 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);
}
EntityView와 외부 접근
Processor 외부에서 엔티티 데이터에 접근하는 방법
Processor 실행 외부에서 특정 엔티티의 데이터에 접근해야 할 때 FMassEntityView를 사용합니다.
// 특정 엔티티의 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는 Processor의 배치 처리보다 느립니다. 개별 엔티티 접근이 필요한 상황(UI 표시, 특정 엔티티 조작 등)에서만 사용하세요. 대량 처리에는 반드시 Query와 ForEachEntityChunk를 사용해야 합니다.
핵심 요약
- FMassEntityQuery는 Processor가 처리할 엔티티를 선택하는 필터로, Fragment/Tag/SharedFragment/Subsystem 요구사항을 정의한다
- 접근 모드는 ReadOnly(TConstArrayView)와 ReadWrite(TArrayView)로 구분되며, 병렬 처리 안전성에 영향을 준다
- EMassFragmentPresence: All(필수), None(제외), Optional(있으면 사용)로 엔티티를 필터링한다
- ForEachEntityChunk는 Chunk 단위로 콜백을 호출하며, GetFragmentView로 연속 메모리에 접근한다
- 하나의 Processor에서 여러 Query를 사용하여 다양한 엔티티 그룹을 처리할 수 있다
- FMassEntityView는 개별 엔티티 접근용으로, 배치 처리보다 느리므로 제한적으로 사용한다
도전 과제
배운 내용을 직접 실습해보세요
FMassEntityQuery에 AddRequirement로 필수 Fragment, AddTagRequirement로 Tag 필터를 설정하세요. ReadOnly/ReadWrite 접근 모드를 올바르게 지정하고, ForEachEntityChunk로 순회하세요.
FMassEntityQuery에 EMassFragmentPresence::Optional로 Fragment를 추가하고, 실행 시 해당 Fragment가 있는 경우와 없는 경우를 분기 처리하세요. 예: FShieldFragment가 있으면 대미지 감소, 없으면 그대로 적용.
FMassTagPresence::All, Any, None 조합으로 복잡한 필터링을 구성하세요. 예: FAliveTag 필수 + FStunnedTag 제외 + (FPoisonedTag OR FBurningTag) 조건을 만족하는 엔티티만 쿼리하세요.