Enemy AI With Behavior Trees In Unreal Engine

Table of Contents

Enemy AI With Behavior Trees In Unreal Engine

Reading Time: 23 minutes
Level: Intermediate – Advanced
Version: Unreal Engine (Any Version)

Help Others Learn Game Development

Share on facebook
Share on twitter
Share on reddit
Share on linkedin

In my previous post about enemy AI we created enemy patrol, chase and attack behavior using Blueprints and C++.

In this post we are going to do the same but we are going to use behavior trees and sensing.

Download Assets And Complete Project For This Tutorial

To follow along with this tutorial, please download the started project by clicking on the green Download assets button above.

In the downloaded folder you will find the finished project, and the started project which I prepared for you to follow this tutorial.

Important Information Before We Start

This tutorial is meant for intermediate and advanced Unreal Engine developers. If you are a complete beginner then I recommend that you first go through my C++ tutorial series, then go through my introduction to Unreal Engine tutorial series and after that go through this tutorial.
 

Blackboards And Behavior Tress

Blackboards and Behavior Trees are two main AI frameworks structures in Unreal Engine. Blackboards are used to store data for the Behavior Tree and the Behavior Tree acts like a brain of the AI.
 
Combined together, these two components allow us to create advanced AI behavior in Unreal Engine faster and more efficient than in any other way.
 
Let’s start by creating the components that we need. Open the started project that is provided in this tutorial and then inside the Content -> Blueprints folder, Right Click -> New Folder and name it AI.
 
Inside the AI folder Right Click -> Artificial Intelligence -> Blackboard:
 
Img-1-3.jpg

Name the Blackboard BB_EnemyAIData and then Right Click -> Artificial Intelligence -> Behavior Tree:

Img-2-3.jpg

Double click BB_EnemyAIData to open the Blackboard in the editor. You will see two tabs in side the Blackboard editor, one named Blackboard and the other Blackboard Details:

Img-3-3.jpg

In the Blackboard tab we will see all the keys that we defined which represents the data of the Blackboard, and in the Blackboard Details tab we will see details about specific key we select.

In the Blackboard tab you will see a New Key blackboard icon, this is where we create new keys for the Blackboard.

Click on the New Key icon and create two new keys, one will be a bool named Can See Player, and the other will be a Vector named Random Patrol Location:

Img-4-3.jpg

Now select the SelfActor variable and name it Player Target. Make sure that the Key Type is set to Object in the Blackboard Details tab and that the Base Class is set to Actor:

Img-5-3.jpg

These are the variables that we will use to shape the enemy AI behavior which will happen in the BT_EnemyAI Behavior Tree.

Creating Enemy AI In The Behavior Tree

The first thing we need to do is open the BT_EnemyAI, click on the Root node, and in the Details tab for the Blackboard Asset select the BB_EnemyAIData:

Img-6-3.jpg

This will allow the Behavior Tree to use the data we defined in the BB_EnemyAIData Blackboard.

Now Left Click and drag from the Root node, when you release a pop up window will appear and you are going to click on Selector:

Img-7-3.jpg

You can also Right Click anywhere in the editor for the Behavior Tree and click on the Selector:

Img-8-3.jpg

The Selector node executes its children from left to right, and it will stop executing its children when one of their children succeeds. This basically means that the Selector will execute the task nodes we provide, and when that task finishes its execution, the Selector node will start executing again from start.

Let’s create a Sequence node and make the Selector node point to it:

Img-9-3.jpg
The Sequence node also executes its children from left to right and stops when one of their children succeeds. From this Sequence node, we are going to calculate a random position in space, and then make the AI move towards that position.
 
To do this, first we need to create a Task which can be done in one of two ways. The first way is to click on the New Task button in the BT_EnemyAI:
 
Img-10-3.jpg

Another way is to Right Click -> Blueprint Class, and in the search bar for All Classes inherit BTTask_BlueprintBase:

Img-11-3.jpg

Before we create a new Task, in the AI folder, Right Click -> New Folder and name it Tasks. Now create a new Task and name it Task_GetRandomLocationPoint then open it in the editor.

Inside the My Blueprint tab under Variables, create a new variable of type Blackboard Key Selector Structure, name it Random Location Key and also make it a public variable:

Img-12-3.jpg

