Alright, now we need a hovering and tracking AI boss character! It's time for us to dive into the UE AI layer. The UE4 AI implementation is robust and covers everything you will need to create intelligent, well-functioning AI. Much like everything else in UE4, the AI interface can be expanded and modified using C++ objects that inherit from the UE4-provided base classes. The next section of this chapter will run you through the basics of the AI system.
The AI toolset present in UE4 is made up of many different asset types and objects. Each object is responsible for a certain subset of the AI's functionality. The following list provides a brief description of the major elements that go into creating an AI in UE4:
UCharacter
base class. An AI character is simply controlled by an AIController
instead of a PlayerController
. In this sense any character can be an AI character or a player character.Move To
, for example). You may create your own AI controllers within which you can create your own movement functions to better articulate your AI during runtime. AI controllers will still use the movement properties of a character's CharacterMovementComponent
.NavMesh
for short, are a way we can identify an area for an AI to move within, which accommodates for obstacle avoidance, pathing, and patrolling. NavMesh
will handle the pathing generation for any specified AI movement agents within the area covered by a NavMesh
volume. This asset is not directly referenced by the others mentioned previously but is a core component of the AI system within UE4.During the rest of this chapter we are going to be making our own abstractions of some of these objects. Thanks to the dependence of these assets within themselves, we are able to create an efficient bi-directional network of objects. Through this network we are able to easily create any AI behavior we can conceive.
Over the course of the next two chapters we are going to be referencing and including a new series of objects and functions that are part of UE4's AI module. To ensure that we have no linking or #include
problems going forward, it is a good idea to explicitly add this module as a public dependency to our project's build.cs
. Navigate to the BossMode.Build.cs
and ensure that the public dependency modules name list matches the following:
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "AIModule" });
As you can see, we have included AIModule
to the list, this will ensure that all of the objects contained within this module are linked publically to our project. With this in place we begin to work the backend for our boss character. We will start by creating a basic AI character that is based in C++.
Much like our standard player characters, we require an object that will represent our boss character. AI characters are created in much the same manner as standard player characters, however they are not possessed by player controllers but AI controllers instead. Start by creating a new C++ class using the wizard. The base class for this new object is to be Character. Call this new object BMBossCharacter
. This abstraction of the UCharacter
base class is going to be very simple. We are going to be including a handle to a Behavior Tree asset that can be assigned from the editor.
Navigate to BMBossCharacter.h
now and simply modify the contents of the definition to match the following:
UCLASS() class BOSSMODE_API ABMBossCharacter : public ACharacter { GENERATED_BODY() public: // Sets default values for this character's properties ABMBossCharacter(); virtual float TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override; UFUNCTION(BlueprintCallable, BlueprintPure, Category = AI) const float GetHealth(); UPROPERTY(EditAnywhere, Category = Pawn) class UBehaviorTree* BehaviorTree; private: UPROPERTY() float Health; };
As you can see, we have removed the Tick()
, Begin Play(),
and SetupPlayerInputComponent()
methods as they will not be used by our AI-controlled character. Ensure you delete the corresponding methods from the BMBossCharacter.cpp
otherwise the project will not compile. We have also added some methods. As you can see, we have overridden the TakeDamage()
method. This method takes in the various parameters that make up a damage event in UE4. We will describe this in more detail later. Following this we have added a standard accessor method for the Health
of this character (a private member declared below).
The last thing to note is that we have added a handle to a UBehaviorTree
. As this is not a UBehaviorTreeComponent
we do not need to initialize the handle with anything, we will simply be assigning the correct UBehaviorTree
asset from the Details panel in editor. Now we will define these functions, so navigate to BMBossCharacter.cpp
.
Define the constructor for this object as follows: // Sets default values ABMBossCharacter::ABMBossCharacter() { // Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; Health = 100.0f; GetMovementComponent()->NavAgentProps.AgentHeight = 320.0f; GetMovementComponent()->NavAgentProps.AgentRadius = 160.0f; }
Here we are simply flagging that this character is to tick
, setting the health of this character to 100.0f
, and establishing some of the parameters for this AI-controlled character's navigation agent properties. These navigation agent properties are used by the navigation system to accurately plot pathing points when generating path-following behavior for AI characters—More on this later. Next, define the TakeDamage()
method as follows:
float ABMBossCharacter::TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) { Super::TakeDamage(Damage, DamageEvent, EventInstigator, DamageCauser); Health -= Damage; return Damage; }
This method simply subtracts the damage value parsed from the Health
property. We will be using this value later to influence the decision making of this AI character. Finally, we need to define the GetHealth()
accessor method as follows:
const float ABMBossCharacter::GetHealth() { return Health; }
Now we can create the AI controller that will drive the movement of our boss!
Much like that of the ABMBossCharacter
, this controller will be a very basic abstraction and provide us with a common place to store handles to the various AI assets we will eventually create and use for the boss AI character. Don't worry, these assets will receive their own detailed explanation shortly. Start by creating a new class using the C++ wizard. This class is to inherit from the AIController
class provided by UE4 and call it BMAIController
. Navigate to BMAIController.h
now. Modify the class definition to match the following:
UCLASS() class BOSSMODE_API ABMAIController : public AAIController { GENERATED_BODY() public: ABMAIController(); virtual void Possess(APawn* InPawn) override; UBlackboardComponent* GetBlackboard(); protected: UPROPERTY(BlueprintReadWrite, Category = Behavior) class UBlackboardComponent* BlackboardComp; UPROPERTY(BlueprintReadWrite, Category = Behavior) class UBehaviorTreeComponent* BehaviorTreeComp; };
Here we are simply providing the object with a default constructor, overriding the Possess()
method (which is part of the AAIController
interface), and declaring handles to the blackboard and behavior tree components that will be used by our AI. We have also included a public accessor method for the UBlackboardComponent
called GetBlackboard()
. It is with the creation of this object that we begin to see the network of our AI assets form. Now navigate to the BMAIController.cpp
.
Firstly, we need to update the include list of this object to accommodate the UBlackboardComponent
and UBehaviorTreeComponent
types as well as the ABMBossCharacter
type we just created. Do this by adding the following #includes
:
#include "BMBossCharacter.h" #include "BehaviorTree/BlackboardComponent.h" #include "BehaviorTree/BehaviorTree.h"
Next we are to define the constructor for this object. Within the constructor we are simply going to initialize the two components via CreateDefaultSubobject
:
ABMAIController::ABMAIController() { BlackboardComp = CreateDefaultSubobject<UBlackboardComponent> (TEXT("BlackboardComp")); check(BlackboardComp); BehaviorTreeComp = CreateDefaultSubobject <UBehaviorTreeComponent>(TEXT("BehaviorTreeComp")); check(BehaviorTreeComp); }
Following this we must define the override for the Possess()
method we declared earlier. The Possess()
method is responsible for providing control of a character to a given controller. We can also utilize this function to retrieve the blackboard and behavior tree assets from the possessed pawn. As not every pawn has a behavior tree and corresponding blackboard asset, we need to ensure that the pawn to be possessed is of type ABMBossCharacter
. We can do this with the following code:
Void ABMAIController::Possess(APawn* InPawn) { Super::Possess(InPawn); ABMBossCharacter* possessedPawn = Cast<ABMBossCharacter>(InPawn); if (InPawn != nullptr) { BlackboardComp->InitializeBlackboard(*(possessedPawn-> BehaviorTree->BlackboardAsset)); BehaviorTreeComp->StartTree(*(possessedPawn-> BehaviorTree)); }<-- closing if(InPawn != nullptr) }<-- closing ABMAIController::Possess()
This code will attempt to cast the parsed APawn
to an ABMBossCharacter
object. If the cast succeeds, the possessedPawn
pointer will be populated and the following if
statement will pass. Within the scope of this if
, check we get access to the UBehaviorTree
handle we declared in the ABMBossCharacter
earlier. With this handle we can initialize the UBlackboardComponent BlackboardComp
via passing a const
reference to the blackboard asset associated with the behavior tree to the InitailzeBlackboard
method. This will ensure that any queries made to BlackboardComp
about variable states will retrieve information from the blackboard asset assigned to the behavior tree we set in the ABMBossCharacter
.
Next, we start the behavior tree by parsing a const
reference to the behavior tree to the StartTree()
method of the BehaviorTreeComponent
. This will ensure that the tree that has been assigned to this pawn starts upon possession and the implemented AI behavior will take place.
The last thing we need to add is a definition for the GetBlackboard()
accessor method we declared earlier:
UBlackboardComponent* ABMAIController::GetBlackboard() { return BlackboardComp; }
Much like a Blueprint, a behavior tree is a grouping of connected nodes that, when fired, will perform some kind of functionality. Unlike Blueprints, we have advanced control over the flow of our behavior tree and the order in which the nodes execute. Node execution in behavior trees is dictated by the way the nodes are connected, and if the previous nodes have either succeeded or failed. Success or failure of a node is gauged by the node's ability to perform its given action. When a node fails its parent node, the previous node in the logic flow will be informed of the failure. This may result in the entire tree failing or simply a single branch of the tree failing, resulting in the logic flow of the tree to shift.
Before we go ahead and begin to make behavior trees of our own, it is important to understand the basic components that make up a behavior tree. As stated previously, there are several important pieces to behavior trees and they are described as follows:
Move To, Rotate
, and Wait
. Tasks can also be created from scratch by either creating a blueprint that inherits from the task base class, or a C++ object that does the same.As described previously, blackboards provide us with a way to create a set of values that we can write to and read from within any of the associated AI assets or objects. This is done through blackboard keys. Blackboard keys are identifiers that allow us to obtain any given value (or variable) within a Blackboard asset. For example, we can have a key saved in our blackboard called HomeLocation
that is of type vector. We can now retrieve this vector from our blackboard by specifying we wish to retrieve the value HomeLocation
from the blackboard asset as a vector type. The use of keys provides us with the ability to retrieve and set data types held in our blackboard from anywhere within our AI network.
3.147.54.242