As you probably know, Behaviour Trees are the way to go for a lot of AAA Titles and there is a good reason for that! The biggest perk Behaviour Trees come with is their simplicity, especially for complex projects. Obviously, Finite State Machines still are the easiest to implement – but in the bigger picture – they can get shockingly overwhelming with increasing amount of possible states.
This blog post will cover the basics of a Behaviour Tree and how to implement your own Behaviour Tree framework. Keep in mind that there are a lot of great solutions out there on the Asset Store that are way more detailed and fine-tuned as what I am showing you here. This blog post is merely for those who have no idea about Behaviour Trees and just want to see how it works and how it’s done in a simple way.
What is a Behaviour Tree?
It is a branching, hierarchical system of different tasks that share a common parent, which is called the root. Picture a tree with the roots, the stem and all its little branches that eventually have leaves attached to them.
Now, Tasks can represent tests or behaviours. If you have been working with Finite State Machines before you will know that they follow transition rules. F.E. if a target is close enough – attack. A Finite State Machine has to test all of the conditions for all of the states it could possible go to from the state it is currently in. A Behaviour Tree works differently in a way that its flow is defined by each single task’s order within the entire tree hierarchy.
Behaviour trees are executed starting from their root task, continuing through each child, which, in turns, runs through each of its children until a condition is met or the action task/ leaf task is reached.
There are different types of tasks, but all of them have one thing in common and that is that they will always return one of the following states:
SUCCESS – The condition we checked for is met.
FAILURE – The condition we checked for isn’t met.
WAITING – The condition we checked for has not been determined.
I think SUCCESS and FAILURE are pretty much self-explanatory, so let’s focus on WAITING.
Behaviour Trees can get really complex, therefore a lot of their implementations are asynchronous and this is why we need a waiting state. Imagine a tree with 100 tasks of different complexities, some of them may even need a couple of frames for evaluation, if they wouldn’t run asynchronous, this would result in massive freezes if we would have to wait for each task to either return true or false/SUCCESS or FAILURE.
Composite tasks can have more than one child and their state is based purely on the evaluation of its children – as long as the evaluation of its children is still running, it will be in WAITING state. These tasks can be split up into different types as well, Sequences and Selectors, those separations are made upon the way their children get evaluated.
Sequences run from left to right. A Sequence only returns SUCCESS if all of its children have been evaluated as SUCCESS.
Selectors return SUCCESS if at least one of its children returns SUCCESS. The only way a selector returns FAILURE is if all of its children returned FAILURE.
A Decorator task has only one child. It may seem strange to rely on a child if there is only one of them but the decorator is special in a way that it takes the state returned by its child and evaluates it based on its own parameters, it can specify for example how and how often its child gets evaluated.
Just as composite Tasks, decorators can be split up into different kinds based on their functionality.
Here are the most common ones:
The repeater will repeat the evaluation of a child until it returns either SUCCESS or FAILURE.
The inverter will inverse the return of its child.
The limiter will limit the number of times a task will be evaluated to avoid a weird looking behaviour loop. It is different from the repeater in a way that it will only try to do something for a certain amount of times before moving on to trying something else.
Like is said, the possibilities are endless and you surely can combine different functionalities aswell, although I would advice you to not go overboard with this as things will get unorganized if you do so.
Action Tasks or leaf Tasks are the outermost tasks of your Behaviour Tree. They will carry your executed agent behaviours like walking, running, shooting, fighting and so on.
Now that you know how Behaviour Trees work in theory, I wrote a simple Behaviour Tree to show you how all of this is applied codewise.