In order to access the Keys we defined in BB_EnemyAIData Blackboard we need to provide the Blackboard Key Selector Structure as the parameter.

Next, we are going to override a function called Receive Execute AI:

Img-13-3.jpg
This function will provide us access to the controlled pawn e.g. the pawn that will be controlled by the AI, so that we can access its position and from it generate a random point:
 
Img-14-3.jpg
You can copy the nodes from here:
 

We need to call Set Blackboard Value as Vector because the Random Patrol Location is a Vector type variable. We also need to call the Finish Execute node to indicate that the Task has finished executing, otherwise the Task will only execute once.

Compile and save the changes we made to Task_GetRandomLocationPoint and open BT_EnemyAI editor. We can either Right Click and under Tasks search for the Task_GetRandomLocationPoint or drag a node from the Sequence node and select the Task_GetRandomLocationPoint:

Img-15-3.jpg

Now select the new task node, and in the Details tab for the Random Location Key, click on the drop down list and select Random Patrol Location variable:

Img-16-Fixed.jpg

This is how we are going to indicate that the Random Location Key variable we defined in the Task_GetRandomLocationPoint is pointing to the Random Patrol Location variable we defined in BB_EnemyAIData Blackboard.

The last step is to call the Move To Task by Right Click -> Tasks -> Move To:

Img-17-3.jpg

You can also drag a node from the Sequence, but make sure that the Move To task is positioned after the Task_GetRandomLocationPoint because if you remember, the Sequence node will execute its children e.g. tasks, from left to right, so we first need to generate a random location point, and then make the AI move to that location.

The order of execution is also denoted with numbers on each node, the lower the number means the node will be executed first:

Img-18-3.jpg

The last step is to select the Move To node, and in the Details tab for the Blackboard Key select the Random Patrol Location:

Img-19-3.jpg


Running Behavior Tree From AI Controller

To make the Behavior Tree control our enemy character, we need to create an AI Controller Blueprint. Inside the Content -> Blueprint -> AI, Right Click -> New Blueprint Class and inherit from AIController and name the new Blueprint BP_BB_EnemyAIController:

Img-20-3.jpg

Before we run the Behavior Tree from the AIController Blueprint, open the BP_Enemy_BT located in Content -> Blueprints.

In the Components tab Select the top parent BP_Enemy_BT(self), and in the Details tab under Pawn for the AI Controller Class select BP_BB_EnemyAIController:

Img-21-3.jpg

Now open the BP_BB_EnemyAIController Blueprint in the editor. Create a new variable of type Behavior Tree and name it AI Behavior Tree:

Img-22-3.jpg

Next, select the AI Behavior Tree variable, and in the Details tab under Default Value select the BT_EnemyAI:

Img-23-3.jpg

To make the Behavior Tree run, we need to add the following nodes:

Img-24-3.jpg

You can copy the nodes from here:

Now let’s run the game and test it out:

Because the BP_BB_EnemyAIController is now controlling the enemy, as soon as the game started and the BeginPlay inside BP_BB_EnemyAIController was executed which has the code to run the Behavior Tree we saw that the enemy started patrolling the level.

We also saw in the BT_EnemyAI the execution flow of the nodes. You can always open the Behavior Tree Blueprint and watch the execution flow for debugging purposes to see if everything works.

Pawn Sensing Component

In the previous post about enemy AI we used collision components to detect the presence of the player, in this post we are going to use PawnSensing component which allows our AI to have human senses like hearing and seeing.

One tip when it comes to setting up the AI system, if you have an AIController like we do in this example, you can attach the PawnSensing component on the AIController.

That way, every enemy in your game that uses that AI controller will have the sensing ability, which is much better than to go in every enemy blueprint and attach a PawnSensing component.

So let’s open BP_BB_EnemyAIController and in the Components tab click on Add Component button and attach the PawnSensing component:

Img-25-3.jpg

Open the Viewport tab and select the PawnSensing component in the Components tab, this what you will see:

Img-26-3.jpg

In the Details tab we have the settings for the PawnSensing component, specifically the AI settings which is what we are interested in:

Img-27-3.jpg

We are going to use sight sensing to make the enemy see the player in the game. For that we have the Sight Radius settings, which is the radius of the enemy’s sight. We also have the Peripheral Vision Angle which is the view angle of the enemy.

