AI in Unity Turorial. Behavior Trees.

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

Great tutorial!

👍︎︎ 1 👤︎︎ u/snobocracy 📅︎︎ Aug 07 2020 🗫︎ replies
Captions
in today's video we are going to make a simple AI framework called behavior trees we will create AI that is going to chase us when we are in a certain range should add us if it gets close enough and run away to look for cover where itself goes under a certain amount let's have a look at how behaviour trees look like for this project we will use unity 2000 19.3 all of the units of the tree are objects of class of type node each node has a field that describes the state of the nodes each node can have only one of three states success/failure and running each node also implements a method called evaluate which will run every time you want to evaluate the tree and find the proper behavior in this method we set the knot state field value let's see what our tree will look like the top node of our tree on which we will call the evaluate method is a composite node we will cover two main composite nodes selector and sequence let's take a look at the selector first every composite node State is dependent on States of its child nodes every composite node evaluates its child nodes one after another that order is very important in case of the selector we are checking each child node until we find one with a success state when the first node after evaluation has a failure state we go to the next node and evaluate it if the next node has the success state the whole selector node will set its state to success and we don't have to evaluate any more nodes only in a situation will all of the child nodes have a failure state will the selector node also be a failure now let's take a look at the sequence node this node will also evaluate its child nodes one after another but in the case of the sequence when the first node is a success we don't automatically make the whole sequence a success but we go to the next note if the next note is a failure then the whole note is a failure and we don't have to evaluate any more notes the sequence note is a success only when all of the child notes are a success now let's take a look at the tree that we will be working on the top note will be a selector note that will have three children first the cover note which will be a sequence the second one will be a shoot sequence note and the last one will be a chase sequence note now in the cover selector note we will have two notes the first will be a health check node second one will be tried to take cover note the health note will return success when the AI agent has health under a given value if the AI agent health is above this value the health check node will return failure so the whole sequence will be a failure and the agent has no reason to go to cover I will try to shoot our chase otherwise the cover sequence node will be able to evaluate the next note that is d try to take cover note which is a selector this note will first check if an agent is currently behind cover so the first child note will be of type is covered note if this note returns success the whole try to take cover selector is also a success and in result the call cover sequence is a success if an agent is not in cover we will evaluate the next child node called find cover this note will select between two notes one that will move an agent to cover if there is one available and if not then an agent will chase us the first note would be a sequence called go to cover note and will check if there is a cover available so let's add the first child now to the go to cover sequence and it will be called ace cover available this note will return success if there is a cover that will hide an agent from the player site if so we will evaluate the next note that will simply move an agent to the cover this note will be called get to cover if there is no cover available then the whole go to cover sequence is a failure the find cover sequence will move to the next note which will be a chase note because the notes are objects we can reuse already existing notes and we don't have to construct them several times in this case we will reuse the chase sequence that we will discuss in a minute if the whole cover note returns failure we can move to the next sequence note called shoot this note is a sequence that first will check if the player is in a certain shooting range by is in range note if we are enraged and the shoot selector will evaluate the next node called should note that will make the agent fire a weapon if the is in range note is a failure the whole shoot sequence is a failure and the top note selector will evaluate the last node which is the chase sequence note the chase sequence note will behave similar to the short sequence but the distance in the east range note will be different and instead of shooting we will chase the player let's take a last look and let's start implementing our base class called node back in unity create a new folder called scripts inside this folder create another one called behavior trees and inside it create a new C shark script called node open this script this class will not be a mono behavior and let's make it so realizable to make it visible in the inspector also the node class will be an abstract class like we said before every node has to have a field that is describing the state of a node so let's add a field responsible for that and create a ennum of type node state that will have three values success/failure and running next add a getter for the node state variable and lastly declare an abstract method called evaluate that has returned type note state every class that inherits from the node class will provide its own implementation of this method back in unity let's create another script called sequence sequence will of course inherit from node class and in result we'll have to implement the abstract method evaluate as we know sequence is a composite now so it will have a reference to child nodes that it will evaluate one after another so let's at a new field that will be a list of nodes let's create a constructor for our sequence with a list in parameter in an evaluate method we will iterate through all of the nodes and call the evaluate method on them each child node evaluation will return the value of node state enum type then we will go through that in um in a switch statement let's also declare a boolean value is any child running that will be true if during any child evaluation we will come across running state so in case of states running the sequence does not break we will just set is any child running boolean value to true and go to the next child evaluation if any child is a failure that means that the whole sequence is a failure so we assign the failure state to the node state value and return it which will in result break us out of the method if the state is a success we don't do anything and we can evaluate the next child after the loop has finished we are sure that none of the child nodes was a failure otherwise we would have never reached this line of code so what we have to do is to ask if any child was running if yes the sequence state is set to running and if not we know that all of the children nodes were a success so the whole sequence is a success and we return the node state value now back in unity let's copy the sequence class and change its name to selector this class will be pretty similar to selector class so it will save some time first be sure that you are in the selector file and change the name of the class in the Declaration and the constructor to selector let's clear the evaluate method and leave the for loop and empty switch statement in the selector node if any of the child not evaluate methods return running state that means that the whole selector also sets its node state to running and we can return out of the methods we do the same thing in case of a success state in any of the child nodes in case of failure we do nothing and go on to evaluate the next child after the loop ends we are sure that all of the child nodes were a failure because we didn't return out of the method so we set the selector state to failure and return it there is one more very useful type of nodes called decorator nodes those nodes take a node and modify their state a very popular decorator node is called inverter this node takes another node and flips its state from success to failure or from failure to success for example our is in range node will return failure if the given distance is bigger than X but what if you want a no that will return success in that case and we don't want to write another node we can just pass the is in range node through the inverter node and get this behavior so back in unity let's copy the sequence node file and change the file name to inverter open this script and modify it similar to what we have done with the selector script the inverter class will have a field with only one note that it will modify let's fix the constructor as well don't forget to change the class name because we don't have a collection of child nodes we don't need to use the for loop we will just switch by the result of the evaluation of our single child node in case of state running we don't change anything and the inverter also sets its state to running if the child node State is a failure the inverter is a success and if the child state is a success the inverter is a failure now that the main framework of our behavior tree system is ready we can start implementing custom nodes so let's create a new folder under the scripts folder called nodes in this folder create a new C sharp script called health node before we start any coding let's prepare the scene first so a playing to act as a ground object then add two cubes for our player objects and for the enemy object and lastly add separate materials for each of them so we can know who's who on the enemy game object add a new script called enemy AI that script will connect the enemy game object to the behavior tree and will provide the tree with necessary data let's implement our first custom node the help node like every other node will have to inherit the node class and implement the evaluate method the health node will have a reference to the enemy AI component to get the current health value of an agent and a float called threshold that will be a value to which will compare the agents current health let's generate a constructor with those fields in the evaluate method we will simply return success if an agent's current health is lower than the threshold assigned in the constructor otherwise we'll return failure there is no situation where we're returning a running state I forgot to assign the calculated state to the node state field this won't be a problem because our composite node switched by the evaluate methods return value and not the note state field of the node but nevertheless there is a mistake on my part we don't have any enemy health yet let's add it in the enemy AI script first let's add a serialized field float for enemy starting health a private float for enemy current health and in this chart method initialized the current health with the starting health now at the gate current health method that will simply return their current health next add two more serialized fields one that will describe what health amount is considered a low amount and a flow that will tell us how fast the enemy will heal in the update method we will add points to the current health value equal to the speed assigned in the inspector multiplied by the time passed in this frame let's also make the current health a property that in a setter will clamp the current health value between zero and the starting health now let's start working on the next node so go back to unity and create a new C sharp script called range node open this script the range node we'll need to know what range we should compare the distance to a transform from which to calculate the distance and this will be our enemy and they transform to which calculate the distance and that will be our player let's generate a constructor with all of the class fields of course the range node inherits from null class and implements the evaluate method in the evaluate method we will first calculate the distance from the origin to the target next we will return success if the distance between them is equal or less than the specified range otherwise we will return failure no running state is possible in this node don't forget to assign the calculated state to the null state field like I forgot to do and like I notice before in the enemyís script add two fields that will define the range in which an enemy should start shooting a player and start chasing him we also need a reference to the player transform for the range node constructor go back to unity and create a new C sharp script for the new note called ice-covered node open that script this note will need a reference for the transform of the player and they transform of an enemy let's generate the constructor for this class and again inherit the note class and implement the evaluate method in the evaluate method we will rake us from an enemy position in this case the origin position in direction of the player in this case the target if we hit something we will ask if that hit object is our target if the answer is no that means that there is something separating us from the player and the enemy is covered and we'll return success otherwise we return failure and that's it for this node so let's go back to unity and create a new script for the next node called chase node it will also inherit the node class therefore implement the abstract method evaluate first we will need a reference to the player transform called target let's add a unity engine dot a i namespace because we will need a reference to enough mesh agent component of an enemy let's generate the constructor in the evaluate method first we will calculate the distance to the target if this distance is greater than some small value then we will start the agent set its destination to the target position and return state running otherwise we stop the agent and return success we do the distance comparison in order to stop an enemy object from getting too close and overlap with the player object let's go back to unity and create a new script for the next node this time it will be a shoot node in this node we will need a reference to a nav mesh agent component so at the Unity engine dot AI namespace also we need a reference to an enemy AI component as always inherit from the node class and implement the evaluate method in the evaluate method we could instantiate some projectiles and so on but to save time we will just change enemy color which will indicate that an enemy is in the shoot node we will also stop the nav mesh agent this node State will always be running so back in the enemy AI script declare a set color method with color as a parameter we will need a material reference so let's add it and initialize it in the start method in the set color method we will simply set the materials color to the one provided in the parameter let's also change color when an enemy is in the chase node to do that we need to add a reference for the enemy AI in the chase node back in unity create another script for the east cover available node and open this script as always inherit from the node class and implement the evaluate method this node needs a field that will be an array of all available covers to choose from we will create the cover class in a second also we need a reference to the target transform so the players transform and an enemy AI component so let's go back to unity and create a cover object this will be a simple cube scale to represent a wall next create a new script called cover and open it up each cover will have a reference to the transforms which will be children of the cover object and will represent spots to which an enemy can move when moving to the cover let's add a method to return those spots now move the cover script to the right folder and attach it as a component to the cover game object next create an empty game object under the cover came object and call it spot move it a little to the side of the cover with the forward vector pointing towards the cover next copy it and move it to the other side of the cover rotate it 180 degrees so that the forward vector points towards the cover now duplicate the cover game object multiple times and create a kind of a level layout with them let's go back to the east cover available node and the evaluate method implementation but first create a constructor for this class the evaluate method will first try to find the best spot to cover an enemy it will return success if it finds one otherwise it will return failure in the find best cover spot method we will iterate through all of the available covers and in each cover we will pick the best spot we will define a spot as being the best when having the lowest angle between its forward vector and the direction from this spot position to the player's position you could also take the distance from the enemy to cover into consideration but for simplicity sake let's only care about the angle so in the for loop we will call a method called find best spot in cover that will take a cover as parameter and an angle as a ref parameter if the current cover finds a better spot then it will assign this spot to the best spot variable which will be the return value in the find best spot in cover methods first we will get all spots in this cover and it's a read through them let's also declare a variable for the best spot in the loop we will first do a check for this spot with check if cover spot is valid we'll get to this method in a second if the spot is valid we calculate the angle between the spots forward vector and the direction to the player so to the target let's cache the direction in a variable if the angle is smaller than the previous smallest angle we will assign the calculated angle to the minimum angle variable here we can see why we needed a ref parameter next we assign the current spot variable to the best spot variable also the direction value has to be calculated inside the loop not outside and calculate the direction not from the covers position but from every spots position now let's discuss the check if spot is valid method so before we even calculate the angle we have to know if in fact an enemy will be covered from the player we will check it by performing a ray cast from the spots position in the target direction in of course in the place we we are checking if the cover is valid we should pass the spot not the cover in a parameter also I named it check if cover is valid I don't know why but let's call it check if spot is valid if we hit something in the raycast and it is not the target then we consider that spot valid we could pass the cover reference in the parameter too and say that the cover is valid only if Ray cast hits the cover it would be a better solution because right now we can hit another enemy and the ray cast will falsely tell us that the spot is valid now back in the evaluate method after we find the best cover spot we will set it to a variable in a enemy AI component that will create in a second so let's add a method in the enemy AI class called set best cover spot also at a reference field for that spot and in the set best cover spot method just assign the parameter to field variable in the enemy AI script let's add a array field that will hold all available covers back in unity let's create the last node called go to cover node it will be similar to the chase node so copy the chase note script and change its name we could even use the chase node here or think of some more generic solution like a go-to position node but let's leave that for now open V go to cover note script and change the class name we will not need a reference to the target so remove that field and the corresponding parameter in the constructor the difference between this node and the chase node is that we won't set the nav mesh agents destination to the players position but to the best cover position that was found in the east cover available now let's add a method to the enemy AI script that will return that spot next check if the spot is not now if it is return failure otherwise set the destination to the spots position now let's construct a behavior tree in the enemy AI script first add a reference to the top node and in the start method called the construct behavior tree method that we are going to implement right now let's bring up our tree graph to help us create appropriate nodes we are going to start with the custom nodes and later the composite nodes so first let's create the ice cover available node and pass the appropriate parameters in the constructor the next node will be the go to cover node for this node we will need a reference to the nav mesh agent component so let's add a field for that also of the Unity engine that AI namespace cache this component in the awake method back in unity at a nav mesh agent component to our enemy game object next select all of the covers and the ground object and mark it as static next go to the navigation tab and bake the nav mesh now that we have the nav mesh setup we can finish creating the go to cover node the next node will be the hell fold next created V is covered node next the chase node next the to range nodes first one with the chasing range in the parameter [Music] then the second range note with shooting range in the parameter then they should know now that we have all of our custom notes ready let's start assembling them in proper sequences and selectors we will create them from right to left and from the bottom to the top first let's take care of the chase sequence so create a new sequence that in its parameter will take a list of two notes the chasing Range note and the chase node now create the same kind of note for the shoot sequence but initialize the list in parameter with the shooting range note and the shoot node then similarly create the go to cover sequence that will be initialized with the list containing the s cover available note and go to cover node next created the find cover selector note the list in the parameter will be initialized with the just created go to cover sequence and the chase sequence here is an example of reusing the already existing part of the tree next create another selector for the try to take cover node we will initialize the list in the parameter with the find cover selector and is covered node now we can see why we need to create nodes from bottom to the top the higher nodes need to be initialized with lower nodes so we have to create those earlier now create the main cover sequence and initialize it with the try to find cover selector and the health node and in the end we can create the top knob that will be initialized with the main cover sequence the shoot sequence and the chase sequence as we can see constructing bigger trees would be a really tedious process if we did everything by hand that's why almost all behavior trees frameworks use node based editor windows that create nodes in a visual editor that is out of scope of this video but if you would like to see something like this please let me know in the comment section now in the update method we will call the evaluate method on the top node we could add some interval value so that we don't run the tree every frame but let's leave that for now we will also check if the whole tree returned failure which would mean that all of the child nodes returned failure in that case we will change the enemy color to red quick fix with the current health property we forgot to declare a private field for current health let's also remove the get current health methods we don't need it anymore we can access the current health from the property in the enemy AI script let's also add the onmousedown method this method will be called each time we click on the enemy object and we will damage the enemy by 20 points now go back to unity and fill all inspector fields in the enemy AI component let's set the starting curve to 100 the low health threshold to 50 the chasing range to 30 and the shooting range to 15 also assign the player transform now assign cover spots in the cover component for each cover in the scene [Music] next assign all covers to the available covers list in the enemy AI inspector and hit play as you can see the enemy starts to chase us and if it gets in the shooting courage he changes his color to green which means that he is in the shooting node when we click several times on the enemy object it starts to run to cover and changes its color to blue if we waits long enough the enemy will heal and stop looking for cover there is one problem every time we move the player the best cover might be changed and that will lead to the enemy changing directions when looking for cover every time we move to counter that we will not look for best cover if the previously selected will still cover us from the players side so let's go back to the s cover available note and in the find best cover spot method let's first check if there is a best spot selected previously if yes then we will check if that spot is still valid and if so we will return the previous best spot as the current best spot now when we hit play and make the enemy go to cover when we change the players position the enemy won't look for another cover until the player is on the same side of the cover as the selected spot now let's wait for the enemy to take the spot and now move the player to the same side and we can see the enemy is running away let's also in the update method in the enemy AI script stop the agent when all of the nodes return failure let's put some more enemies in the scene and hit play again there we go full AI based cubes are running around so now that you have the knowledge about the behavioral trees I strongly encourage you to write your own custom notes and create different behaviors and that's it guys you can find the whole project on github in the description below I hope you enjoyed it and please like and subscribe if you like my content there are new videos every Monday also leave a comment I'm happy to answer all of your questions take care
Info
Channel: GameDevChef
Views: 19,424
Rating: undefined out of 5
Keywords: Unity, Unity3d, Artificial intelligence, AI, behavior trees, unity tutorial, tutorial, Enemy AI, game development
Id: F-3nxJ2ANXg
Channel Id: undefined
Length: 33min 45sec (2025 seconds)
Published: Tue Jun 09 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.