PART 4 - 강의 3/3

EQS 디버깅과 최적화

Visual Logger, EQS Testing Pawn, 성능 최적화 전략으로 EQS를 실전 수준으로 활용합니다

01

EQS Testing Pawn

에디터에서 EQS 쿼리를 시각적으로 테스트

EQS Testing Pawn은 에디터에서 EQS 쿼리를 실시간으로 시각화하는 도구입니다. 레벨에 배치하고 쿼리를 지정하면, 각 아이템의 점수를 색상으로 표시합니다.

설정// EQS Testing Pawn 사용법 // 1. 레벨에 배치 // Place Actors → AI → EQS Testing Pawn // 2. Details 패널 설정 // - Query Template: 테스트할 EQS 에셋 선택 // - QueryParams: 추가 파라미터 설정 // - Step to Debug Draw: 특정 Test 단계 시각화 // 3. 시각화 해석 // - 파란색 구: 높은 점수 // - 빨간색 구: 낮은 점수 // - X 표시: 필터에 의해 제거됨 // - 구의 크기: 점수 크기 // 4. 런타임 테스트 // - PIE(Play In Editor)에서도 동작 // - Testing Pawn을 이동시키면 실시간 업데이트 // Step to Debug Draw 활용 // 0: 최종 결과 // 1: 첫 번째 Test 후 결과 // 2: 두 번째 Test 후 결과 // → 각 Test가 점수에 미치는 영향을 개별 확인 // QueryingMode // SingleBestItem: 최고 점수 하이라이트 // AllMatching: 모든 통과 아이템 표시

Step to Debug Draw를 변경하면서 각 Test의 영향을 개별적으로 확인할 수 있습니다. 의도치 않은 필터링이나 점수 편향을 찾는 데 매우 유용합니다.

02

Visual Logger와 Gameplay Debugger

런타임 EQS 실행 결과 분석