We are going to change the Peripheral Vision Angle to 60:

Img-28-3.jpg

You will notice that this has also change the shape of the PawnSinsing component in the Viewport tab:

Img-29-3.jpg
I am also going to change the Sensing Interval to 0.2:
 
Img-30-3.jpg

The Sensing Interval implies how often the system will update the senses, setting the value at 0.2 means every 0.2 seconds the system will update the senses and make the AI “see” in the game.

These are the settings that I am going to use for our example, you are, of course, free to experiment with all the values and see the outcome of the changes you made.

You can make the enemy detect the player on large distances or you can make the enemy detect the player only when he is close enough to the enemy, so be my guest and experiment with different options which is the best way to learn.

Next, I am going to select the PawnSensing component and in the Details tab scroll all the way down until you see the Events settings. Click on the + button for the On See Pawn:

Img-31-3.jpg

This will create the On See Pawn node in the Event Graph which is called every time the PawnSensing component sees an Actor in the game, and we can use this node to test if the Actor that the PawnSensing component sees is the player character, then we can make the enemy chase the player.

Sensing The Presence Of The Player

Before we add the code that will sense the player’s presence, we are going to create a function that is going to set the values in the Blackboard Blueprint when the player is visible or not.
 
Inside the BP_BB_EnemyAIController create a new function and name it SetCanSeePlayer:
 
Img-32-3.jpg

This function is going to have two parameters, a bool parameter called Can See Player, and an object parameter called Player Object. We will use these two parameters to denote if we see the player, and if we do, we will also pass a reference to the player actor:

Img-33-3.jpg

You can copy the nodes from here:

Now that we have the CanSeePlayer function, we can go back in the Event Graph tab and inside the On See Pawn event, the first thing we will do is test if the Pawn the enemy sees, is the player:

Img-34-3.jpg

Since the project I am using is the Third Person template project, the player character uses the ThirdPersonCharacter Blueprint, that is why we are casting the Pawn parameter from the On See Pawn event to the ThirdPersonCharacter.

If the cast succeeds, that means the enemy can see player, so we call the SetCanSeePlayer function passing true for the Can See Player parameter and passing ThirdPersonCharacter as the Player Object parameter.

If you remember, we set the Sensing Interval for the PawnSensing component to 0.2, meaning every 0.2 seconds the PawnSensing component will be updated.

This is important because currently we only have the logic to make the enemy see the player, but what if the player escapes the enemy’s sight? In that case we need a logic that will inform us that now the enemy doesn’t see the player anymore.

For that I am going to use a function called Retriggerable Delay:

Img-35-3.jpg

You can copy the nodes from here:

The Retriggerable Delay function has the same functionality as the Delay function, it will wait for the specified duration and after that it will continue executing.

But the difference is, when you call the Retriggerable Delay function while it is counting down, it will reset the countdown and start from scratch.

For that reason, for the Duration parameter I used the Sensing Interval value of the PawnSensing component, and I have multiplied it by 2, which means when the PawnSensing component sees the player, it will call the Retriggerable Delay function which will start its countdown.

In the meantime, the PawnSensing component will continue to run, and if it sees the player again, it will call the Retriggerable Delay function which will reset its countdown, and since we set the Duration of the delay to be 2 times the Sensing Interval, that means if the PawnSensing component doesn’t see the player for 0.4 seconds, then the Retriggerable Delay function will go through and it will call the SetCanSeePlayer to inform us that the enemy doesn’t see the player anymore.

So now that the enemy can see the player, we need to go in the BT_EnemyAI and add another sequence that will call the Move To Task and make the AI move to the Player Target:

Img-36-3.jpg

As you can see I’ve set the Sequence that will make the AI chase the player on the left side, because I want that task to be executed first. When that task is finished then the tree will move to the second Sequence.

Don’t forget to select the Move To node and for the Blackboard Key set the Player Target:

Img-37-3.jpg

We are still not finished because the current set up of the tree doesn’t have any conditions that need to be checked before we perform each task. For example, if we don’t have a reference to the player, we can’t make the AI move to the player. For that we are going to add a decorator to the Sequence.

To do that, simply Right Click on the Sequence and scroll down where it says Decorator:

