UE4 Behavior Trees
So your zombie needs a brain...
Luckily there is a bunch of stuff built-in to UE that can help....Behavior Trees!
Behavior trees are another tool you can add to your AI arsenal.
As an abstract concept, they are a mechanism to let you define tasks that get executed depending on state. In this regard they are similar to a finite state machine (FSM). But while a state machine focuses on defining transitions from one state to the next, the behavior tree focuses more on defining a hierarchical tree of tasks which can be evaluated to determine what to do next sequentially.
For very simple AI agents, it is often easier to just setup behavior directly in the character blueprint. But as an agent develops more complex behaviors, defining the sequence in which to execute these behaviors can become more and more cumbersome. This is where behavior trees can really shine.
It is still important to emphasize that at the end of the day, behavior trees are just tools to help you define behavior, and they are not magic beans. The key task(pun intended) and hard part as a game developer is still just figuring out what you want your zombie to do - and you can always implement that with a Behavior tree, or a state machine, or a bunch of conditional if statements. So if your zombie just needs to find a target and walk toward it, you might be better off just implementing that without a behavior tree. That being said, Behavior Trees in UE have a bunch of great built-in capabilities so lets dig in and check them out.
Behavior Tree Components
Behavior Tree (BT) - This is the main directed graph where you define a tree of nodes. One very important consideration is that by default this tree is not evaluated every tick - rather it is event-driven. It is evaluated on demand. This is especially important when you have a zombie horde and don't want to be constantly evaluating hundreds of behavior trees every tick.
Blackboard (BB) - The Blackboard is a "data dictionary" that you can use to share information between the different components of an entity. Most important (and often overlooked) is the fact that the Blackboard is evented and fully observable. So if an alien landing generates an event that updates the blackboard of each minion in the game, then each minion's respective behavior tree will execute! (This is because a conditional in the behavior tree is registered as an observer of the changes in the blackboard)
Composite Nodes- These are the core task choosers. The main composites are Sequence and Selector.
Selector Nodes execute their children from left to right, and will stop executing its children when one of their children succeeds. If a Selector's child succeeds, the Selector succeeds. If all the Selector's children fail, the Selector fails.
Sequence Nodes execute their children from left to right, and will stop executing its children when one of their children fails. If a child fails, then the Sequence fails. If all the Sequence's children succeed, then the Sequence succeeds.
Decorator (or perhaps better known as Conditionals) These are "conditional gates" that allow or abort nodes from executing. Decorators can be configured to abort the currently executing task and re-evaluate the tree. This can be particularly useful for evented decorators.
Service - This is a self ticking evaluator that is often used to update blackboard state during some periodic interval.
Task - This is what you want the agent to do. This is the task that gets executed. A task may complete right away, or it can be a long running task that is ticked - independent of the tree. The tree activates the task, and the task ticks until it is aborted by the tree, or until it completes itself by returning. Each task can return either true or false upon completion to the composite that created it.
Overview Basically, each time the behavior tree is evaluated it is executed top down and left to right. (You can see the automatically created sequential index on the badge in the top right corner of each node in the editor). Typically, the behavior tree is evaluated after events trigger changes in the blackboard - it is not automatically ticked every frame.
For example, consider a behavior tree for a human. Suppose you are in a sequence branch that defines what to do when there are no enemies around. This branch can have a decorator on it configured to abort when a blackboard key "enemy in range count" is greater than 0. In this context, abort means stop executing this node and move to the next node in the tree.
Elsewhere, a service might update this observed blackboard entry every .4 ms. (Or perhaps a collision sphere on the human might update the blackboard whenever a new enemy enters it). Either way, once the blackboard is updated, the decorator aborts the currently executing task and branch, and the next node in the the tree is evaluated.
When viewing the tree while debugging, you can see what is being executed because it is highlighted - similar to the highlighted execution line trace in a blueprint. Note that this does NOT represent a ticking of the tree as it does with the execution line trace- it is simply showing which node is currently active.
Designing and troubleshooting Behavior Trees can be difficult and confusing. Especially when you are first learning them. I found it useful to create a Print Task node that simply outputs to the screen. Strategically placing this on the tree during execution can help you understand what is going on.
Notice two things about this simplest of tasks - first there are optional AI versions of Execute and Tick - these serve the same functions as the non-AI versions except they include pins for the Owner Controller and the Controlled Pawn. (Saving you the annoying boilerplate to get them yourself.).
Second there is a FinishExecute node. This will stop the task from ticking, and return the result to the parent composite. If you leave off the Finish Execute, the task will execute indefinitely until aborted by the tree. The Success flag on FinishExecute lets you return success or failure to the composite that executed the task.
A better example of using a tick event might be a WaitUntilClose type of task:
In this case, we continue checking the walker's range to his target until it is within some tolerance.
An example composite using this task might be:
There are a few things to notice here:
First, there is a composite sequence "Hunting Hero", that will continue executing its leaf tasks in sequence until one of the fails or until it is aborted.
In addition, there is a Blackboard Based Conditional Decorator that will only allow this composite to run if the state in the blackboard is "Hunting Hero Target".
The 2 tasks that are continually executed sequentially are:
Also notice the Cooldown decorator on WalkerDoAttack.
Behavior trees are instrumented to support the built-in GameplayDebugger. Hit ' while playing in PIE to bring up the debugger, then hit 2 on the keyboard to display BT info. This displays the current behavior tree on the left as well as which nodes are executing, and a dump of the blackboard contents on the right.
When you press ', the information displayed is for whatever character is at the center of the screen.
Digging In - An Example
Let's walkthrough setting up some behavior for our Zombie.
Suppose we already have a Character Blueprint named WalkerBP with an associated Animation Blueprint named WalkerAnimBP.
Let's also create a new BlackBoard named WalkerBB and a new BehaviorTree named WalkerBT.
The final piece of AI glue is the AIController itself. Let's create a new WalkerAIController and wire it in to the character blueprint.
Now for this particular game, our zombie has a few main states he can be in:
Looking For New Target
So let's create a new Blueprint Enum CurrentWalkerState to hold this:
An important consideration is that the BB is not replicated and only exists on the server. But the BB is evented and is the preferred mechanism to communicate with the BT. So I often make the BB authoritative for the CurrentState, but then also copy it every tick to the Character BP so it can be replicated, used by the Anim BP, etc...
Since the Blackboard is our evented glue, lets add some variables to it:
Make sure to fully wire in CurrentWalker State, you need to specify the type of enum.
So at the high level, our tree consists of branches of behavior for each possible state the zombie can be in. Remember, the tree does not "tick" every frame. Rather changes in state cause the decorators to evaluate and activate the appropriate branch.
In this game, we have two kinds of potential targets - the hero or the humans. These are treated differently, because we want the zombie to prefer humans over heros, unless they are angry at the hero (have been hit). This drives the desired mechanic of shooting a zombie to distract it from its victim.
Additionally, a zombie has a limited sense range.
To summarize the rules:
If both out of range, Wander Around From Random Location To Random Location
If Only Hero in Range, Choose Hero As Target
If Only Human in Range, Choose Human As Target
If Both in Range, If Aggro Choose Hero, else choose Human
Given this, our service needs to update the following BB entries:
Object - ClosestHuman
Float - ClosestHumanRange
Object - ClosestHero
Float - ClosestHeroRange
Enum - CurrentWalkerState
The full behavior tree
The initial state of our Walker is "Looking For New Target". Let's create a new service that our zombie can use that periodically scans for a new target and updates the correct blackboard state.
Root Motion Considerations
Another thing to consider is that our zombie is driven using Root Motion. For any animation that has non-constant locomotion, such as our zombie lurch, root motion can really enhance his movement.
Without root motion, a constant, or at best linearly changing movement must be used. This also often results in sliding foot motion. Even if the constant speed is perfectly matched to the animation to eliminate the sliding feet, then the zombie will move at a constant rate of speed, instead of the desired lurch.
Because we are using Root Motion, we can no longer utilize the simpler AI Move To nodes. But all we really need to do is face the zombie toward a target and let the animation get him there. It really boils down to point him in the right direction and select the right animation. To make him move faster, we can blend to a faster run animation, and/or increase the playback speed.
Every tick our zombie rotates toward his current target (unless he is idle). This rotation is done using rinterp to smooth it over time.
The behavior tree, blackboard, and associated components only run on the server.
As briefly mentioned earlier, since the blackboard only exists on the server and is not replicated, any state that needs to be replicated should be put on the CharacterBP. For example, the Animbp instance should not reference blackboard state but should instead rely on replicated characterBP state.
Often, you may already have an existing BP based AI.
Some steps for refactoring an existing single BP based AI
Move Tick logic into Services
Move Custom Events/Macros That Do Specific Things -> Tasks
Events that respond to change -> BB and Decorators
EQS and Perception
Two more things UE also gives you... the EQS Environment Query System, and the AIPerception Component. ( May cover these in more detail in a future post )
The EQS comes with pre-defined tasks for querying the environment around the actor.
The AIPerception Component enables your pawn to receive events when registered stimuli occurs - e.g. sight and sound.
For example, the zombie would receive an event anytime a human became visible. And the zombie would also receive an event when that target was no longer visible, and would remember how long it has been since he last saw the player.
By default, the sight component registers all existing pawns as targets. Every tick, these targets update their visibility and distance information. New pawns register themselves as potential targets to the system. Thus, the AISense_Sight component really maintains a list of potential targets which updates every tick.
Incidentally, for those getting started with reading the UE Source Code, this represents a good example to get started. New pawns register with the Perception system using the standard delegate system. It is a great example of the delegate system at work. Perhaps a separate source code digging post might be interesting....
Possibly more to come in future posts....