This project is about Behavior Trees their applications in games. The functionality from my demo is based off of a game called Majesty: The Fantasy Kingdom Sim. Its one of games that really got me interested in AI. Every hero has a similar set of actions, but their personality shapes which ones are performed frequently or if they have access to new actions.
The goal of this demo is recreate some of the basic behaviors seen in the game specifically Fear, Attack, Treasure Chest Seeking, Item Purchasing, and Wandering. These Behaviors make up the core of a hero’s behavior from my time playing the game. When each behavior starts the hero agent will have an icon appear above its head that matches.
I chose the Behavior Tree technique because it is very modular and easy to add or edit existing behaviors. It makes a lot of sense to use for me to use a very modular technique since in Majesty many of the heroes share similar or the same behaviors. It is also very easy to give priority to certain behaviors in the trees by simply changing their order in which they are added.
Most of the architectural structure of the implemented behavior tree came from the Game AI Pro book written by Alexander J. Champandard and Philip Dunstan. In their book they use a lot of inheritance when it comes to the actual behavior that the agents perform. Their base Behavior class is set up to have four main functions, OnInitialize, OnUpdate, OnTerminate, and Tick.
Since the project was done in C# and the Game AI Pro was written in C++ I had to adapt the architecture a little bit to fill in for some differences between the two languages. The OnInitialize function handles anything the behavior needs to get before it performs its function. OnUpdate is when the Behavior performs its actions/calculations. The OnUpdate function also returns a Status. This status indicates whether the Behavior has successfully performed its function, failed to perform its function, or is in the process of performing its function. This is typically encapsulated in an enumeter with the values of SUCCESS, FAILURE, and RUNNING.
The Tick function uses the previous three functions to make the whole behavior work shown below.
Behavior nodes (or leaf nodes) do not have any children so they typically do not directly lead to any other node. They can hold whatever functionality is required for the type of game being created.
The next most important part of any behavior tree are the Sequence and Selector nodes, also know as Composite nodes. Composite nodes are nodes that are allowed to have multiple children. Selectors and sequences each have similar, but different ways of interacting with their child nodes.
Sequences traverse through all children in sequence until it iterates through all children or one of its children’s return a failure status. If any child returns a failure status the sequence will automatically stop and return a failure status. You can think of the logic being similar to a logical AND node. If all children return something other than failure the sequence will fully complete and return success.
Selectors will traverse through their children until they reach a child who returns a success status. if a child returns a failure status the selector will instead move on to the next child. A selector node is similar to a logical OR node. It will return a success status so long as any one of its child nodes returns a success status.
Decorator nodes are the final node that are used in a basic behavior tree. They typically have one child node who then has their Tick function function called based on the functionality of the decorator. Below is an example of a common Decorator known as an Inverter, which inverts the status of whatever node it is attached to.
When constructing the behavior tree I isolated each behavior I wanted to emulate and created a small tree that would be the behavior. Below are two examples of the behaviors I built the Fear and Collect Treasure behaviors.
The fear behavior is one of the more iconic states in Majesty. Heroes in the game will run away from enemies that are too powerful for them to fight. When they run away they will typically return to one of two locations, either the nearest Inn or their guild hall.
The Behavior starts with a sequence node so that the other nodes are done in a specific order. The first node visited in the root sequence is a conditional node. Conditional nodes can be either be implemented as leaf nodes or decorators. I implemented a leaf node version of them for my project. The conditional I check first is whether the hero agent is scared (because if you’re not scared then why run away). If that check succeeds the sequence continues to the next node.
The next node is a leaf node that locates the nearest location with a specified tag (the house shown above). The Node returns success if it can locate a safe spot. If the node can’t it returns failure, meaning there is no location to move to. This will stop the Sequence and the tree might be able to find another solution to the problem.
If the find location node does successfully find a location the tree will advance down to the selector. The selector has two options. If the hero’s health is low they will drink a health potion. Since the low health conditional’s parent is a selector if it returns a failure status the selector will move onto the next node, the move to node.
The move to node is a special leaf node since it utilizes the RUNNING return status. The move to leaf node compares the hero agents current position to the target position. If the hero agent is within a certain distance of the target the node return success. If the hero agent is not within that distance a simple seek steering is performed in addition to return an running status. When the running status is returned the behavior tree stops iterating until the status changes. If a node needs to run over time returning a running status will accomplish this.
After the hero agent moves to the desired location the selector will return to its parent sequence which will then activate the EnterLocation leaf node, which simulates the hero agent entering the building.
The collect treasure behavior is the next one I will explain the process for. This behavior starts with a sequence node, however instead of a conditional node being the first node in the sequence instead a leaf node is. The leaf node checks for treasure chests around the hero agent within a certain radius. If it detects one it calculates whether the hero is interested enough in the treasure to go get it.
If the hero agent is interested enough then the will enter a selector node similar to the one in the fear behavior, however there is a conditional with an inverter decorator node attached to it. Since the conditional checks if the chest is in pick-up range an inverter is applied to make sure that sequence continues when the hero agent is not in range. If the hero agent is not in range the conditional node returns failure, however if the conditional returns false then the sequence returns failure. The inverter is applied so that the failure status turns into a success and the sequence continues onto the move to leaf node. After the hero agent is in range the sequence returns to the selector and we can collect the treasure.