PART 2 · 강의 1/3

MassProcessor 기초

ECS의 System에 해당하는 MassProcessor의 구조와 작성 방법을 학습합니다

01

Processor의 역할

데이터를 변환하는 상태 없는 로직 단위

UMassProcessor는 ECS의 System에 해당합니다. Fragment 데이터를 읽고 변환하는 상태 없는(Stateless) 로직 클래스입니다. 매 틱마다 자동으로 실행되며, Query를 통해 처리할 엔티티를 선택합니다.

핵심 원칙

Processor는 데이터를 소유하지 않습니다. Fragment가 데이터를, Processor가 로직을 담당합니다. 이 분리가 Mass Entity의 확장성과 병렬 처리의 기반입니다.

C++ - 기본 Processor 구조 // MyMovementProcessor.h UCLASS() class UMyMovementProcessor : public UMassProcessor { GENERATED_BODY() public: UMyMovementProcessor(); protected: // Query 설정: 어떤 Fragment를 사용할지 정의 virtual void ConfigureQueries() override; // 매 틱 실행되는 메인 로직 virtual void Execute( FMassEntityManager& EntityManager, FMassExecutionContext& Context) override; private: FMassEntityQuery MovementQuery; };
C++ - Processor 구현 // MyMovementProcessor.cpp UMyMovementProcessor::UMyMovementProcessor() { // 자동으로 처리 단계에 등록 bAutoRegisterWithProcessingPhases = true; // 물리 시뮬레이션 전에 실행 ProcessingPhase = EMassProcessingPhase::PrePhysics; // 실행 환경 설정 ExecutionFlags = (int32)( EProcessorExecutionFlags::Client | EProcessorExecutionFlags::Standalone); } void UMyMovementProcessor::ConfigureQueries() { // Transform을 읽고 쓸 수 있게, Velocity는 읽기만 MovementQuery.AddRequirement<FTransformFragment>( EMassFragmentAccess::ReadWrite); MovementQuery.AddRequirement<FVelocityFragment>( EMassFragmentAccess::ReadOnly); // 이 Processor에 Query 등록 MovementQuery.RegisterWithProcessor(*this); } void UMyMovementProcessor::Execute( FMassEntityManager& EntityManager, FMassExecutionContext& Context) { MovementQuery.ForEachEntityChunk(EntityManager, Context, [](FMassExecutionContext& Ctx) { auto Transforms = Ctx.GetMutableFragmentView<FTransformFragment>(); const auto Velocities = Ctx.GetFragmentView<FVelocityFragment>(); const float DT = Ctx.GetDeltaTimeSeconds(); for (int32 i = 0; i < Ctx.GetNumEntities(); ++i) { Transforms[i].Transform.AddToTranslation( Velocities[i].LinearVelocity * DT); } }); }
02

처리 단계 (Processing Phase)

6개의 처리 단계로 구성된 실행 파이프라인

Mass Entity는 매 틱마다 6개의 처리 단계(Processing Phase)를 순서대로 실행합니다. 각 Processor는 하나의 단계에 등록됩니다.

단계 EMassProcessingPhase 용도
1 PrePhysics 물리 전 준비: 이동, 입력 처리, AI 결정
2 StartPhysics 물리 시작: 물리 시뮬레이션에 데이터 전달
3 DuringPhysics 물리 실행 중: 물리와 무관한 처리
4 EndPhysics 물리 종료: 물리 결과 수집
5 PostPhysics 물리 후 처리: 충돌 응답, 위치 보정
6 FrameEnd 프레임 마무리: 시각화 업데이트, 정리
C++ - 단계별 Processor 배치 예시 // AI 판단 → PrePhysics UAIDecisionProcessor::UAIDecisionProcessor() { ProcessingPhase = EMassProcessingPhase::PrePhysics; } // 이동 적용 → PrePhysics (AI 이후) UMovementProcessor::UMovementProcessor() { ProcessingPhase = EMassProcessingPhase::PrePhysics; } // 충돌 후 보정 → PostPhysics UCollisionResponseProcessor::UCollisionResponseProcessor() { ProcessingPhase = EMassProcessingPhase::PostPhysics; } // 시각화 업데이트 → FrameEnd UVisualizationProcessor::UVisualizationProcessor() { ProcessingPhase = EMassProcessingPhase::FrameEnd; }
03

ExecutionFlags와 실행 제어

서버, 클라이언트, 스탠드얼론 환경별 실행 제어

ExecutionFlags는 Processor가 어떤 네트워크 환경에서 실행될지를 결정합니다.

플래그 설명
Server 데디케이트 서버에서만 실행
Client 클라이언트에서만 실행
Standalone 스탠드얼론(싱글플레이) 모드에서 실행
C++ - ExecutionFlags 조합 // 클라이언트와 스탠드얼론에서 실행 (시각화 등) ExecutionFlags = (int32)( EProcessorExecutionFlags::Client | EProcessorExecutionFlags::Standalone); // 서버와 스탠드얼론에서 실행 (게임 로직) ExecutionFlags = (int32)( EProcessorExecutionFlags::Server | EProcessorExecutionFlags::Standalone); // 모든 환경에서 실행 ExecutionFlags = (int32)( EProcessorExecutionFlags::Server | EProcessorExecutionFlags::Client | EProcessorExecutionFlags::Standalone);

