As I’ve already showed in AI part 1:General concept the main part of AI actor’s brain consists of a behavior tree and decision engine. Let’s take a look at them.
The behavior tree
I’m not going to describe how the tree works. There are plenty of articles on that topic. My goal is to have as simple and as readable behavior tree as possible without any hacks and unnecessary helper nodes.
One of the greatest things I like on Unity is the asset store. After some research and suggestions from friends I’ve decided to buy and use the NodeCanvas framework https://nodecanvas.paradoxnotion.com/ . It comes with easy to understand source code and also looks great 🙂
On the picture above is basically just my former debug AI transferred to the behavior tree. It can do three things: an idle movement, attack and of course to die.
Top level dynamic selector handles death. If the actor dies it ends up in Run Forever node and waits for deleting from the game scene.
The next branch is more interesting. The root component is my custom selector called Top priority dynamic selector. It is a composite that checks priority of all it’s children and executes only the one with the highest priority. If that child returns success or failure the whole node returns the same result. The running child can be interrupted by the higher priority child anytime. Priorities are provided by decision engine through the blackboard.
Right now just two branches are connected to top priority selector. They represents desires or possible actions that can an AI actor take.
First branch represents an idle behavior. It just repeatedly selects random time and stores it to the serialized memory. Waits that time and then selects a random position around and moves to it. Random position selection includes path to that position so the two tree nodes are nicely separated.
The second branch is about melee combat. Subbranches covers all cases that may happen in melee combat execution.
Note: all condition nodes like ‘Can move to target’ write the result together with the path to the serialized memory for caching and execution purposes.
The decision engine is part of the code that evaluates data in the memory and calculates priorities for the Top priority dynamic selector mentioned above. I’ve tried multiple approaches to the implementation including fuzzy logic but ended up with the most simple ‘magic’ constants solution.
Each possibility (or desire if you want) is represented by separate class and does evaluation of some part of memory. The most simple is possibility to do an idle stand/movement. It just returns very small priority value but greater than zero. Melee combat attack then returns priority based on knowledge of a target, equipped weapon and ability to reach the target. If no target is known or the known one has been seen long time ago melee combat returns zero. The inability to do something (like range attack for a rat) is also represented by zero priority.
More possibilities will be implemented in the near future. The described principle should cover them without need to rewrite anything.
That’s all. In the next part I’ll focus on navigation and pathfinding.