Enemy AI with Behavior Trees - with Demo in Godot Engine

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hi guys today i'll be talking about implementing enemy ai using behavior trees i'll be modeling the ai from typical ground and air enemies that you can find in games such as hollow knight let's look at some clips to see what we can learn about that behavior here we have a typical ground enemy and hollow knight what we're seeing right now is their default behavior which is to patrol their spawn area with an occasional pause to loiter when the player gets close they turn towards a player and run right at them the player loses hell when the enemy touches them and when the enemy sustains enough damage they do a death animation of course there's a lot of juicy nuance and behavior i'm leaving out here but that's enough for the basics the behavior of the air enemy is pretty much the same the enemy patrols a small area around their spawn point and when the player gets close enough the enemy moves towards them again the player loses health when the enemy touches them when the enemy sustains enough damage they do a short death animation so as you can see the overall behavior for both types of enemies is very similar ok so how do behavior trees work well each node in the behavior tree does some sort of processing at a regular interval that interval is called a tick in games that interval is often the same as a frame but either way it amounts to the same thing processing starts at the root node it calls a function on each of its child nodes in order and each of those nodes calls a function on each of its child nodes in order and so on so as you can see this is a depth first processing of the tree nodes and isn't anything specific to behavior trees yet each node returns a result of some sort to its parent node that result can either be success failure or running success and failure are just that they mean that the node succeeded or failed to do something or that some condition was either present or absent running means that the nodes process is still running and success or failure has not yet been determined you may wonder why there needs to be a running result if no did not return a running result and instead continue to wait for the running process to complete then the behavior tree could not react to changing conditions when a parent receives a running result from a child node it immediately returns a running result to its parent node in this way running is returned all the way to the root node and causes a tree to stop trying to determine behavior till the next tick parent nodes also look at the success or failure result to determine if they should return a result immediately to their parent or process the next child node exactly how that happens is what we'll discuss next here is a skeleton of a simple enemy behavior tree for this example the enemy can either attack or patrol when we want to select among various actions trying one after another until an action succeeds we use what is called a selector node selector nodes process their children in order looking for either a success or running result when a success or running result is encountered the selector node stops processing children and returns that result to its parent so in the example we have so far the enemy first tries to attack if that fails then the enemy would patrol the area so what would make an attack fail well let's see what our simple enemy does for its attack behavior first it checks to see if the player is close then moves to the player and then hurts the player what we have here is a sequence of actions to perform so this branch of the behavior tree will have what is called a sequence node sequence nodes are like selector nodes in that they process their children in order but instead of looking for a successor running result they look for a failure or running result once the sequence node sees a failure or running result it stops processing its children and returns that result to its parent now for the patrol behavior for it we have two actions move forward or turn around we want the enemy to move forward until it can't anymore when moving forward fails we want the enemy to turn around so it will begin to move in the opposite direction since we're selecting between two actions moving forward or turning around we'll use a selector node as the parent collectively selector nodes and sequence nodes are known as composite nodes composite nodes have multiple children there are other types of composite nodes i'll go over later the nodes at the bottom are unsurprisingly called leaf nodes these are the nodes you write code for but they can be further categorized into conditional and action nodes as their name suggests conditional nodes check for conditions returning success or failure depending on if the condition is met and action nodes perform an action and may return success failure or running let's go over some examples of processing our simple nab behavior tree we start with the top selector node processing its first child in turn its first child the sequence node processes its first child if the is player close node returns a failure then the sequence node would stop processing its child nodes and return a failure to the root selector node the root selector node would process its other child node for the patrol branch that child node a selector node would process its first child node the move forward node let's say the attempt to move forward is successful so running is returned by the move forward node the selector node seeing that has received a running status result stops processing its children and returns a running status to its parent upon receiving a running status result the root selector node would stop processing its child nodes let's back up to the point move forward returns a result what happens when it returns a failure because say the enemy hit something in the level well its parent selector node will continue processing the next child node remember it's looking for an action to return a result of success or running because it's trying different actions out to see which will work so the turnaround node is processed let's say that this node always succeeds and so the selector node is finally given a success result seeing the success result it returned that result to its parent selector node when the root selector node sees the success result it stops processing its child nodes and processing is complete for this tick so now that we've covered the patrol branch let's see what happens for the attack branch for anything in the attack branch to happen the is player close conditional node needs to return a success result when it does the sequence node will then process the move to player node the move to player node moves the enemy towards the player if the enemy hasn't made contact with the player then it will return running when its parent node sees the running result it will stop processing child nodes and return a running result itself back to the root selector node the root selector node in turn stops processing its child nodes and processing is done for that tick once the move to player node detects that the player has been reached it returns a success result to the sequence node which then finally calls the hurt player node to do damage to the player the hurt player node returns a running result since damage will continue to be done to the player as long as they are close enough that the move to player node continues to return a success as before the running result is returned all the way up to the root selector node and processing of the tree is done for the tick hopefully running through the processing of this behavior tree has helped you to understand how behavior trees work now remember how we went over the types of nodes earlier those were composite nodes which include selector and sequence nodes and leaf nodes the difference between these two groups were the number of children they had with leap nodes having no children and composite nodes having one or more so there's a third group those nodes having exactly one child node these are called decorator nodes they augment the processing and result value of their child node for example there are inverter nodes which invert the result value of their child nodes and an always succeed node which always returns a success result no matter what its child node returns there are other types of decorator nodes i'm not going to go over here the list is quite long and i'm sure we can always dream up more types along with selector and sequence nodes there are other types of composite nodes in fact you could make up your own if you had the need but the obvious other composite nodes are variations on selector and sequence nodes that being ones that process their children in random order and ones that process their children in parallel again as i've said before leaf nodes break down into conditional and action nodes these are the nodes you most often write yourself to check for conditions and actually implement actions there's one more thing about behavior trees to mention before we get into the demos and that's something called a blackboard a blackboard is nothing more than a data bucket to dump information into this allows the behavior tree to have a memory and only do calculations once and reuses the results in multiple tree nodes for example let's say we're calculating the distance to the player to make some decision in other parts of the behavior tree we can do this once and add the result to the blackboard we could even use a decorator to further limit the number of times we do this calculation since the distance is not likely to change all that much between ticks now that we've gone over behavior trees let's see them in action i've created a demo of ground and air enemies using behavior trees this code is available on github and is what we'll be going over right now link is in the description also i've included my own behavior tree implementation in the demo but you should have a look at the asset library on godotng.org for other implementations these offer you a way to create the behavior tree visually using the graph editor doing it in gdscript or using nodes let's run the demo to see what we have here's the ground enemy demo i created a player and a ground enemy note the behavior the zombie is patrolling its spawn area walking forward turning around the edges and pausing to idle ever so often when i move the player to the same level as the zombie it runs towards the player once it's close enough it starts doing an attack animation when the player leaves the area the zombie goes back to patrolling since the animations i used also include death animations i made it so when the player jumps on top of the zombie it does its death animation and respawns the air enemy is a little different rather than patrolling around its spawn location it just idles in place but just like the zombie once the player gets near the bat it moves towards the player and begins attacking once the player gets far enough away the bat goes back to its idle state and just like the zombie if you jump on the bat it does a death animation and then respawns so let's look at the behavior tree setup starting with the zombie ground enemy what we have here is a kinematic body with an animated sprite and a collision shape there are also two detector areas for detecting the player the areas have different collision shapes to detect the player when they are nearby and close the fact that the player is nearby or close is used in the behavior tree let's go over that next the behavior tree starts with a sequence that is a list of actions to do in order it starts with these record steps that add data to the blackboard about the state of the enemy that is if it's currently dying or if the player is near or close the last step in the sequence is a selector note called do one of these actions remember a selector evaluates his children until he gets a success or running so each of these actions will be checked until one is successful the first action checked is if a death animation should be done if the check for is dying succeeds then a die action is performed the other actions in the selector are the same we check a condition and if that succeeds do some action lastly is a patrol area node which first tries to move or idle and if that doesn't succeed then turns around this mirrors the simple enemy behavior tree we went over earlier except with the addition of an idle behavior the idle node is written to switch between a running result which runs the idle animation and a failure result which allows the move node to run the air enemy is structured much the same way we have a kinematic body animation sprite and collision shape along with two detection areas to tell when the player is near or close again the behavior tree has a sequence node that records data into the blackboard about the state of the enemy and if the player is near or close and then we have a selector node where the enemy checks what actions to do in order all this is identical to the ground enemy except for the default behavior instead of a patrol behavior we just have the idle action which just runs the idle animation so that's my introduction to enemy ai using behavior trees be sure to check out the source code it should help you really understand how all this works and feel free to use any of it for your project if you have any questions or comments about behavior trees or the demo please let me know in the comments below i hope you found this video useful if so give it a like and be sure to subscribe so you don't miss my next video about game development
Info
Channel: Jason Lothamer
Views: 3,191
Rating: undefined out of 5
Keywords: Godot Engine, Game Development, AI, Artificial Intelligence, Behavior Tree
Id: QWMoCSeCmGQ
Channel Id: undefined
Length: 15min 26sec (926 seconds)
Published: Sat Sep 04 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.