Img-38-3.jpg
The Decorator will act like a condition that needs to be met before the Sequence can run, and as you can see we have plenty of options to chose for the condition. We are going to select Blackboard condition:
 
Img-39-3.jpg
When you do that, you will see a blue Blackboard Based Condition icon on the Sequence node:
 
Img-40-3.jpg

When you click on the Blackboard Based Condition in the Details tab you will see the Flow Control and Blackboard settings which is where you set the conditions for this Blackboard.

To make the AI move to the player, we need to make sure that the enemy can see the player, which means the Can See Player value needs to be set:

Img-41-3.jpg

For the Key Query in the Blackboard setting select the Is Set, and for the Blackboard Key select Can See Player, which means Can See Player value needs to be set to true for this condition to evaluate.

I’ve also set the Observer aborts to Both in the Flow Control setting, which means that when the result changes, which is set in the Notify Observer setting for the Flow Control, then the tree will abort this Sequence(node), all of its children and any nodes to the right of this node(Sequence).

For the Sequence that makes the enemy patrol, we are also going to add a Blackboard Decorator condition, and we are going to set the following settings for it:

Img-42-3.jpg
The condition for this Decorator is when the Can See Player value is not set, which you can see in the Blackboard setting. And in the Flow Control we set the Observer aborts to Self, which means when the value of Can See Player changes, the tree will abort this Sequence(node) and all of its children.
 
Let’s run the game and test it out:
 

As soon as the enemy sees the player it starts moving towards him, and when it gets close to the player it stops moving. You can select the Move To node and in the Details tab change the Acceptable Radius to a higher value, this will make the enemy stop when the distance to player is equal to the value that you set:

Img-43-3.jpg

We also saw the execution flow of the Behavior Tree when the enemy was chasing the player and when the enemy was patrolling the level.

You can always play with the AI settings for the PawnSensing component to make the enemy detect the player when he is far away or when he is close and so on.

Attacking The Player

The last step is to make the enemy attack the player. To make this work we need to create a new Task, name it Task_Attack and open it in the editor.

In the Task_Attack we are going to override the Receive Execute AI event:

Img-44-3.jpg

From the Receive Execute AI event we are going to get a reference to BP_Enemy_BT and play the attack montage animation:

Img-45-3.jpg
You can copy the nodes from here:
 
The attack animation is already prepared just select it from the drop down list for the Play Montage node:
 
Img-46-3.jpg

Same as for the Task_GetRandomPointLocation, for the attack task we also need to call Finish Execute function to inform the tree that the task has finished executing.

One thing that you will notice is that we are calling the Finish Execute function from the On Complete and On Interrupted events for the Play Montage function:

Img-47-2.jpg

The On Completed part is clear, when the animation finishes playing, then we will call Finish Execute. As for the On Interrupted, it will be called when the animation is interrupted in any way and didn’t finish playing.

The reason why I am using this is because when the enemy tries to attack the player, the player can run away from the enemy, if that happens I want the enemy to abort the attack and continue chasing the player.

Before we can see that in action, we need to call the Task_Attack in the Behavior Tree:

Img-48-2.jpg

With that node, this is the final version of our Behavior Tree:

Img-49-2.jpg

Before we can test this out, we need to do one more thing. Open the BP_BB_EnemyAIController, and in the My Blueprint tab create a new variable and make it type of BP_Enemy_BT:

Img-50-2.jpg
In the BeginPlay after we start the Behavior Tree, we are going to get a reference to the BP_Enemy_BT:
 
Img-51-2.jpg

You can copy the nodes from here:

Next, in the On See Pawn event, after we call the Retriggerable Delay and the Set Can See Player function, we are going to use the Enemy BT REF to stop the attack animation:
 
Img-52-2.jpg

You can copy the nodes from here:

We already know when the Retriggerable Delay function is called that means the enemy doesn’t see the player, and when that happens we are also going to abort the attack animation because the player has escaped the enemy and we need to chase him again.

When we abort the animation, this will also stop the Task_Attack because we are calling Finish Execute when the animation is interrupted in any way:

Img-53-2.jpg

Let’s test the game out and see the final outcome:

Now when the enemy reaches the player it attacks him, and when the player escapes the enemy in the middle of the attack the enemy stops the attack and starts patrolling.