게임 스레드 실행 강제

C++ - bRequiresGameThreadExecution UMyProcessor::UMyProcessor() { // true: 반드시 게임 스레드에서 실행 // (UObject 접근, 월드 쿼리 등이 필요할 때) bRequiresGameThreadExecution = true; // false: 워커 스레드에서 병렬 실행 가능 (기본값) // bRequiresGameThreadExecution = false; }
게임 스레드 주의

bRequiresGameThreadExecution = true는 성능에 부정적입니다. UObject 접근, 월드 쿼리, 라인 트레이스 등 게임 스레드가 꼭 필요한 경우에만 사용하세요. 대부분의 순수 데이터 처리 Processor는 false로 두어 병렬 실행을 허용합니다.

04

Observer Processor

이벤트 기반으로 동작하는 특수 Processor

UMassObserverProcessor는 매 틱 실행되는 일반 Processor와 달리, 특정 Fragment/Tag가 추가되거나 제거될 때만 트리거됩니다. 초기화나 정리 로직에 적합합니다.

C++ - Observer Processor UCLASS() class UDeathObserver : public UMassObserverProcessor { GENERATED_BODY() public: UDeathObserver(); protected: virtual void ConfigureQueries() override; virtual void Execute( FMassEntityManager& EntityManager, FMassExecutionContext& Context) override; FMassEntityQuery DeathQuery; }; UDeathObserver::UDeathObserver() { // FIsDeadTag가 추가될 때 트리거 ObservedType = FIsDeadTag::StaticStruct(); Operation = EMassObservedOperation::Add; ExecutionFlags = (int32)( EProcessorExecutionFlags::Server | EProcessorExecutionFlags::Standalone); } void UDeathObserver::ConfigureQueries() { DeathQuery.AddRequirement<FTransformFragment>( EMassFragmentAccess::ReadOnly); DeathQuery.AddTagRequirement<FIsDeadTag>( EMassFragmentPresence::All); DeathQuery.RegisterWithProcessor(*this); } void UDeathObserver::Execute( FMassEntityManager& EntityManager, FMassExecutionContext& Context) { DeathQuery.ForEachEntityChunk(EntityManager, Context, [](FMassExecutionContext& Ctx) { auto Transforms = Ctx.GetFragmentView<FTransformFragment>(); auto Entities = Ctx.GetEntities(); for (int32 i = 0; i < Ctx.GetNumEntities(); ++i) { // 사망 처리: 파티클 스폰, 점수 계산 등 UE_LOG(LogTemp, Log, TEXT("Entity died at %s"), *Transforms[i].Transform.GetLocation().ToString()); // 엔티티 파괴 예약 Ctx.Defer().DestroyEntity(Entities[i]); } }); }
Observer 활용 사례

Add 트리거: 초기화, 시각 이펙트 스폰, 사운드 재생
Remove 트리거: 리소스 정리, 메모리 해제, 통계 업데이트

SUMMARY

핵심 요약

  • UMassProcessor는 상태 없는 로직 클래스로, ConfigureQueries()와 Execute()를 오버라이드한다
  • 6개의 ProcessingPhase가 매 틱 순서대로 실행된다: PrePhysics ~ FrameEnd
  • ExecutionFlags로 Server/Client/Standalone 환경별 실행을 제어한다
  • bRequiresGameThreadExecution은 UObject 접근이 필요할 때만 true로 설정한다
  • UMassObserverProcessor는 Fragment/Tag 추가/제거 이벤트에 반응하는 특수 Processor이다
  • Processor는 데이터를 소유하지 않으며, Query를 통해 필요한 Fragment에만 접근한다
PRACTICE

도전 과제

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

실습 1: 기본 MassProcessor 구현

UMassProcessor를 상속받아 이동 Processor를 만드세요. ConfigureQueries에서 FTransformFragment(ReadWrite)와 FVelocityFragment(ReadOnly)를 요구하고, Execute에서 Transform += Velocity * DeltaTime을 계산하세요.

실습 2: Observer Processor 구현

UMassObserverProcessor를 상속받아 특정 Fragment 추가/제거를 감지하는 Processor를 만드세요. FDeadTag가 추가될 때 트리거되어 사망 처리 로직을 실행하도록 구현하세요.

심화 과제: 멀티스레드 Processor 작성

bRequiresGameThreadExecution=false로 설정하여 멀티스레드에서 실행되는 Processor를 작성하세요. Thread-safe한 데이터 접근 패턴을 확인하고, 동일 Processor의 싱글/멀티스레드 성능을 비교하세요.