In video games, Artificial Intelligence (AI) usually refers to how a non-player character makes decisions. This could be as simple as an enemy seeing the player and then attacking. It could also be something more complex such as an AI-controlled player in a real-time strategy.
In Unreal Engine, you can create AI by using behavior trees. A behavior tree is a system used to determine which behavior an AI should perform. For example, you could have a fight and a run behavior. You could create the behavior tree so that the AI will fight if it is above 50% health. If it is below 50%, it will run away.
In this tutorial, you will learn how to:
- Create an AI entity that can control a Pawn
- Create and use behavior trees and blackboards
- Use AI Perception to give the Pawn sight
- Create behaviors to make the Pawn roam and attack enemies
Download the starter project and unzip it. Navigate to the project folder and open MuffinWar.uproject.
Press Play to start the game. Left-click within the fenced area to spawn a muffin.
In this tutorial, you will create an AI that will wander around. When an enemy muffin comes into the AI’s range of vision, the AI will move to the enemy and attack it.
To create an AI character, you need three things:
- Body: This is the physical representation of the character. In this case, the muffin is the body.
- Soul: The soul is the entity controlling the character. This could be the player or an AI.
- Brain: The brain is how the AI makes decisions. You can create this in different ways such as C++ code, Blueprints or behavior trees.
Since you already have the body, all you need is a soul and brain. First, you will create a controller which will be the soul.
What is a Controller?
A controller is a non-physical actor that can possess a Pawn. Possession allows the controller to—you guessed it—control the Pawn. But what does ‘control’ mean in this context?
For a player, this means pressing a button and having the Pawn do something. The controller receives inputs from the player and then it can send the inputs to the Pawn. The controller could also handle the inputs instead and then tell the Pawn to perform an action.
In the case of AI, the Pawn can receive information from the controller or brain (depending on how you program it).
To control the muffins using AI, you need to create a special type of controller known as an AI controller.
Creating an AI Controller
Navigate to Characters\Muffin\AI and create a new Blueprint Class. Select AIController as the parent class and name it AIC_Muffin.
Next, you need to tell the muffin to use your new AI controller. Navigate to Characters\Muffin\Blueprints and open BP_Muffin.
By default, the Details panel should show the default settings for the Blueprint. If it doesn’t, click Class Defaults in the Toolbar.
Go to the Details panel and locate the Pawn section. Set AI Controller Class to AIC_Muffin. This will spawn an instance of the controller when the muffin spawns.
Since you are spawning the muffins, you also need to set Auto Possess AI to Spawned. This will make sure AIC_Muffin automatically possesses BP_Muffin when spawned.
Click Compile and then close BP_Muffin.
Now, you will create the logic that will drive the muffin’s behavior. To do this, you can use behavior trees.
Creating a Behavior Tree
Navigate to Characters\Muffin\AI and select Add New\Artificial Intelligence\Behavior Tree. Name it BT_Muffin and then open it.
The Behavior Tree Editor
The behavior tree editor contains two new panels:
- Behavior Tree: This graph is where you will create nodes to make the behavior tree
- Details: Nodes you select will display its properties here
- Blackboard: This panel will show Blackboard keys (more on this later) and their values. Will only display when the game is running.
Like Blueprints, behavior trees consist of nodes. There are four types of nodes in behavior trees. The first two are tasks and composites.
What are Tasks and Composites?
As its name implies, a task is a node that "does" something. This can be something complex such as performing a combo. It could also be something simple such as waiting.
To execute tasks, you need to use composites. A behavior tree consists of many branches (the behaviors). At the root of each branch is a composite. Different types of composites have different ways of executing their child nodes.
For example, you have the following sequence of actions:
To perform each action in a sequence, you would use a Sequence composite. This is because a Sequence executes its children from left to right. Here’s what it would look like:
If any of a Sequence’s children fail, the Sequence will stop executing.
For example, if the Pawn is unable to move to the enemy, Move To Enemy will fail. This means Rotate Towards Enemy and Attack will not execute. However, they will execute if the Pawn succeeds in moving to the enemy.
Later on, you will also learn about the Selector composite. For now, you will use a Sequence to make the Pawn move to a random location and then wait.
Moving to a Random Location
Create a Sequence and connect it to the Root.
Next, you need to move the Pawn. Create a MoveTo and connect it to Sequence. This node will move the Pawn to a specified location or actor.
Afterwards, create a Wait and connect it to Sequence. Make sure you place it to the right of MoveTo. Order is important here because the children will execute from left to right.
Congratulations, you have just created your first behavior! It will move the Pawn to a specified location and then wait for five seconds.
To move the Pawn, you need to specify a location. However, MoveTo only accepts values provided through blackboards so you will need to create one.
Creating a Blackboard
A blackboard is an asset whose sole function is to hold variables (known as keys). You can think of it as the AI’s memory.
While you are not required to use them, blackboards offer a convenient way to read and store data. It is convenient because many of the nodes in behavior trees only accept blackboard keys.
To create one, go back to the Content Browser and select Add New\Artificial Intelligence\Blackboard. Name it BB_Muffin and then open it.
The Blackboard Editor
The blackboard editor consists of two panels:
- Blackboard: This panel will display a list of your keys
- Blackboard Details: This panel will display the properties of the selected key
Now, you need to create a key that will hold the target location.
Creating the Target Location Key
Since you are storing a location in 3D space, you need to store it as a vector. Click New Key and select Vector. Name it TargetLocation.
Next, you need a way to generate a random location and store it in the blackboard. To do this, you can use the third type of behavior tree node: service.
What is a Service?
Services are like tasks in that you use them to do something. However, instead of making the Pawn perform an action, you use services to perform checks or update the blackboard.
Services are not individual nodes. Instead, they attach to tasks or composites. This results in a more organized behavior tree because you have less nodes to deal with. Here’s how it would look using a task:
Here’s how it would look using a service:
Now, it’s time to create a service that will generate a random location.
Creating a Service
Go back to BT_Muffin and click New Service.
This will create a new service and open it automatically. Name it BTService_SetRandomLocation. You’ll need to go back to the Content Browser to rename it.
The service only needs to execute when the Pawn wants to move. To do this, you need to attach it to MoveTo.
Open BT_Muffin and then right-click on MoveTo. Select Add Service\BTService Set Random Location.
Now, BTService_SetRandomLocation will activate when MoveTo activates.
Next, you need to generate a random target location.
Generating a Random Location
To know when the service activates, create an Event Receive Activation AI node. This will execute when the service’s parent (the node it’s attached to) activates.
To generate a random location, add the highlighted nodes. Make sure to set Radius to 500.
This will give you a random navigable location within 500 units of the Pawn.
If you would like to create your own NavMesh, create a Nav Mesh Bounds Volume. Scale it so that it encapsulates the area you want to be navigable.
Next, you need to store the location into the blackboard. There are two ways of specifying which key to use:
- You can specify the key by using its name in a Make Literal Name node
- You can expose a variable to the behavior tree. This will allow you to select a key from a drop-down list.
You will use the second method. Create a variable of type Blackboard Key Selector. Name it BlackboardKey and enable Instance Editable. This will allow the variable to appear when you select the service in the behavior tree.
Afterwards, create the highlighted nodes:
- Event Receive Activation AI executes when it’s parent (in this case, MoveTo) activates
- GetRandomPointInNavigableRadius returns a random navigable location within 500 units of the controlled muffin
- Set Blackboard Value as Vector sets the value of a blackboard key (provided by BlackboardKey) to the random location
Click Compile and then close BTService_SetRandomLocation.
Next, you need to tell the behavior tree to use your blackboard.
Selecting a Blackboard
Open BT_Muffin and make sure you don’t have anything selected. Go to the Details panel. Under Behavior Tree, set Blackboard Asset to BB_Muffin.
Afterwards, MoveTo and BTService_SetRandomLocation will automatically use the first blackboard key. In this case, it is TargetLocation.
Finally, you need to tell the AI controller to run the behavior tree.
Running the Behavior Tree
Open AIC_Muffin and connect a Run Behavior Tree to Event BeginPlay. Set BTAsset to BT_Muffin.
This will run BT_Muffin when AIC_Controller spawns.
Click Compile and then go back to the main editor. Press Play, spawn some muffins and watch them roam around.
That was a lot of set up but you got there! Next, you will set up the AI controller so that it can detect enemies within its range of vision. To do this, you can use AI Perception.
Setting Up AI Perception
AI Perception is a component you can add to actors. Using it, you can give senses (such as sight and hearing) to your AI.
Open AIC_Muffin and then add an AIPerception component.
Next, you need to add a sense. Since you want to detect when another muffin moves into view, you need to add a sight sense.
Select AIPerception and then go to the Details panel. Under AI Perception, add a new element to Senses Config.
Set element 0 to AI Sight config and then expand it.
There are three main settings for sight:
- Sight Radius: The maximum distance the muffin can see. Leave this at 3000.
- Lose Sight Radius: If the muffin has seen an enemy, this is how far the enemy must move away before the muffin loses sight of it. Leave this at 3500.
- Peripheral Vision Half Angle Degrees: How wide the muffin’s vision is. Set this to 45. This will give the muffin a 90 degree range of vision.
By default, AI Perception only detects enemies (actors assigned to a different team). However, actors do not have a team by default. When an actor doesn’t have a team, AI Perception considers it neutral.
As of writing, there isn’t a method to assign teams using Blueprints. Instead, you can just tell AI Perpcetion to detect neutral actors. To do this, expand Detection by Affiliation and enable Detect Neutrals.
Click Compile and then go back to the main editor. Press Play and spawn some muffins. Press the ‘ key to display the AI debug screen. Press 4 on the numpad to visualize AI Perception. When a muffin moves into view, a green sphere will appear.
Next, you will move the muffin towards an enemy. To do this, the behavior tree needs to know about the enemy. You can do this by storing a reference to the enemy in the blackboard.
Creating an Enemy Key
Open BB_Muffin and then add a key of type Object. Rename it to Enemy.
Right now, you will not be able to use Enemy in a MoveTo. This is because the key is an Object but MoveTo only accepts keys of type Vector or Actor.
To fix this, select Enemy and then expand Key Type. Set Base Class to Actor. This will allow the behavior tree to recognize Enemy as an Actor.
Close BB_Muffin. Now, you need to create a behavior to move towards an enemy.
Moving Towards An Enemy
Open BT_Muffin and then disconnect Sequence and Root. You can do this by alt-clicking the wire connecting them. Move the roam subtree aside for now.
Next, create the highlighted nodes and set their Blackboard Key to Enemy:
This will move the Pawn towards Enemy. In some cases, the Pawn will not completely face towards its target so you also use Rotate to face BB entry.
Now, you need to set Enemy when AI Perception detects another muffin.
Setting the Enemy Key
Open AIC_Muffin and then select the AIPerception component. Add an On Perception Updated event.
This event will execute whenever a sense updates. In this case, whenever the AI sees or loses sight of something. This event also provides a list of actors it currently senses.
Add the highlighted nodes. Make sure you set Make Literal Name to Enemy.
This will check if the AI already has an enemy. If it doesn’t, you need to give it one. To do this, add the highlighted nodes:
- IsValid will check if the Enemy key is set
- If it is not set, loop over all the currently perceived actors
- Cast To BP_Muffin will check if the actor is a muffin
- If it is a muffin, check if it is dead
- If IsDead returns false, set the muffin as the new Enemy and then break the loop
Click Compile and then close AIC_Muffin. Press Play and then spawn two muffins so that one is in front of the other. The muffin behind will automatically walk towards the other muffin.
Next, you will create a custom task to make the muffin perform an attack.
Creating an Attack Task
You can create a task within the Content Browser instead of the behavior tree editor. Create a new Blueprint Class and select BTTask_BlueprintBase as the parent.
Name it BTTask_Attack and then open it. Add an Event Receive Execute AI node. This node will execute when the behavior tree executes BTTask_Attack.
First, you need to make the muffin attack. BP_Muffin contains an IsAttacking variable. When set, the muffin will perform an attack. To do this, add the highlighted nodes:
If you use the task in its current state, execution will become stuck on it. This is because the behavior tree doesn’t know if the task has finished. To fix this, add a Finish Execute to the end of the chain.
Next, enable Success. Since you are using a Sequence, this will allow nodes after BTTask_Attack to execute.
This is what your graph should look like:
- Event Receive Execute AI will execute when the behavior tree runs BTTask_Attack
- Cast To BP_Muffin will check if Controlled Pawn is of type BP_Muffin
- If it is, its IsAttacking variable is set
- Finish Execute will let the behavior tree know the task has finished successfully
Click Compile and then close BTTask_Attack.
Now, you need to add BTTask_Attack to the behavior tree.
Adding Attack to the Behavior Tree
Open BT_Muffin. Afterwards, add a BTTask_Attack to the end of the Sequence
Next, add a Wait to the end of the Sequence. Set its Wait Time to 2. This will make sure the muffin doesn’t constantly attack.
Go back to the main editor and press Play. Spawn two muffins like last time. The muffin will move and rotate towards the enemy. Afterwards, it will attack and wait two seconds. It will then perform the entire sequence again if it sees another enemy.
In the final section, you will combine the attack and roam subtrees together.
Combining the Subtrees
To combine the subtrees, you can use a Selector composite. Like Sequences, they also execute from left to right. However, a Selector will stop when a child succceeds rather than fail. By using this behavior, you can make sure the behavior tree only executes one subtree.
Open BT_Muffin and then create a Selector after the Root node. Afterwards, connect the subtrees like so:
This set up will allow only one subtree to run at a time. Here is how each subtree will run:
- Attack: Selector will run the attack subtree first. If all tasks succeed, the Sequence will also succeed. The Selector will detect this and then stop executing. This will prevent the roam subtree from running.
- Roam: The selector will attempt to run the attack subtree first. If Enemy is not set, MoveTo will fail. This will cause Sequence to fail as well. Since the attack subtree failed, Selector will execute its next child which is the roam subtree.
Go back to the main editor, press Play. Spawn some muffins to test it out.
"Hang on, why doesn’t the muffin attack the other one immediately?"
In traditional behavior trees, execution starts from the root every update. This means every update, it would try the attack subtree first and then the roam subtree. This means the behavior tree can instantly change subtrees if the value of Enemy changes.
However, Unreal’s behavior trees do not work the same way. In Unreal, execution picks up from the last executed node. Since AI Perception does not sense other actors immediately, the roam subtree begins running. The behavior tree now has to wait for the roam subtree to finish before it can re-evaluate the attack subtree.
To fix this, you can use the final type of node: decorators.
Creating a Decorator
Like services, decorators attach to tasks or composites. Generally, you use decorators to perform checks. If the result is true, the decorator will also return true and vice versa. By using this, you can control if a decorator’s parent can execute.
Decorators also have the ability to abort a subtree. This means you can stop the roam subtree once Enemy is set. This will allow the muffin to attack an enemy as soon as one is detected.
To use aborts, you can use a Blackboard decorator. These simply check if a blackboard key is or isn’t set. Open BT_Muffin and then right-click on the Sequence of the attack subtree. Select Add Decorator\Blackboard. This will attach a Blackboard decorator to the Sequence.
Next, select the Blackboard decorator and go to the Details panel. Set Blackboard Key to Enemy.
This will check if Enemy is set. If it is not set, the decorator will fail and cause the Sequence to fail. This will then allow the roam subtree to run.
In order to abort the roam subtree, you need to use the Observer Aborts setting.
Using Observer Aborts
Observer aborts will abort a subtree if the selected blackboard key has changed. There are two types of aborts:
- Self: This setting will allow the attack subtree to abort itself when Enemy becomes invalid. This can occur if the Enemy dies before the attack subtree completes.
- Lower Priority: This setting will cause lower priority trees to abort when Enemy is set. Since the roam subtree is after attack, it is of lower priority.
Set Observer Aborts to Both. This will enable both abort types.
Now, the attack subtree can immediately go into roaming if it no longer has an enemy. Also, the roam subtree can immediately go into attack mode once it detects an enemy.
Here is the complete behavior tree:
Summary of attack subtree:
- Selector will run the attack subtree if Enemy is set
- If it is set, the Pawn will move and rotate towards the enemy
- Afterwards, it will perform an attack
- Finally, the Pawn will wait two seconds
Summary of roam subtree:
- Selector will run the roam subtree if the attack subtree fails. In this case, it will fail if Enemy is not set.
- BTService_SetRandomLocation will generate a random location
- The Pawn will move to the generated location
- Afterwards, it will wait for five seconds
Close BT_Muffin and then press Play. Spawn some muffins and prepare for the deadliest battle royale ever!
Where to Go From Here?
You can download the completed project here.
As you can see, it’s easy to create a simple AI character. If you want to create more advanced AI, check out the Environment Query System. This system will allow your AI to collect data about the environment and react to it.
If you want to keep learning, check out the next post in the series, where I’ll show you how to create a simple first-person shooter.