Of course, you can add additional logic to test if the player is in a certain distance away from the enemy so that the enemy will start chasing him instead of patrolling right away when the player is not in the enemy’s sight anymore which will make a more realistic enemy AI.

C++ Enemy AI Controller

Let us now create the C++ version of our Behavior Tree which starts with creating AI controller class. Inside the C++ Classes -> Enemy_AI folder, Right Click -> New C++ Class and make sure that it inherits from AIController:

Img-53-2.jpg

Name the class BTAIController and click Create Class button. Before we proceed to code the AI behavior, we need to add public dependency module names so that we can use things like tasks, AI and navigation system in our code.

Open the Enemy_AI.Build.cs file:

Img-54.jpg

Inside the file, this is how your PublicDependencyModuleNames should look like:

				
					PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", 
			"Engine", "InputCore", "HeadMountedDisplay", 
			"GameplayTasks", "AIModule", "NavigationSystem" });
				
			

You can simply copy and paste this line of code instead of the same line you have in your project.

Now that we have that out of the way, inside the BTAIController.h file, add the following lines of code:

				
					public:
	void BeginPlay() override;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
		class UBehaviorTree* BehaviorTree;
				
			

If you remember, inside the BP_BB_EnemyAIController we got a reference to the Behavior Tree and then we run the Behavior Tree when the game starts, we are going to do the same thing except with C++.

That is why we have the UBehaviorTree variable on line 5 in the code above. Now open BTAIController.cpp file and first we are going to add includes we need at the top of the file:

				
					#include "BehaviorTree/BehaviorTree.h"
#include "Perception/PawnSensingComponent.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Components/SkeletalMeshComponent.h"
#include "GameFramework/Character.h"
#include "Enemy_AICharacter.h"
				
			

Instead of going back and fort adding new includes whenever we need them, I am going to add all the includes we will need for our logic now.

Run the Behavior Tree, we only need to call one line of code which we will do in the BeginPlay function:

				
					void ABTAIController::BeginPlay()
{
    Super::BeginPlay();

    RunBehaviorTree(BehaviorTree);
}
				
			

Make sure that you compile the code by going under Build -> Build solution or pressing CTRL + SHIFT + B in Visual Studio, or pressing the Compile button in Unreal Engine editor.

C++ Behavior Tree

To run the Behavior Tree we need to create a Blueprint out of our BTAIController. Inside the Content -> Blueprints -> AI, Right Click -> Blueprint Class, make sure it inherits from BTAIController and name it BP_BB_EnemyAIController_CPP:

Img-55.jpg

Now open BP_Enemy_BT Blueprint located in Content -> Blueprints folder and in the Components tab, select the BP_Enemy_BT(self), then in the Details tab under Pawn settings set the AI Controller Class to the new BP_BB_EnemyAIController_CPP Blueprint that we just created:

Img-56.jpg

Next, we are going to create a new Behavior Tree that we are going to use with the C++ version of this tutorial. We are not going to create a C++ class for the Behavior Tree because there is no need to do that, instead we are going to create a new Behavior Tree by Right Click -> Artificial Intelligence -> Behavior Tree:

Img-57.jpg

Name the new Behavior Tree BT_EnemyAI_CPP and then open it in the editor. For the Blackboard we are going to reuse the same Blackboard we used in the first part of this tutorial:

Img-58.jpg
I am also going to reuse the random patrol logic that we created in the first example:
 
Img-59.jpg

This is only to test if our code for running the Behavior Tree works, after that we will create our own random patrol logic using C++.

Just make sure that you select the correct Blackboard key for both GetRandomLocationPoint and Move To task which is Random Patrol Location.

Open BP_BB_EnemyAIController_CPP in the editor and in the Components tab select BP_BB_EnemyAIController_CPP(self), then in the Details tab for the Behavior Tree select BT_EnemyAI_CPP:

Img-60.jpg

Compile and save the changes to the BP_BB_EnemyAIController_CPP Blueprint and now let’s run the game to test it out:


Creating Behavior Tasks With C++

Now that we see that our initial set up is working, let us create our own task using C++ which is going to generate a random location point.

Inside the C++ Classes -> Enemy_AI folder, Right Click -> New C++ Class. In the pop up window search for the BTTaskNode and inherit from it. Name the new class Task_GetRandomLocation_CPP and create the class:

