Let’s see how to implement a basic behaviour tree to make a simple guard AI!
This article is also available on Medium.
This tutorial is available either in video format or in text format – see below 🙂
Get the scripts and assets for this tutorial on my Github 🙂
Wait – what about FSMs?
In a previous tutorial, I talked about finite state machines – aka FSMs – and I discussed how this pattern can help you implement well-organised and well-structured behaviour system, as long as this behaviour isn’t too complex.
In a follow-up episode, we saw how hierarchical finite state machines can mitigate some of the downsides of the FSMs by introducing inheritance and higher-order abstract states.
But the fact is that, when you try and model a more complex behaviour, FSMs will eventually come short. Chances are that, if you work on a really advanced AI system, or just on a player system that offers more options to the player, FSMs will be too rigid a structure to be pleasant to use as developers.
Now, of course – you can always manage to cram up all of your super AI in an FSM. But trust me: it will soon be a real pain to maintain, verify and update.
What are behaviour trees?
Another common AI pattern that is way more flexible is the behaviour tree.
Here, rather than defining a finite set of states that your character can transition between, you define a tree of nodes that, altogether, create branches with various behaviours.
Each node has an execution state that determines whether it’s currently running, if it’s succeeded or if it’s failed. This state is computed by “evaluating” the node thanks to a function naturally called
Evaluate(). The state of the branches is then computed from the state of the children nodes and aggregated in various ways, depending on the parent node type.
The nodes at the very bottom that have no children are called leaves. Those leaves are going to contain the actual checks and tasks performed by our AI while the rest of the tree is the logic structure that’s going to essentially “switch” from one behaviour branch to another. In other words: the inner nodes control the flow and the leaves ultimately implement the actions.
The “flow-control” nodes in the middle define how the tree is processed; they can be of two types:
- composites: those nodes are sequencers that have a bunch of child nodes that they run in predetermined or random order, one after the other or in parallel. Usually, they act like an “and” or an “or” logical gate, for example.
- decorators: in that case, they have a single child and they may impact its processing or modify its result before returning from the branch (like reversing the failure/success state, delaying the execution, and so on)
Action nodes are more diverse but you can usually group them in two categories: the checks and the tasks. Checks are simple if-like nodes that condition whether or not this branch is valid. Tasks are more complex nodes that perform a real update of the character or its environment.
Why use behaviour trees?
Something really nice with behaviour trees is that prioritising actions is easy: because the success of the child nodes conditions how the branch and therefore the tree runs, combining composite “and”s and “or”s can directly give you a set of prioritised features. They natively handle sequences, interruptions and fallbacks.
Also, as you can see, this structure is pretty easy to read and visualise – you just follow the branches by priority and, at each node, check whether it succeeds or not to know if you can continue on this branch or must go to the next one.
But the biggest advantage of behaviour trees is that they are highly modular and scalable: by removing or adding a branch, you essentially add or remove a feature to your character; similarly, it’s pretty simple to copy a sub-branch and paste it somewhere else to copy-paste part of your behaviour. Some sub-trees can even be completely self-contained autonomous bits of logic that you can then re-import and re-use in another character’s behaviour tree to quickly implement chunks of a system (like following a target, patrolling between a set of points, et caetera).
Moreover, this modularity means that you can build your tree piece by piece and have an incremental design strategy.
Finally, note that contrary to finite state machines where, by definition, states are as isolated from each other as possible, behaviour trees are far more flexible to leaking information between nodes. Even though each node is executed on its own and lives its own life, it is pretty common to have some shared data state that can be accessed from various points in your tree to have global (although context-specific) variables.
Implementing our guard AI
But enough talking, let’s get to actually coding a behaviour tree so that we better understand how it works!
In this tutorial, we will create a basic AI for a guard. By the end of the session, it will be able to patrol between a set of points when idle, keep an eye out for monsters and go to them if one is nearby and finally attack until the monster is dead:
Note: we’ll focus on the guard, so the enemy won’t do anything, but of course the techniques we’ll learn here can be applied for any unit, so you could easily transpose what we’re going to do to another AI system for the skeleton.
About the 3D scene
In this tutorial, I’ll be using various free assets from the Asset Store or from online texture databases. Here are the links if you want more info, or if you want to download them yourself:
- the guard model (+ animations): https://assetstore.unity.com/packages/3d/characters/toon-rts-units-demo-69687
- the skeleton model (+ animations): https://assetstore.unity.com/packages/3d/characters/creatures/dungeon-skeletons-demo-71087
- the ground tiles texture: https://ambientcg.com/view?id=Tiles089
- the wood crate: https://opengameart.org/content/crate-2
And by the way: I won’t go into the details of how to make the 3D scene but, if you’re interested, be sure to let me know in the comments and I might make another video on this topic! 🙂
So anyway – let’s assume you have your 3D scene designed, populated and ready. In here, I have my guard, the skeleton near a campfire (with a simple particle system) and a few crates and lights for the ambience:
The guard has a basic animator that has three states: idle, walking and attacking. Each state has its own animation and the transitions between those animator states are controlled by two boolean variables: “Walking” and “Attacking”:
The skeleton just has a single animation in its animator, it has a BoxCollider and it is on a user-custom layer called “Enemy”:
This physics layer will help optimise the checks for nearby enemy units in our code 😉
Part of my behaviour tree will also re-use the simple patrolling system I talked about in this other tutorial, so I’ve prepared a few empty game objects to act as waypoints for my patrol route:
Now, it’s time to dive into the programming of our behaviour tree! 😉
About the behaviour tree
To implement the behaviour tree, we will work in two phases:
- first, we’ll define some generic architecture that could be used by any behaviour tree, along with a few composite nodes
- then, we’ll focus more specifically on our guard and design its particular behaviour tree structure and the nodes inside it
Preparing a generic behaviour tree architecture
First, let’s prepare our generic behaviour tree architecture.
To begin with, we’ll work on our atomic element: the Node. Let’s create a new script folder called
BehaviorTree/ and, inside it, create a new C# script:
Node.cs – we’ll open it and remove the default Unity
MonoBehaviour. Also, to make this tool more re-usable, we can declare the generic classes inside a
Node class will be a basic C# class that represents a single element in the tree and can access both its children and its parent, has a node state that uses an enum and can store, retrieve or clear shared data.
Let’s start with the state. We need an enum with 3 possible values:
FAILURE. Then, in the class, we’ll declare a state for the node using this enum. We’ll make it protected so that classes that derive from this
Node class can access and modify this value:
Similarly, we can make a public parent variable and a protected list of children. Having a link in both directions is interesting because it will make it easier to create our composite nodes (by looking at the children) and to have shared data (by looking at the parent and backtracking in the branch):
These children will be assigned in the constructor of the class, or else they’ll just be empty; by default, the empty constructor will simply assign a null parent node. To be sure we properly link the parent field when we create our tree, we’ll rely on a util method called
_Attach() that properly creates the edge between a node and its new child, and call it from the Node constructor, like this:
Now, we can prepare the prototype of the
Evaluate() function – it will be virtual so that each derived-
Node class can implement its own evaluation function and have a unique role in the behaviour tree:
To finish up our class, we need to take care of the shared data. This data will be stored in a dictionary of
object pairs: by using the C#
object lazy type, we essentially have a mapping of named variables that can be of any type and that is therefore pretty agnostic to the data you want to store:
To set the data, we just need to add a key in the dict:
To get it back, it’s a bit more complex because we want to check if it’s defined somewhere in our branch – not just in this particular node. This will make it easier to access and use shared data in our behaviour tree. To do this we’ll make the
GetData() function recursive and continue working up the branch until we’ve either found the key we were looking for or reached the root of the tree:
To clear data, the process is pretty much the same: we recursively search for the key and, if we find it, then we remove it from the dict. Else if we reach the root we just ignore the request.
Now that we have a
Node class, we can go ahead and implement the second core object of our package: the
Tree class. This other C# script will be a MonoBehaviour and it will contain a reference to a root node that, itself, recursively contains the entire tree.
It just needs to do two things: first, upon
Tree class will build the behaviour tree according to the
SetupTree() function we defined; then, in the
Update(), if it has a tree, it will evaluate it continuously:
Preparing util nodes
Finally, to end this first part on the implementation of a generic behaviour tree, let’s prepare two composite nodes: the
Sequence and the
Sequence is a composite that acts like an “and” logic gate: only if all child nodes succeed will this node succeed itself.
To implement it, we just have to derive from the
Node class and override its
Evaluate() method. In our overridden evaluation function, we will iterate through the children and check their state after evaluation. If any child fails, then we can stop there and return a failed state too. Else, we need to keep on processing the children, and eventually check if some are running (and therefore block us in the running state too) or if all have succeeded:
Selector node, on the other hand, is like an “or” logic gate. It’s almost the same code as the
Sequence, except that we return early when a child has succeeded or is running:
Implementing the guard-specific behaviour tree
Ok! We now have a toolbox for our behaviour trees that will make it easier to create our own specific AI system for this guard.
We will build this custom tree gradually and add more actions as we progress.
Step 1: “Patrolling” – a single-node tree
So our very first behaviour tree will be real simple – it will have only one node: the
TaskPatrol node. There won’t be any internal node for flow control, because for now the guard will always execute the same action: patrolling. This node will be executed continuously and re-implement the basic patrolling algorithm we saw in the other tutorial.
First, in another scripts subfolder (
GuardAI/), let’s create a new C# class called
TaskPatrol, import our
BehaviorTree package and have
TaskPatrol inherit from the
Again, we’ll want to override the
Evaluate() function, but also the constructor.
That’s because our patrolling algorithm requires some additional info, like the waypoints to use, but also a reference to the transform of the agent performing this action. Since
TaskPatrol is not a MonoBehaviour but a somewhat abstract blob of data, we need to pass it the transform to use.
We will store these variables in the class as private members and assign them in the constructor:
Of course, I need to update and return the state of my evaluation function. This node will just be running continuously, again and again, so I’ll just put this in the method:
Then, let’s simply copy back the code we had in the patrolling tutorial.
I need to replace the few missing variables with their new equivalent – so instead of the
transform, I’ll use
_transform; same thing for
For the speed, I’d like this to be defined at a higher-level so that all the nodes in my tree that need this variable can find and share it easily. So I’ll just make it a public static float in my behaviour tree itself.
This behaviour tree will be defined in a
GuardBT C# class that inherits from our
This is where we’ll expose a public field for the waypoints (that we’ll then pass on to the
TaskPatrol node) where we’ll set our global
speed variable and where we’ll define the structure of our tree, in the
Don’t forget we can now use the
GuardBT.speed in our
All that’s left to do is, back in our
GuardBT, to design the tree structure. For now, this tree is just a single root that uses our new
I can finally put this component on my guard and assign the waypoints:
Now, you see that when I run the game, the guard goes from one waypoint to the other with a little wait at each, just like in the patrolling tutorial 🙂
Of course, we’d like for the model to use its walk animation in-between the waypoints. So let’s go back to our
TaskPatrol and, when we get the
transform, also get a reference to the animator on the same object. Then, in the
Evaluate() function, we’ll set the “Walking” boolean to true or false when we finish waiting or when we reach a new waypoint:
And tadaa! The guard now walks through its patrol path!
Step 2: “Targeting” – checking for nearby colliders
Now, let’s ramp things up a little and add some behaviour to spot nearby enemies and go to a target once it’s been defined. This time, this action will require two nodes:
- first, a
CheckEnemyInFOVRangenode that returns a success if there is at least one enemy close by, in the field of vision radius, or else a failure
- second, a
TaskGoToTargetnode that moves the guard towards the target that has been found
These actions will be inside a
Sequence because we only want to move to a target if we spotted one. Then, overall, we’ll want either this
Sequence or the basic
TaskPatrol to be used, so we’ll need a new
Selector node as the root to link those two little branches together:
To keep a reference to this target enemy, we’ll use our shared data system: the
CheckEnemyInFOVRange, if it finds an enemy, will store this target in the root of the tree so that the
TaskGoToTarget (and perhaps other nodes later on…) can retrieve it and use it in its computation.
This new class will again inherit from the
Node class and require the transform in its constructor.
In the overridden
Evaluate() function, we’ll check to see if we already have a target, by using
GetData(). If we have one, then we succeed automatically.
Else, we need to do a little check of our surroundings, using something like Unity’s
Physics.OverlapSphere(), that returns all the colliders that are found inside a sphere around a given position. We’ll limit this search to just the enemy layer, take our position as the center of the sphere and define a new static variable in the tree, the
fovRange, that we’ll use here:
Then, if we did find at least one collider, we’ll store the first collider in our list in the
target slot of our shared data, in the root (which is two levels above, that’s why I use
parent.parent) and return a success.
Else, we’ll fail on this node and stop the processing of this branch there.
Just to be sure, we can also make sure that the walk animation is playing by getting a reference to our animator and setting the “Walking” boolean to true.
TaskGoToTarget class is pretty straight-forward to code, based on the previous nodes we prepared. We just get our transform, try and get a target from our
target data slot and, if we have one, move towards it. Also, we return a continuous running state.
To finish this up, let’s use our new nodes in the
GuardBT script. We’ll replace our root with a
Selector and pass in two children: first, the
Sequence with the check and the move task; and then our
TaskPatrol node from before:
Remember that the order in here is important: this gives the list of priorities for your actions and, in our case, sets patrolling as the fallback.
If you run the game again, you’ll see that, now, when the guard gets close enough from the enemy, it interrupts its patrol and move towards the target!
Step 3: “Attacking” – one more branch!
The last thing we want to implement is a basic attack behaviour. When the guard gets really close to an enemy and this enemy enters its attack range, then the guard should switch to attack mode and regularly deal some damage to the enemy. Again, we won’t have the enemy react in any way in this tutorial but you see we could easily use the same techniques to create a behaviour tree for this unit too 🙂
Sequence will be pretty similar to the targeting one: we’ll have a check node for the attack range and then a task node for actually attacking.
However, we’ll assume that the target has already been found and filled in when we try to attack, so the
CheckEnemyInAttackRange routine will not look for colliders – it will just check the
target shared data slot.
If it doesn’t find a target, it fails. Else, if the enemy is close enough, it will succeed. We’ll also want to change the guard animation to attack, so I’ll take my animator and set the “Attacking” flag to true and the “Walking” flag to false.
TaskAttack class will need a reference to the enemy’s
EnemyManager component so that it can lower its healthpoints. I won’t detail this script too much but you can check out the Github repo if you want to see what this script looks like! 🚀
EnemyManager script defines an initial amount of healthpoints for the enemy and then has some methods to decrease this amount by a fixed amount or, when the healthpoints reach zero, have the enemy “die” and its game object disappear.
To get the reference to this script, we’ll use Unity’s
GetComponent(). But because
GetComponent() is not efficient, I will cache this result for as long as my target stays the same.
Then, I’ll just have a little counter, like in my
TaskPatrol, to attack every second:
TakeHit() method from my
EnemyManager script returns a boolean that tells me if this blow was the final one and if the enemy is now dead. In that case, I want to clear my
target data slot and switch back to walking, to return to my patrol path:
We can add this new branch in our tree at the very beginning so that it has the highest priority:
And if we run this, we see that the guard now patrols, spots the enemy, moves towards it, attacks until the skeleton is down and finally resumes patrolling as its fallback behaviour! 🙂
That’s it for today folks!
In this tutorial, we saw what behaviour trees are and why they can be better than finite state machines for complex systems; we then created a basic AI for our little guard so that it is able to patrol and target or attack monsters.
This was a very short introduction to the behaviour tree AI pattern; of course, if you’re time-constrained or you’d rather look into ready-made solutions, there are plenty of packages in the Unity Asset store for this feature. Most notably, the Opsive behaviour tree system, that is really great and has an intuitive user-friendly graph-based editor!
I hope you enjoyed this quick Unity tutorial and the dual video/text versions. Feel free to react in the comments and tell me if you like this format — and as usual, go ahead and share your ideas for future topics you’d like me to make Unity tutorials on!