콘텐츠로 이동

UE5 EQS (Environmental Query System)

EQS(Environmental Query System)는 AI가 주변 환경을 쿼리해 가장 적합한 위치나 액터를 선택하는 시스템입니다.

예시 사용 사례:

  • 플레이어에서 숨을 수 있는 엄폐물 찾기
  • 적을 공격할 수 있는 최적 사격 위치 선택
  • 아군 근처의 안전한 이동 목표 지정

Editor Preferences → Experimental → AI → Environment Query System 활성화

에디터에서 EQS Query 에셋 생성:

Generator: Points around Context (Querier 주변 360도 샘플링)
└─ Radius: 500, Points per circle: 16, Num circles: 3
Tests:
1. Trace (Line of Sight to Player) Weight: 1.0 Filter: Must Pass
2. Distance to Player Weight: -1.0 (가까울수록 낮은 점수 = 멀리서 사격)
3. Distance to Querier Weight: 0.5 (너무 멀리 이동하지 않도록)
UCLASS()
class UCoverPointGenerator : public UEnvQueryGenerator_ProjectedPoints {
GENERATED_BODY()
UPROPERTY(EditDefaultsOnly) float SearchRadius = 600.f;
UPROPERTY(EditDefaultsOnly) int32 NumPoints = 24;
public:
virtual void GenerateItems(FEnvQueryInstance& QueryInstance) const override {
UObject* Querier = QueryInstance.Owner.Get();
if (!Querier) return;
FVector Origin = Cast<AActor>(Querier)->GetActorLocation();
TArray<FNavLocation> NavPoints;
for (int32 i = 0; i < NumPoints; i++) {
float Angle = (360.f / NumPoints) * i;
float Rad = FMath::DegreesToRadians(Angle);
FVector Offset(FMath::Cos(Rad), FMath::Sin(Rad), 0.f);
FVector Point = Origin + Offset * SearchRadius;
// NavMesh 위 실제 위치로 투영
FNavLocation NavLoc;
if (GetWorld()->GetNavigationSystem()->ProjectPointToNavigation(
Point, NavLoc, FVector(50, 50, 50)))
NavPoints.Add(NavLoc);
}
AddItemData<UEnvQueryItemType_Point>(QueryInstance, NavPoints);
}
};
UCLASS()
class UCoverTest : public UEnvQueryTest {
GENERATED_BODY()
public:
UCoverTest() {
Cost = EEnvTestCost::High;
ValidItemType = UEnvQueryItemType_Point::StaticClass();
SetWorkOnFloatValues(true);
}
virtual void RunTest(FEnvQueryInstance& QueryInstance,
const FEnvQueryContextData& ContextData) const override {
UObject* Querier = QueryInstance.Owner.Get();
ACharacter* Char = Cast<ACharacter>(Querier);
if (!Char) return;
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
if (!PC || !PC->GetPawn()) return;
FVector PlayerLoc = PC->GetPawn()->GetActorLocation();
for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It) {
FVector TestPoint = GetItemLocation(QueryInstance, It.GetIndex());
// 플레이어에서 테스트 위치로 시야 차단 여부
FHitResult Hit;
bool bBlocked = GetWorld()->LineTraceSingleByChannel(
Hit, PlayerLoc, TestPoint,
ECC_Visibility);
It.SetScore(TestPurpose, FilterType,
bBlocked ? 1.f : 0.f, // 시야 차단 = 좋은 엄폐물
0.f, 1.f);
}
}
};
Behavior Tree:
Sequence
└─ EQS Query Task
├─ Query Asset: EQS_FindCoverPoint
├─ Query Context: EQSContext_Player (타겟 기준)
└─ Blackboard Key: BB_MoveTarget
└─ Move To (BB_MoveTarget)
void AMyAIController::FindBestCoverPoint() {
FEnvQueryRequest QueryRequest(CoverQueryAsset, this);
QueryRequest.Execute(EEnvQueryRunMode::SingleResult,
this, &AMyAIController::OnCoverQueryFinished);
}
void AMyAIController::OnCoverQueryFinished(TSharedPtr<FEnvQueryResult> Result) {
if (Result->IsSuccessful()) {
FVector BestLoc = Result->GetItemAsLocation(0);
GetBlackboardComponent()->SetValueAsVector("MoveTarget", BestLoc);
MoveToLocation(BestLoc);
}
}
  • EQS는 Generator(위치 생성) + Test(점수 평가) 파이프라인
  • C++ 커스텀 Generator로 게임 특화 샘플링 전략 구현
  • C++ 커스텀 Test로 Line Trace, 거리, 시야 등 점수 계산
  • Behavior Tree의 RunEQSQuery 태스크로 결과를 블랙보드에 저장
  • SingleResult 모드로 최고 점수 위치만 빠르게 반환