Img-61.jpg

Open the Task_GetRandomLocation_CPP.h file and add the following lines of code:

				
					private:

	class UNavigationSystemV1* NavArea;

	FVector RandomLocation;

	virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp,
		uint8* NodeMemory) override;
				
			

To generate a random location, we need to have a reference to the navigation system, that is why we need the variable on line 3. The function on line 7, is the override function which will inform the task that it needs to execute and it will be called automatically by the Behavior Tree.

Now open the Task_GetRandomLocation_CPP.cpp file, and first we are going to add all includes we need:

				
					#include "Kismet/GameplayStatics.h"
#include "NavigationSystem.h"
#include "BehaviorTree/BlackboardComponent.h"
				
			
And in the ExecuteTask function we are going to get a random location and inform the Blackboard component about the random location we generated:
 
				
					EBTNodeResult::Type UTask_GetRandomLocation_CPP::
	ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
	NavArea = FNavigationSystem::
		GetCurrent<UNavigationSystemV1>(UGameplayStatics::GetPlayerPawn(GetWorld(), 0));

	if (NavArea)
	{
		NavArea->K2_GetRandomReachablePointInRadius(GetWorld(),
			GetWorld()->GetFirstPlayerController()->GetPawn()->GetActorLocation(),
			RandomLocation, 15000.0f);
	}
	else
	{
		return EBTNodeResult::Failed;
	}

	// inform blackboard about random location result
	OwnerComp.GetBlackboardComponent()
		->SetValueAsVector(FName("Random Patrol Location"), RandomLocation);

	return EBTNodeResult::Succeeded;
}
				
			
First we get a reference to the navigation system on line 4. Then we use the navigation system to generate a random location in the radius of 15000 units from the player pawn on line 9.
 
What’s important to note is that we need to return the result outcome for the ExecuteTask function. That is why we are testing if we have a reference to the navigation system on line 7, and if we don’t have a reference to it we will return Failed as the task outcome on line 15.
 
But if we have a reference then we will generate a random location, then we will inform the Blackboard component about the random location we generated on line 19, and lastly we will return Succeeded as the task outcome on line 22.
 
Not that we are calling SetValueAsVector and we pass the name of the Blackboard Key parameter using FName. This is something that we need to specify so that the Blackboard component will know which value to set.
 
Now we can open BT_EnemyAI_CPP in the editor, and use our custom task:
 
Img-62.jpg

This will replace the task node we created using Blueprints and it will use the C++ task node we just created.

Let’s run the game and test it out:

We will not notice any difference in the behavior of the enemy in comparison to the previous example, but now we see that the Behavior Tree is using the task node we created via C++ instead of the Blueprint one.
 

Creating PawnSensing Component With C++

The next step is to sense or detect the player actor in the game and we are going to do that with the help of PawnSensing component.
 
Open BTAIController.h file and add the following lines below the UBehaviorTree variable declaration:
 
				
					ABTAIController();

	UPROPERTY(EditAnywhere)
	class UPawnSensingComponent* PawnSensing;

	UFUNCTION()
		void OnSeePawn(APawn* PlayerPawn);

	UFUNCTION()
	void SetCanSeePlayer(bool SeePlayer, class UObject* Player);

	FTimerHandle RetriggerableTimerHandle;
	FTimerDelegate FunctionDelegate;
	void RunRetriggerableTimer();
				
			

Now open BTAIController.cpp file and inside the constructor we are going to create the PawnSensing component:

				
					ABTAIController::ABTAIController()
{
    PawnSensing = CreateDefaultSubobject<UPawnSensingComponent>(TEXT("PawnSensing"));   
}
				
			
Inside the BeginPlay we are going to bind the OnSeePawn function to the OnSeePawn event of the PawnSensing component:
 
				
					void ABTAIController::BeginPlay()
{
    Super::BeginPlay();

    PawnSensing->OnSeePawn.AddDynamic(this, &ABTAIController::OnSeePawn);

    RunBehaviorTree(BehaviorTree);
}
				
			

