Think And Build

Unreal Engine

Unreal Engine: Environment Query System (EQS) in C++

Posted on .

Unreal Engine: Environment Query System (EQS) in C++

Introduction

I’m still working at the AI system for The Mirror’s End and I decided to move the entire AI core from Behavior Tree to Utility AI. Environment Query System (EQS) is very well integrated with Behavior Trees and I really didn’t want to loose the ability to run EQS from my custom AI system. Luckily enough Unreal Engine lets us use EQS outside Behavior Trees in a very easy way. With this quick tutorial I’d like to show you how to do it with C++.

LET’S CODE

The data types we are interested in are UEnvQuery and FEnvQueryRequest.
The UEnvQuery class inherits from UDataAsset that, as the UE4 documentation states, is the “base class for a simple asset containing data”.
The easiest way to setup this data is to keep a reference to an UEnvQuery in your Controller.

class AMyController : public AAIController{
.
.
.
    UPROPERTY(EditAnywhere, Category = “AI”)
    UEnvQuery *FindHidingSpotEQS;
.
.
}

And fill it from the editor with an EQS asset previously created:

The FEnvQueryRequest is a struct that works as wrapper to allow query execution and also to pass it information (query parameters).

You will call it directly from your class implementation (I actually prefer to keep a reference in my Controller to the query request , but it is not needed).

RUNNING THE QUERY

From your Controller you can simply run the query through the FEnvQueryRequest this way:

FEnvQueryRequest HidingSpotQueryRequest = FEnvQueryRequest(FindHidingSpotEQS, GetPawn());

HidingSpotQueryRequest.Execute(
        EEnvQueryRunMode:: SingleResult, 
        this,    
        &AMyController::HandleQueryResult);

As first we initialize the query request passing the query we want to run (the reference to the EQS assets previously defined in the controller header) and the request owner, that in this case is the controlled pawn.
When the request is initialized we are ready to execute it with the Execute function.
The first parameter defines how to calculate the result of the query. We obviously have the same options available from the BehaviorTree (SingleResult, RandomBest5Pct, RandomBest25Pct, AllMatching). The second parameter is the delegate object that will receive query results and the third is the delegate method to use to handle the result, this method has to implement the FQueryFinishedSignature signature.

HANDLING QUERY RESULT

The delegate function that we will designate to handle the execution of the query might be something like:

void HandleQueryResult(TSharedPtr result){
    if (result->IsSuccsessful()) {
        MoveToLocation(result->GetItemAsLocation(0));
    }
}

The query result is wrapped into a FEnvQueryResult struct handled by a shared pointer (in a few word, a smart pointer that own the object it points and delete it when no other references to the object are available).
The FEnvQueryResult struct has some handy functions to verify the query result. I’m using isSuccessfull()but you could also use isFinished() and isAborted() to verify other query states. Once you know that the query is successfully completed you can access query results using a function like GetItemAsLocation(index) that will return a single item coming from the query (In the example I’m asking for the item at index 0) or you could ask for GetAllAsLocations() function. If you prefer, and if it has sense for the query you were running, you could also get the items as Actors using GetItemAsActorand GetAllAsActors. Another interesting and useful option is to retrieve all the items scores using the GetItemScore function:

for (int i = 0; i < result->Items.Num(); i++) {
    UE_LOG(LogTemp, Warning, TEXT("Score for item %d is %f"), i, result->GetItemScore(i));
}

Depending on what you have defined as query run mode (SingleResult, RandomBest5Pct, RandomBest25Pct, AllMatching) you might get different scores but the items will be always ordered from highest to lowest score.

Here you can find the complete code example, you can also find a gist on GitHub .

Feel free to poke me on Twitter!


// AMyAIController.h ---------------------------------------------
#include "CoreMinimal.h"
#include "AIController.h"
#include "EnvironmentQuery/EnvQueryTypes.h"
#include "MyAIController.generated.h"

class UEnvQuery;

UCLASS()
class EQSTUTORIAL_API AMyAIController : public AAIController
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, Category = "AI")
	UEnvQuery *FindHidingSpotEQS;

	UFUNCTION(BlueprintCallable)
	void FindHidingSpot();

	void MoveToQueryResult(TSharedPtr<FEnvQueryResult> result);
};

// AMyAIController.cpp ---------------------------------------------
#include "MyAIController.h"
#include "EnvironmentQuery/EnvQueryManager.h"

void AMyAIController::FindHidingSpot()
{
	FEnvQueryRequest HidingSpotQueryRequest = FEnvQueryRequest(FindHidingSpotEQS, GetPawn());
	HidingSpotQueryRequest.Execute(EEnvQueryRunMode::SingleResult, this, &AMyAIController::MoveToQueryResult);
}

void AMyAIController::MoveToQueryResult(TSharedPtr<FEnvQueryResult> result)
{
	if (result->IsSuccsessful()) {
		MoveToLocation(result->GetItemAsLocation(0));
	}
}

Ciao!

Yari D'areglia

Yari D'areglia

https://www.thinkandbuild.it

Senior iOS developer @ Neato Robotics by day, game developer and wannabe artist @ Black Robot Games by night.

Navigation