EQS(Environment Query System) 소개
환경 쿼리 시스템(EQS)은 언리얼 엔진에서 AI가 주변 환경을 분석하고 최적의 결정을 내리는 데 사용되는 강력한 도구입니다.
이 절에서는 EQS의 기본 개념과 C++에서의 활용 방법을 살펴보겠습니다.
EQS의 기본 구성 요소
- 쿼리(Query) : 환경에 대한 질문
- 테스트(Test) : 각 후보 위치에 대한 평가 기준
- 제너레이터(Generator) : 후보 위치 생성 방식
C++에서 커스텀 EQS 쿼리 구현
UCLASS()
class MYPROJECT_API UEnvQueryTest_CustomTest : public UEnvQueryTest
{
GENERATED_BODY()
public:
UEnvQueryTest_CustomTest();
virtual void RunTest(FEnvQueryInstance& QueryInstance) const override;
private:
UPROPERTY(EditAnywhere, Category = "Test Parameters")
FAIDataProviderFloatValue Radius;
};
UEnvQueryTest_CustomTest::UEnvQueryTest_CustomTest()
{
Cost = EEnvTestCost::Low;
ValidItemType = UEnvQueryItemType_Point::StaticClass();
}
void UEnvQueryTest_CustomTest::RunTest(FEnvQueryInstance& QueryInstance) const
{
UObject* QueryOwner = QueryInstance.Owner.Get();
if (QueryOwner == nullptr)
{
return;
}
float RadiusValue = Radius.GetValue();
for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It)
{
const FVector ItemLocation = GetItemLocation(QueryInstance, It.GetIndex());
const float Distance = FVector::Dist(ItemLocation, QueryOwner->GetActorLocation());
It.SetScore(TestPurpose, FilterType, Distance < RadiusValue, Distance);
}
}
EQS를 활용한 동적 환경 분석
UCLASS()
class MYPROJECT_API UEnvQueryContext_DynamicObjects : public UEnvQueryContext
{
GENERATED_BODY()
public:
virtual void ProvideContext(FEnvQueryInstance& QueryInstance, FEnvQueryContextData& ContextData) const override;
};
void UEnvQueryContext_DynamicObjects::ProvideContext(FEnvQueryInstance& QueryInstance, FEnvQueryContextData& ContextData) const
{
TArray<AActor*> DynamicObjects;
UGameplayStatics::GetAllActorsOfClass(QueryInstance.World, ADynamicObject::StaticClass(), DynamicObjects);
UEnvQueryItemType_ActorBase::SetContextHelper(ContextData, DynamicObjects);
}
AI 의사결정 최적화
EQS를 사용하여 AI의 의사결정을 최적화할 수 있습니다.
UCLASS()
class MYPROJECT_API AMyAIController : public AAIController
{
GENERATED_BODY()
public:
void FindBestCoverLocation();
private:
UPROPERTY()
UEnvQuery* FindCoverQuery;
UFUNCTION()
void OnFindCoverQueryFinished(TSharedPtr<FEnvQueryResult> Result);
};
void AMyAIController::FindBestCoverLocation()
{
if (FindCoverQuery)
{
FEnvQueryRequest QueryRequest(FindCoverQuery, GetPawn());
QueryRequest.Execute(EEnvQueryRunMode::SingleResult, this, &AMyAIController::OnFindCoverQueryFinished);
}
}
void AMyAIController::OnFindCoverQueryFinished(TSharedPtr<FEnvQueryResult> Result)
{
if (Result.IsValid() && Result->IsSuccessful())
{
FVector BestLocation = Result->GetItemAsLocation(0);
// 최적의 위치로 이동
}
}
복잡한 게임플레이 상황에서의 EQS 활용
전략적 위치 선정을 위한 EQS 활용 예
UCLASS()
class MYPROJECT_API UEnvQueryTest_StrategicPosition : public UEnvQueryTest
{
GENERATED_BODY()
public:
UEnvQueryTest_StrategicPosition();
virtual void RunTest(FEnvQueryInstance& QueryInstance) const override;
private:
UPROPERTY(EditAnywhere, Category = "Test Parameters")
FAIDataProviderFloatValue CoverValue;
UPROPERTY(EditAnywhere, Category = "Test Parameters")
FAIDataProviderFloatValue VisibilityValue;
};
void UEnvQueryTest_StrategicPosition::RunTest(FEnvQueryInstance& QueryInstance) const
{
for (FEnvQueryInstance::ItemIterator It(this, QueryInstance); It; ++It)
{
const FVector ItemLocation = GetItemLocation(QueryInstance, It.GetIndex());
float CoverScore = CalculateCoverScore(ItemLocation);
float VisibilityScore = CalculateVisibilityScore(ItemLocation);
float FinalScore = (CoverValue.GetValue() * CoverScore + VisibilityValue.GetValue() * VisibilityScore) /
(CoverValue.GetValue() + VisibilityValue.GetValue());
It.SetScore(TestPurpose, FilterType, FinalScore, FinalScore);
}
}
EQS의 성능 최적화 전략
- 쿼리 실행 빈도 조절
UCLASS()
class MYPROJECT_API AMyAIController : public AAIController
{
GENERATED_BODY()
private:
FTimerHandle QueryTimerHandle;
void PerformPeriodicQuery();
};
void AMyAIController::PerformPeriodicQuery()
{
// EQS 쿼리 실행
GetWorldTimerManager().SetTimer(QueryTimerHandle, this, &AMyAIController::PerformPeriodicQuery, 1.0f, false);
}
- 결과 캐싱
UCLASS()
class MYPROJECT_API UMyEQSManager : public UObject
{
GENERATED_BODY()
public:
FVector GetCachedResult(FName QueryName);
void UpdateCache(FName QueryName, FVector Result);
private:
TMap<FName, FVector> QueryResultCache;
};
대규모 환경에서의 효율적인 EQS 사용법
- 공간 분할
UCLASS()
class MYPROJECT_API UEnvQueryGenerator_SpatialPartition : public UEnvQueryGenerator
{
GENERATED_BODY()
public:
virtual void GenerateItems(FEnvQueryInstance& QueryInstance) const override;
private:
UPROPERTY(EditAnywhere, Category = "Generator Parameters")
float CellSize;
};
void UEnvQueryGenerator_SpatialPartition::GenerateItems(FEnvQueryInstance& QueryInstance) const
{
// 공간을 CellSize 크기의 격자로 분할하고 각 셀의 중심점을 생성
}
- LOD (Level of Detail) 시스템 구현
UCLASS()
class MYPROJECT_API UEnvQueryTest_LODFilter : public UEnvQueryTest
{
GENERATED_BODY()
public:
virtual void RunTest(FEnvQueryInstance& QueryInstance) const override;
private:
UPROPERTY(EditAnywhere, Category = "LOD")
float HighDetailDistance;
UPROPERTY(EditAnywhere, Category = "LOD")
float MediumDetailDistance;
};
비헤이비어 트리와 EQS의 연동
비헤이비어 트리에서 EQS를 사용하는 태스크 구현
UCLASS()
class MYPROJECT_API UBTTask_RunEQSQuery : public UBTTaskNode
{
GENERATED_BODY()
public:
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
private:
UPROPERTY(EditAnywhere, Category = "EQS")
UEnvQuery* EQSQuery;
UPROPERTY(EditAnywhere, Category = "Blackboard")
FBlackboardKeySelector ResultKey;
};
EBTNodeResult::Type UBTTask_RunEQSQuery::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
AAIController* AIController = OwnerComp.GetAIOwner();
if (AIController && EQSQuery)
{
FEnvQueryRequest QueryRequest(EQSQuery, AIController->GetPawn());
QueryRequest.Execute(EEnvQueryRunMode::SingleResult, this, &UBTTask_RunEQSQuery::OnQueryFinished);
return EBTNodeResult::InProgress;
}
return EBTNodeResult::Failed;
}
고급 AI 기능 구현
- 은신처 찾기
UCLASS()
class MYPROJECT_API UEnvQueryTest_CoverQuality : public UEnvQueryTest
{
GENERATED_BODY()
public:
virtual void RunTest(FEnvQueryInstance& QueryInstance) const override;
private:
UPROPERTY(EditAnywhere, Category = "Cover")
FAIDataProviderFloatValue MinCoverHeight;
float EvaluateCoverQuality(const FVector& Location, const AActor* Threat) const;
};
- 전략적 위치 선정
UCLASS()
class MYPROJECT_API UEnvQueryTest_StrategicValue : public UEnvQueryTest
{
GENERATED_BODY()
public:
virtual void RunTest(FEnvQueryInstance& QueryInstance) const override;
private:
UPROPERTY(EditAnywhere, Category = "Strategy")
FAIDataProviderFloatValue ControlPointWeight;
UPROPERTY(EditAnywhere, Category = "Strategy")
FAIDataProviderFloatValue ResourcePointWeight;
float CalculateStrategicValue(const FVector& Location) const;
};
- 동적 난이도 조절
UCLASS()
class MYPROJECT_API UEnvQueryTest_DynamicDifficulty : public UEnvQueryTest
{
GENERATED_BODY()
public:
virtual void RunTest(FEnvQueryInstance& QueryInstance) const override;
private:
UPROPERTY(EditAnywhere, Category = "Difficulty")
FAIDataProviderFloatValue PlayerSkillFactor;
float AdjustScoreBasedOnDifficulty(float BaseScore, float PlayerSkill) const;
};
EQS는 언리얼 엔진에서 AI의 환경 인식과 의사결정을 크게 향상시킬 수 있는 강력한 도구입니다.
C++를 사용하여 커스텀 쿼리와 테스트를 구현함으로써 게임의 특정 요구사항에 맞는 복잡한 AI 행동을 구현할 수 있습니다.
동적 환경 분석을 통해 AI는 실시간으로 변화하는 게임 환경에 적응할 수 있으며, 이는 더 현실적이고 도전적인 AI 행동으로 이어집니다.
또한 EQS를 비헤이비어 트리와 연동하여 사용하면 더욱 유연하고 강력한 AI 시스템을 구축할 수 있습니다.