Inside the OnSeePawn function we are going to test if the pawn that the PawnSensing component sees is the player, if that is the case we will inform the Behavior Tree that we see the player and we will pass the player’s reference to the Behavior Tree:

				
					void ABTAIController::OnSeePawn(APawn* PlayerPawn)
{
    AEnemy_AICharacter* Player = Cast<AEnemy_AICharacter>(PlayerPawn);

    if (Player)
    {
        SetCanSeePlayer(true, Player);
        RunRetriggerableTimer();
    }
}
				
			
The SetCanSeePlayer function contains the code that will inform the Behavior Tree, or better yet, the Blackboard component, that the enemy sees the player:
 
				
					void ABTAIController::SetCanSeePlayer(bool SeePlayer, UObject* Player)
{
    if (SeePlayer)
    {
        GetBlackboardComponent()
            ->SetValueAsBool(FName("Can See Player"), SeePlayer);

        GetBlackboardComponent()
            ->SetValueAsObject(FName("Player Target"), Player);
    }
    else
    {
        GetBlackboardComponent()
            ->SetValueAsBool(FName("Can See Player"), SeePlayer);

        ACharacter* EnemyChar = Cast<ACharacter>(GetPawn());
        EnemyChar->GetMesh()->GetAnimInstance()->StopAllMontages(0);
    }
}
				
			
When the SeePlayer parameter is true, we will pass that parameter, or the value of that parameter to the Blackboard component using FName(“Can See Player”) which refers to the same Blackboard key inside the Blackboard component.
 
We do the same thing with FName(“Player Target”) when we pass a reference to the player pawn.
 
If you remember in the Behavior Tree Blueprint version, we are using Can See Player as the parameter for the Blackboard decorator, which servers as the condition for the Sequence, and we use the Player Target as the parameter for the Move To node which will make the enemy move towards the player pawn:
 
Img-63.jpg

In the code example above, we are doing the same thing, except using C++.

And when the SeePlayer parameter is false, then we only pass that value to the Blackboard component without the player pawn, because if the enemy can’t see the player, it will not run towards him and it will not attack him, thus, we don’t need to pass player pawn to the Blackboard component.

We also need to stop the enemy from attacking the player if the attack is currently under way, and we do that on lines 16 and 17 in the code example above.

First we get a reference to the enemy actor, then we stop his montage animations that are currently playing by calling StopAllMontages(0) same as what we did with the Blueprint version of the Behavior Tree.

The 0 parameter in the StopAllMontages functions will make the enemy animation Blueprint stop the attack animation and it will play another animation based on the condition we set for the animations.

The reason why I am mentioning this is if you set 1 as the parameter, then the enemy will finish the attack animation and then play other animations which is not the behavior we want.

Lastly, we need to code the RunRetriggeranbleTimer function, so add the following lines of code:

				
					void ABTAIController::RunRetriggerableTimer()
{
    GetWorld()->GetTimerManager().ClearTimer(RetriggerableTimerHandle);

    FunctionDelegate.BindUFunction(this, FName("SetCanSeePlayer"),
        false, GetPawn());

    GetWorld()->GetTimerManager().SetTimer(RetriggerableTimerHandle,
        FunctionDelegate, PawnSensing->SensingInterval * 2.0f, false);
}
				
			
Inside the OnSeePawn function we are calling the RunRetriggerableTimer function. With this function we are building the same set up we created in BP_BB_EnemyAIController Blueprint version:
 
Img-64.jpg
Essentially, every time we call the RunRetriggeranbleTimer is called, it will reset its countdown which we are doing on line 3 in the code above. Then it will use the FunctionDelegate variable and its BindUFunction to pass the SetCanSeePlayer function and the two parameters.
 
Since we are passing false as the parameter meaning the enemy can’t see the player, I am using GetPawn function as the second parameter.
 
Then we call the timer manager to set the timer. We are using SensingInterval * 2 as the parameter for the wait time, because we are wait twice as long it takes the PawnSensing component to refresh itself, which means if the PawnSensing component doesn’t see the player after SensingInterval / 2 seconds, then the timer will execute.
 
Before we proceed, I am going to leave the BTAIController.h and .cpp file below as a reference, in case you want to copy paste the code or compare it to your own.
 
BTAIController.h:
 
				
					#include "CoreMinimal.h"
#include "AIController.h"
#include "BTAIController.generated.h"

/**
 * 
 */
