While our game may be running without any issues in the editor or even in…
Creating Custom EQS Generators in C++
In this post we’re going to create our own EQS Generator in Unreal Engine 4. This post assumes you’re familiar with the Environment Query System that resides in UE4. In case you don’t quite remember what this is, check out my previous post here.
Before we start creating our own Generator, check out the end result here:
This post was written in 4.13 version of UE4. In case you’re using a different version you may need to adjust the code in order to match with the corresponding API changes.
Creating a custom EQS Generator
Our generator will create a cone in front of our AI pawn. Please note that in this case we won’t create a Pawn with custom behavior, instead, we’re going to use an EQS Testing Pawn. These Pawns help us test our EQS logic faster, since we don’t have to play or simulate our game.
Having said that, create a template project (it doesn’t matter if it’s C++ or Blueprint) and when the editor loads up activate the Environment Query System. In 4.13 version this option is located in the following path:
Edit -> Editor Preferences -> Experimental -> AI.
Here is a screenshot of the mentioned path:
When you’re done with that, add a new C++ class named EnvQueryGenerator_Cone that inherits the EnvQueryGenerator_ProjectedPoints class. Then, inside its header file, type in the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
/** Generates items in a cone and places them in the environemtn */ virtual void GenerateItems(FEnvQueryInstance& QueryInstance) const override; /* The distance between each point of the same Angle */ UPROPERTY(EditAnywhere, Category = ConeProperties) float PointsDistance = 50.f; /* The maximum degrees of the generated cone */ UPROPERTY(EditAnywhere, Category = ConeProperties) float ConeDegrees = 20.f; /* Angle Step is the step that the angles increase. A small value means that more item will get generated */ UPROPERTY(EditAnywhere, Category = ConeProperties) float AngleStep; /* The radius of our cone */ UPROPERTY(EditAnywhere, Category = ConeProperties) float ConeRadius = 150.f; |
Then, inside the source file, implement the following logic for the GenerateItems function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
void UEnvQueryGenerator_Cone::GenerateItems(FEnvQueryInstance& QueryInstance) const { //This array will hold a reference to all the generated items, meaning, the cone items TArray<FNavLocation> ItemCandidates; //Get a reference for our AI Pawn AActor* AIPawn = Cast<AActor>((QueryInstance.Owner).Get()); //Store its location and its forward vector FVector PawnLocation = AIPawn->GetActorLocation(); FVector PawnForwardVector = AIPawn->GetActorForwardVector(); //If the angle step is zero we're going into an infinite loop. //Since we don't want that, don't execute the following logic if (AngleStep == 0) return; for (float Angle = -ConeDegrees; Angle < ConeDegrees; Angle += AngleStep) { //Start from the left side of the pawn and rotate its forward vector by Angle + 1 FVector LeftVector = PawnForwardVector.RotateAngleAxis(Angle + 1, FVector(0, 0, 1)); //The Left Vector is showing a straight line for that angle. The only thing we need //is to generate items in that line //Generates all the points for the current line (LeftVector) for (int32 Point = 0; Point < ConeRadius; Point++) { //Generate a point for this particular angle and distance FNavLocation NavLoc = FNavLocation(PawnLocation + LeftVector * Point * PointsDistance); //Add the new point into our array ItemCandidates.Add(NavLoc); } } //Projects all the nav points into our Viewport and removes those outside of our navmesh ProjectAndFilterNavPoints(ItemCandidates, QueryInstance); //Store the generated points as the result of our Query StoreNavPoints(ItemCandidates, QueryInstance); } |
Notice that we didn’t call the Super::GenerateItems function. This is because this particular function is designed to be overriden completely. The default implementation should never be called, since it’s designed to throw an error.
Save and compile your code. Then:
- Add a Nav mesh into your map
- Create a Blueprint Pawn that inherits the EQSTestingPawn class
- Place the EQSTestingPawn in your map
Then, create an Environment Query and call your C++ generator like the following screenshot suggests:
When you’re done with that, assign the created EQS (ConeEQS in my case) to your placed EQSTesting Pawn:
Moreover, you should disable the Re Run Query Only on Finished Move for a more smooth display of your EQS while moving your Pawn.
At this point you have created your own generator! Temper with the values we exposed to the editor to have different results!
If you’ve made it this far, thank you!
Hi Orfeas,
thank you for another great tutorial. I am actually trying to create a generator that finds all weapons that are not already held by someone else. How would I return the weapon found to the behavior tree from the generator? I would basically need a alternative to StoreNavPoints, which you are using.
This is what it currently looks like. It causes an error at QueryInstance.AddItemData(*ActorItr);
void UFindUnequippedFirearmGenerator::GenerateItems(FEnvQueryInstance& QueryInstance) const
{
AActor* aiPawn = Cast((QueryInstance.Owner).Get());
for (TActorIterator ActorItr(GetWorld()); ActorItr; ++ActorItr)
{
if ((ActorItr->GetActorLocation() – aiPawn->GetActorLocation()).Size() <= _range)
{
AFirearm *desiredFirearm = *ActorItr;
QueryInstance.AddItemData(*ActorItr);
return;
}
}
}
Hello Kai,
EQS Generators are used in order to “ask” the environment various queries. From your description I think that it would be best to create your own Behavior Tree Task instead of an EQS Generator.
-Orfeas
Hey Orfeas,
thanks for the reply. Sorry it took me so long to get back. I want to ask the environment where all the unused firearms within a certain area are. Wouldn’t that exactly be what the eqs is for? I am already using it to help the AI find cover, but in the case of the firearms I have one boolean condition, which is: not picked up.