Visual Logger는 EQS 실행 이력을 타임라인으로 기록하여, 특정 시점의 AI 의사결정 과정을 사후 분석할 수 있습니다. Gameplay Debugger('키)는 런타임에 실시간으로 확인합니다.

C++// Visual Logger 활성화 // Window → Developer Tools → Visual Logger // 또는 콘솔: VisLog // Visual Logger EQS 로그 // - 각 쿼리 실행 시점 표시 // - 각 아이템의 점수 기록 // - 선택된 아이템 하이라이트 // - 필터링된 아이템 이유 표시 // Gameplay Debugger EQS 카테고리 // 런타임: ' (따옴표) 키로 Gameplay Debugger 열기 // Numpad 4: EQS 카테고리 토글 // 표시 내용: // - 마지막 실행된 EQS 쿼리 결과 // - 각 아이템 위치와 점수 // - 선택된 아이템 하이라이트 // C++에서 EQS 로그 추가 UE_VLOG(GetPawn(), LogAI, Log, TEXT("EQS Query: %s, Result: %s"), *QueryAsset->GetName(), Result->IsSuccessful() ? TEXT("Success") : TEXT("Failed")); // EQS 쿼리 실행 통계 // 콘솔 명령어로 확인 // ai.debug.eqs 1 → EQS 디버그 표시 // stat ai → AI 시스템 통계
참고

Visual Logger는 녹화 방식이므로 문제 상황을 재현 후 타임라인을 되감아 분석할 수 있습니다. "왜 그 시점에 그 위치를 선택했는지"를 정확히 파악할 수 있습니다.

03

EQS 성능 최적화

아이템 수 관리와 Test 순서 최적화

EQS 성능은 "아이템 수 x Test 수 x Test 비용"에 비례합니다. 체계적인 최적화 전략으로 프레임 드롭 없이 복잡한 쿼리를 실행할 수 있습니다.

C++// 최적화 전략 1: 아이템 수 줄이기 // - GridSize 축소 또는 SpaceBetween 증가 // - OnCircle의 NumberOfPoints 제한 // - ActorsOfClass의 SearchRadius 축소 // 최적화 전략 2: Test 순서 최적화 // ★ 비용 싼 Filter Test를 먼저 배치 // 순서 예시: // 1. Distance Filter (매우 저렴) → 먼 아이템 제거 // 2. Trace Filter (중간) → LOS 없는 아이템 제거 // 3. Pathfinding Score (비쌈) → 남은 아이템만 평가 // 최적화 전략 3: 쿼리 실행 빈도 제어 // BTService에서 실행 시 Interval 조절 // 또는 조건부 실행: void UBTService_RunEQSOptimized::TickNode( UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) { // 이전 결과가 유효하면 재실행 스킵 FVector LastResult = BB->GetValueAsVector(TEXT("CoverPos")); if (!LastResult.IsNearlyZero() && TimeSinceLastQuery < 3.0f) { return; // 3초 이내면 재사용 } // ... EQS 실행 ... } // 최적화 전략 4: EQS Manager 설정 // Project Settings → AI → EQS // MaxAllowedTestingTime: 프레임당 최대 처리 시간 // 기본값: 0.003 (3ms) // 초과 시 다음 프레임에 이어서 처리 // bTestQueriesUsingBreadth: 너비 우선 처리 // 최적화 전략 5: 비동기 실행 FEnvQueryRequest Request(QueryAsset, this); Request.Execute( EEnvQueryRunMode::SingleBestItem, Callback); // Execute()는 비동기로 동작 // 결과는 다음 프레임 이후 콜백으로 전달
주의

MaxAllowedTestingTime을 초과하면 쿼리가 여러 프레임에 걸쳐 처리됩니다. AI가 즉각 반응해야 하는 상황에서는 아이템 수를 줄여 한 프레임에 완료되도록 하세요.

04

EQS와 BT 통합 패턴

BTTask_RunEQSQuery와 Service 활용

EQS는 주로 BTTask_RunEQSQuery를 통해 BT에서 실행합니다. Service에서 주기적으로 실행하여 Blackboard를 업데이트하는 패턴도 자주 사용됩니다.

C++// 패턴 1: BT Task에서 EQS 실행 // BT 에디터에서: // Task: Run EQS Query // - Query Template: FindCoverPosition // - Run Mode: SingleBestItem // - EQS Query Blackboard Key: CoverLocation // 실행 흐름: // Sequence // ├─ Service: UpdateEnemy (BB에 적 정보) // ├─ Task: RunEQSQuery (CoverLocation 업데이트) // └─ Task: MoveTo (CoverLocation으로 이동) // 패턴 2: Service에서 주기적 EQS // Service: RunEQS // - Query Template: FindBestAttackPosition // - Interval: 2.0 // - BB Key: AttackPosition // 패턴 3: C++에서 조건부 EQS void AMyAIController::FindCoverPosition() { if (!CoverQuery) return; FEnvQueryRequest Request(CoverQuery, this); // BB 값을 EQS 파라미터로 전달 // (Named Parameter 활용) Request.SetFloatParam( FName("SearchRadius"), 2000.f); Request.Execute( EEnvQueryRunMode::RandomBest25Pct, FQueryFinishedSignature::CreateUObject( this, &AMyAIController::OnCoverQueryDone)); } void AMyAIController::OnCoverQueryDone( TSharedPtr<FEnvQueryResult> Result) { if (Result->IsSuccessful()) { FVector CoverPos = Result->GetItemAsLocation(0); GetBlackboardComponent()->SetValueAsVector( TEXT("CoverLocation"), CoverPos); // 점수 확인 (디버깅용) float Score = Result->GetItemScore(0); UE_LOG(LogAI, Log, TEXT("Cover found: Score=%.2f"), Score); } }

EQS 결과를 RandomBest25Pct로 선택하면, AI가 매번 같은 엄폐물만 선택하는 것을 방지합니다. 플레이어 입장에서 AI가 더 자연스럽고 예측 불가능하게 느껴집니다.

SUMMARY

핵심 요약

  • EQS Testing Pawn으로 에디터에서 쿼리를 시각화하고, Step to Debug Draw로 각 Test의 영향을 개별 분석한다
  • Visual Logger는 타임라인 기반 사후 분석, Gameplay Debugger는 실시간 EQS 모니터링에 사용한다
  • 성능은 "아이템 수 x Test 비용"에 비례하므로, 저비용 Filter를 먼저 적용하고 Pathfinding Test는 마지막에 실행한다
  • MaxAllowedTestingTime으로 프레임당 EQS 처리 시간을 제한하여 프레임 드롭을 방지한다
PRACTICE

도전 과제

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

실습 1: EQS Testing Pawn 활용

레벨에 EQS Testing Pawn을 배치하고 쿼리를 할당하세요. 뷰포트에서 각 포인트의 점수를 색상으로 시각화하고, 의도한 위치가 높은 점수를 받는지 검증합니다.

실습 2: 쿼리 성능 최적화

EQS 쿼리에서 Generator의 포인트 수를 줄이고, Test 실행 순서를 조정하세요. 비용이 낮은 Test를 먼저 실행하여 초기에 후보를 걸러내는 방식으로 쿼리 시간을 50% 이상 단축합니다.

심화 과제

50개 이상의 AI가 동시에 EQS를 실행하는 상황에서 프레임 드롭을 방지하세요. 쿼리 실행 주기를 분산(Time Slicing)하고, 결과를 캐싱하여 동일 쿼리의 중복 실행을 방지하는 시스템을 구현합니다.