UCLASS()
class ENEMY_AI_API ABTAIController : public AAIController
{
	GENERATED_BODY()

public:
	void BeginPlay() override;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
		class UBehaviorTree* BehaviorTree;
	
	ABTAIController();

	UPROPERTY(EditAnywhere)
		class UPawnSensingComponent* PawnSensing;

	UFUNCTION()
		void OnSeePawn(APawn* PlayerPawn);

	UFUNCTION()
		void SetCanSeePlayer(bool SeePlayer, class UObject* Player);

	FTimerHandle RetriggerableTimerHandle;
	FTimerDelegate FunctionDelegate;
	void RunRetriggerableTimer();

};
				
			

BTAIController.cpp:

				
					#include "BTAIController.h"

#include "BehaviorTree/BehaviorTree.h"
#include "Perception/PawnSensingComponent.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Components/SkeletalMeshComponent.h"
#include "GameFramework/Character.h"
#include "Enemy_AICharacter.h"

ABTAIController::ABTAIController()
{
    PawnSensing = CreateDefaultSubobject<UPawnSensingComponent>(TEXT("PawnSensing"));
}


void ABTAIController::BeginPlay()
{
    Super::BeginPlay();

    PawnSensing->OnSeePawn.AddDynamic(this, &ABTAIController::OnSeePawn);

    RunBehaviorTree(BehaviorTree);
}

void ABTAIController::OnSeePawn(APawn* PlayerPawn)
{
    AEnemy_AICharacter* Player = Cast<AEnemy_AICharacter>(PlayerPawn);

    if (Player)
    {
        SetCanSeePlayer(true, Player);
        RunRetriggerableTimer();
    }
}

void ABTAIController::SetCanSeePlayer(bool SeePlayer, UObject* Player)
{
    if (SeePlayer)
    {
        GetBlackboardComponent()
            ->SetValueAsBool(FName("Can See Player"), SeePlayer);

        GetBlackboardComponent()
            ->SetValueAsObject(FName("Player Target"), Player);
    }
    else
    {
        GetBlackboardComponent()
            ->SetValueAsBool(FName("Can See Player"), SeePlayer);

        ACharacter* EnemyChar = Cast<ACharacter>(GetPawn());
        EnemyChar->GetMesh()->GetAnimInstance()->StopAllMontages(0);
    }
}

void ABTAIController::RunRetriggerableTimer()
{
    GetWorld()->GetTimerManager().ClearTimer(RetriggerableTimerHandle);

    FunctionDelegate.BindUFunction(this, FName("SetCanSeePlayer"),
        false, GetPawn());

    GetWorld()->GetTimerManager().SetTimer(RetriggerableTimerHandle,
        FunctionDelegate, PawnSensing->SensingInterval * 2.0f, false);
}
				
			

Make sure that you compile the code and then open BP_BB_EnemyAIController_CPP in the editor. You will see the Pawn Sensing component under the Components tab, select it and in the Details tab change the Sensing Inverval value to 0.2 and for the Peripheral Vision Angle set the value 60:

Img-65.jpg

We already explained what these options mean when we edited the Pawn Sensing component in the Blueprint example.

Now open the BT_EnemyAI_CPP in the editor, and the last setup is pretty much the same as the one we had in the Blueprint version:

Img-66.jpg

On the left side we have a Sequence node that will make the enemy move towards the player and attack him. The sequence has a Decorator which has the Can See Player as the condition with the Key Query Is Set, and it aborts both nodes when the result changes:

Img-67.jpg

We already tested the Sequence node on the right which makes the enemy patrol, the only thing that changed is that we added a Decorator which also has the Can See Player as the condition with Key Query Is Not Set:

Img-68.jpg

One thing that you will notice is that I have reused the Task_Attack node we created via Blueprints. The reason why I didn’t create an attack task with C++ is, it is complicated to get all the references we need such as the enemy and make him attack and so on, it is a lot easier to do this with Blueprints that is why I reused that node.

And besides, Unreal Engine is all about combining Blueprints and C++, and a lot of games are created purely in Blueprints so throw that Blueprints are not optimized mentality out of the window.

The last thing that is left for us to do is to test the game and see the outcome:

Leave a Comment

With a FREE Awesome Tuts account

you can download assets and project files, bookmark your favorite posts and more

Already have an account? Sign in