Transcript for:
Building Believable Enemy AI with Goal-Oriented Action Planning

having believable enemy AI in your game is one of the most difficult things to pull off and one of the reasons is that it's too predictable and your players find it too easy to outsmart them GOP or goal oriented action planning is one of those techniques that can really take it to the next level but it's complex and can be hard to understand today we're going to talk about what gulp is for a few minutes and then spend the rest of the video building a GOP system from the ground up by the end of the video I hope you'll be able to take what we build today and run with it or you feel like you're in a place where you could jump into a third party tool with ease let's get started GOP was developed by Jeff Orin at MIT in the early 2000s and it was a significant leap in AI planning techniques I'll put a link to his original paper in the description now unlike traditional methods GOP was designed with the flexibility and complexity of real world decisionmaking in mind its successful implementation in the game fear said a new standard for NPC behavior and it showed an advanced level of autonomy and intelligence go shares a lot of similarities with a finite State machine but it diverges significantly in its approach in GP actions and goals are not rigidly tied together instead they're decoupled allowing for an Adaptive and dynamic planning process this means that instead of following a predetermined set of actions gulp allows agents to analyze their environment consider various actions at their disposal and devise a plan that best achieves their goals given the current situation the GOP system enables it to explore different strategies such as finding food trading with others or stealing each with its own set of preconditions effects and Associated costs this flexibility allows for multiple often unexpected solutions to emerge depending on the agent's current state and the environment each action within the GOP framework is defined by its preconditions what must be true for it to be executed and its effects the outcome of executing the action the plan system then stitches these actions together much like connecting dominoes forming a coherent plan that transitions the agent from its current state to the desired goal State the Brilliance of GP lies in its planning component which operates in a goal-driven manner working backwards from the desired goal to the current state this reverse engineering of plans ensures that every step taken is a step towards achieving the goal with the planner evaluating multiple paths and choosing the most efficient one implementing GP in a game AI doesn't just Elevate the realism and unpredictability of MPC behaviors it also significantly reduces the overhead in extending and enhancing AI capabilities adding new actions goals and beliefs becomes very easy this allows you to continually evolve and deepen the game's AI complexity with minimal effort let's have a quick think about the overall architecture before we start diving into into an implementation the GP agent is going to be the glue that joins all these things together but we're going to start by defining the collections of beliefs actions and goals that it's going to work with we're also going to create a few sensors so the GOP will know a little bit about its environment and once we have the beliefs actions and goals collected in the agent that Define the way we want them we can feed them into the planner which will use an algorithm to stitch all these things together and it's going to Output a plan for us the plan is actually going to be a stack of actions and we'll pop an action off the top of the stack as we work our way towards the goal the most difficult part of GP is wrapping your head around how this all comes together to make a planning agent that can reason about the world and come up with goals and often the best way to understand is to do so let's get started the agent beliefs are foundational to everything else so let's start there I'm going to give all the beliefs actions and goals their own name and this is mostly for debugging but if we were to build an editor tool as well later that'd be very useful all of the beliefs in this system can evaluate as true or false so I'm going to default the evaluation here to false and we can replace that as necessary a lot of agent beliefs are about a place in the game like where did I last see the player or where can I get food the default value of vector 3.0 could just mean this thing doesn't really have a location in the game it's just a belief that I have let's also expose this as a public property this way anytime we want to find out the location of this belief we'll re-evaluate the delegate I'm going to create a builder for this so I'm going to add a private Constructor that only accepts the name then I'll just add one more method here so that we can evaluate the condition and find out whether we believe something is true or not for now this is all we really need to encapsulate the agent's beliefs into one object let's scroll down a bit and implement this Builder let's have a reference to the belief that we're building then in the Constructor we'll accept the name for the belief and create a new one next let's add a method so that we can add a condition to this belief and let's add another method so that we can also add an optional location then we just need to build and we're all done now we're going to have quite a few beliefs for our agent so I'm going to make this even easier on myself by creating a belief Factory the factory is just going to have some information and methods that will help me create a dictionary full of beliefs with some nice short easy methods that I can call so the factory will need references to our agent and to our belief dictionary we can take those in through the Constructor so our first factory method can just be to create the most basic of all beliefs and that is just a true or false belief that doesn't care about location at all for this video I'm going to keep it simple and key the dictionary with the same name as the agent belief let's make some room and add one more Factory method so that we can create beliefs with locations easily I'm going to add a helper method here that will calculate whether or not I'm in range of something because I want these location beliefs to tell me enough information so that I can Reon reason about the location do I need to move towards it am I close enough to attack it things like that so now we can create a method where we just accept the key the distance to the location and the location itself this will allow us to build a logical predicate for our condition to say whether or not we actually are in range of this thing and then we can also set the location with the builder method now it might be useful also to pass in a transform instead of vector 3 so let's make an overload okay so nothing magical there we're just going to call the other method there's one more kind of belief I want to make and that's a belief about sensors and we're going to work on the sensor class in just a moment and that'll detect things as we're playing the game things that are happening around us like did the player come near me or did that health pack finally spawn so every sensor will have some kind of collider I think I'm going to go with sphere colliders for the most part and you can have multiple sensors on an agent and just going to make them all children of the agent so let's add a few serialized Fields here one is going to be how wide is this actual sensor going to be and we're going to set that on the Collider I'm going to want to keep track of things within the sensor radius as well and not just when they cross the collider boundary so I'm going to create a timer that will re-evaluate what's within the bounds of the sensor but it doesn't have to fire every frame let's add a field to keep a reference to this collider and also in event if we've targeted something with our sensor and it moves around we can fire the event to accommodate that let's have a reference for whatever it is that we've targeted that came within sensor range and let's also keep track of its last known position so that we can compare that if it starts moving so now we can expose some information that our belief system can use let's make some public properties here one is going to be the actual position of our Target or vector 30 if there is no Target and we can also have a Boolean that will tell us whether or not we're in range of this thing the only way we'll know the position is if that thing is actually cross the boundaries of our sphere collider let's give ourselves a little hand with some gizmos I think the target's in range we'll turn it red otherwise green no target let's create an awake method so that we can get everything initialized here starting with a reference to our sphere collider let's also make sure that we've got it set as a trigger we'll do that programmatically here and then we'll also make sure that the radius is correct next let's set up a reference for our countdown timer we'll do all the configuration for this in start we can instantiate a new timer using the time interval that we defined above when the countdown timer runs out we're going to want to check to see if something has changed so let's make a method for that real quick it could be that our target has actually left the sensor so let's have a optional default value of null so let scroll down a bit here what we want to do is set our Target Field to be the value of this parameter that got passed in then we can say if the target is in range and either the last known position is not the target position or the last known position is not Vector 3 then we can update the last known position to be the value of the target position property so now we can fire the event because we know that either the target is new or it's moved if we come back up to the start method now our countdown timer has an event on timer stop so when the timer has run out we can actually call this method and we can pass in Target with the extension method or null or null is an extension method that we built quite a few months ago in the video about extension methods so I'm going to have a link to that and there'll be links to other helpers like this countdown timer and so on in the description of this video the or null extension method is just verifying that an object is actually null and not just a Unity null which is a occasionally a false positive so when the timer's stopped we actually want to start it again so it runs again every 1 second or however often and we actually need to start it for the very first time as well and the way this timer works is we have to pass in Delta time every frame last thing to do is we need to set up some trigger colliders for this particular sensor right now I only care about the player so if it's anything other than the player just bail out otherwise we'll call the update Target position with no value it can use the default and that'll set the target to n on trigger enter very similar but this time instead we'll actually pass in the player that we collided with that's it the sensor class is complete now we can go make a belief about it so if we jump back over to the beliefs class we can replace this to-do here with a sensor belief Factory method this will capitalize on those two public properties that we Exposed on the sensor so our condition can be a delegate that evaluates that first property is the Target in range the other one of course will evaluate its location okay well that didn't take too long and now we have a really good way of representing everything that we know and believe about the game world let's move on to the things we can do in the game World which is Agent actions just like beliefs I'm going to give all actions their own string name actions will also have a cost that we can set how expensive is it to actually perform this action and as we talked about earlier all actions have preconditions that have to be met in order to be able to actually perform this action and when we're done performing the action there will be effects that take place so these are things that we need to believe are true before we start and things that we believe will be true when we finish we're going to use the strategy programming pattern in order to decouple what's actually happening from the action that means we can pump in whatever strategy we want to execute into an action no problem we won't make any concrete strategies yet but let's define the interface quickly so we need to know can we start executing the strategy is the strategy finished running and then we need three methodss one to run every time we start executing a strategy one when we're updating frame by frame so we can pass in Delta time in case the strategy needs it and we'll run one more method when we're stopping the strategy not all strategies are going to need these methods but they're going to get executed by the action anyway so I'm just going to put some default implementations right here in the interface mark them all as no op so now we can start making use of these strategies first thing I want to do is I want to expose a public property here so that we can tell the rest of the system whether or not this is actually finished running we'll know that the action is done when the strategy is complete then let's add in a few methods so that we can call the strategy start so when we call the action to start that'll actually start the strategy running same with stop then for update we need a little bit more special handling and I'm just going to write it right in between these two methods update will accept time. Delta time if we can actually perform the strategy then let's call its update method passing in that Delta time then at this point we can also check to see if is the strategy actually complete or not and if it's not complete let's just get out at this point we're finished just let it keep running if the strategy is finished running however we want to re-evaluate all of the effects that we think are going to happen when we were finished whatever this action was right so did we actually change the state of the world or not so this will re-evaluate all those beliefs that are stored in the effects hash set we need to construct this agent action somehow so I'm going to create a private Constructor and a builder just like I did for the beliefs but I'm going to go through it just a little bit faster first let's get a reference so we can store this action in then we need a public Constructor that'll take in the name we'll put a default cost of one and then I'm just going to add a method for every single one of the properties we might want to set for this then we build and return our action and that's really it an action just wraps up a strategy and it has preconditions and effects afterwards time for the last pillar which is goals what do we want to achieve just like the other classes we're going to give it a name unlike actions this one does not have a cost it has a priority level how important is this goal compared to other goals in addition to that we have a hash set of beliefs that are the things that we would like to come true if we can reach this goal now that's really all there is to a goal it's just a basically a list of outcomes what do I want the world to be like when I'm finished doing all these things I'm going to do so just like the others we're a private Constructor here and then I'll introduce a builder and it's really that simple there's I mean there's nothing more to say about that so we're done building the three basic building blocks that we need to build a GOP system so now we can start working on the agent and then the planning system then the agent is where we're going to pull all these different things together and make them one cohesive system this will let us expose some properties to the editor Let Us connect everything up and do all of our setup first of all I'm going to have two sensors on this particular agent one is a wider one to determine when something's in a Range that we could actually Chase it and the other one will be when we're close enough to actually attack something then I have some static things in the world I want the agent to always know about where it can go to rest where it can go to eat some doorways that should try and go through the doorways are there mostly so I can just test some longer action plans we're also going to need references to all the components on this particular agent the animation controller class is a custom class I've got to running all my animations so I don't want to spend any time on it but I did want to show one thing that's in there quickly at that's at the very bottom if you ever needed to know the duration of a particular animation clip that's being referenced by your animator you can search through the runtime animator controller all the animation Clips find the one that matches your hash and you can get the clip length out of there but this video is not really about animation so let's move on I'm going to add a very naive implementation of some statistics for this agent so that it has some health and some stamina these will degrade over time and force the agent to reevaluate his goals I'm going to set up a timer for these as well because I'm probably going to eventually move these stats into a proper you know a proper class to handle them Beyond this the agent's going to need a few other fields to keep track of things one would be its Target it might also have a destination because it's going to be a Naish agent now onto its beliefs actions and goals I always want to keep track of the last goal that it tried to achieve just to try to make sure that it's not trying to achieve the same goal all the time like if it only has low priority goals I don't want it wandering all the time it should be wandering or idling or going on a patrol or things like that so we'll also want to keep track of the current goal I'm going to make some of these variables public because I want to show them in an editor while we're testing things out so we need the current goal we're going to have an action plan that's basically going to be a stack of actions that we're just going to pop off one at a time as we complete the plan as we're popping things off the stack we'll put them into the current action so it's clear to us in the editor what the agent is trying to do then we need storage for our dictionary of beliefs a hash set for our actions and a hash set for our goals okay let's set up some methods in a wake we can get references to all of the components that are going to be on this agent I'm going to set freeze rotations on this rigid body as well then I'm going to use the start method to kind of orchestrate all the setup and I got four things want to set up the first one is timers and the timers right now is just used to degrade those stats but we also need to set up the beliefs we need to set up the actions and we also need to set up our goals and we can do those all one by one the beliefs have to come first because the other two depend on them but uh we can set them all up in their own methods I'm also going to use onenable and on disable to handle registering to events on the sensors the chase sensor is the sensor that has the bigger radius so I'm going to subscribe to that one and if something changes within that particular sensor that's a good time to either re-evaluate what I'm currently doing or re-evaluate the entire plan so let's make a method for this that will force the planner to reassess everything just and all we have to do there is say I'm not doing anything anymore current action is null and current goal is null with no action and no goal the planner is going to kick into effect and try to find something for this agent to do so I think we're at the point where we can set up these timers beliefs actions and goals and then we'll have enough to work with to actually Implement a planning algorithm I really only need one timer right now so let's create a setup timers method and all it's going to do is change those stats for me over time so that we can start having some different beliefs about the condition of the agent so this is just like the other timer except I'm going to have it go every two seconds it'll update the stats by some amount now the catches I only want the agent to receive healing or stamina when it's in range of a certain location and I've set up two little locations that it needs to travel to in order to make this happen and that'll lead to a more interesting action plan so when it's in range of one of these locations it gets a positive benefit and if it's not in range it's going to start degrading so stamina and health will work the same but at different amounts and I'm going to clamp those values between Z and 100 for both of them that's it for the timers I'm just going to put a note here for myself to you know come back and move this off into its own module let's up a few beliefs here so first of all we need to initialize our dictionary then we need to get ourselves a new belief Factory so we can start creating beliefs the first belief that I'm going to create might surprise some of you and that belief is nothing so it always evaluates as false so something that always evaluates as false suppose that's a goal that you have then you will always be able to achieve that goal because it's nothing you could also conversely have a something belief that is always true but I haven't found a use for that yet I want the agent to be able to reason about whether it's idling or moving around and we can do that just by evaluating properties on the nav mesh agent okay that's enough beliefs for us to get started let's set up a few actions so I'll create a new method here and the first thing I want to do is initialize the actions hashset and then I'm just going to create two actions that we can do for now one is going to be just to relax so relax will be an action that uses a strategy called idle strategy that we're going to make in one moment and the effect or the outcome of this is nothing absolutely nothing in the world is going to change if we just stand around now on the other hand we can make an action that'll get us to wander around and the wander action will believe that we are actually moving which if you look up above there in our beliefs means that the nav agent actually has a path so let's also Define some goals what is it that we're trying to achieve right now well first of all let's initialize the hashset for the goals but then let's set up one goal would be to actually do nothing and so that could just be to we'll call it chill out chill out has a low priority of one and the desired effect of achieving this goal is nothing let's have one more goal that will be to wander around this can also be a low priority goal and the desired outcome of achieving this goal is that our agent is moving and we believe that's true remember that this belief is true if the nav mesh agent has a path okay well we have two strategies to implement so let's go have a look at that I'm just going to collapse up this interface since we don't really need to look at it anymore and just below that let's Implement an idle strategy now this is going to be super simple we can say that can preform is always true because we can always just stand around nothing's going to stop us from doing that and we'll set complete when we're done running a timer of whatever the time was that we passed in now believe I set 5 seconds for this we'll pass in the duration we'll set up the timer when the timer starts we'll make sure that complete is false but when it stops we'll set it to be true and that'll be the end of this now we can use some of those noop interface methods to a start the timer and B on every update we can pass our timer Delta time which it needs to keep running but in this case we don't need to stop it because the timer is going to handle the stop condition for us so that's it this is a small simple idle strategy let's make a nearly as simple wander strategy basically the wander strategy is going to need to know about the nav agent and control it a little bit we can confine it to some little radius to wander around now we can perform this as long as we haven't actually completed our wandering and we'll know we're completed the wandering when our remaining distance is small and we don't have some path pending let's pass in the stuff we need through the Constructor then in our start method we need to actually choose a path and sometimes you can't find a path Maybe there's a little bit of obstacles or whatever what we can do is have a loop we'll check five times to try to find a hit on the nav mesh within this radius and if we can find one that's going to become our destination the vector 3 with extension method is in the library that I mentioned earlier okay now we have beliefs actions goals and strategies for all of our actions it's time to build the last thing we need which is our GOP planner so just before we start writing the planner we actually need somewhere to store our plan so let's make a construct for that the action plan will need to know what goal it is we're trying to solve and it's going to have a stack of actions we can take to actually reach that goal we're going to have a total cost of executing this plan which will be the sum of all the costs of the actions and we can just pass all those things in through the Constructor now let's create an interface for our planner it's actually going to only have one method but it's a great place to introduce a seam in your code so that if you wanted to have a mock plan for testing you could just inject a mock instead of the actual planner so this one method we can just call plan and it's going to return one of these action plans and to calculate a plan we need to know about the agent all of its goals and what its most recent goal was so let's set up our concrete implementation here the first thing I want to do is take the goals that we passed in and order them in priority from the most important to the least I also don't want to include goals where all of the effect effect of the goal are already complete there's no point in making a plan for a goal that's already been achieved so let's use Link's wear method to filter those ones out next we'll use order by descending to figure out which ones are top priority now here I'm going to use a turnery operator on the most recent goal and this is just so I don't continually try to grab the same goal all the time if it was a goal that we recently tried to achieve will just give it a slightly smaller priority than all the other goals of the same priority level so we'll just convert that to a list and now we can cycle through them and try to find Solutions so our planner is going to use depth first search in this video and to do that we're going to have to have some little constructs so we can create a graph of nodes so let's create a new class here node every node needs to know about its parent it needs to know what action it actually represents it needs to know all of its beliefs at this position in the graph and it needs to know all of its child leaves and it needs to have a c a running cost of how expensive it is at this point we can have a helper property here that will tell us whether or not this Leaf is dead which means it has no children and it doesn't even have an action this will be useful later on and then we can just take all this information in through the Constructor notice that I'm creating a brand new hash set out of the effects that I pass in that's because as we progress down every single branch in the graph we will have satisfied the conditions of some of the beliefs but we'll find new beliefs that we need to satisfy let's come back up to our Loop and see how this is going to work so the very first node that we create is going to be the final State our goal State you could work from the start and work forward and see if you can actually achieve a goal but I think it's better to start with a goal in mind knowing that you're working from the top priority and work backwards to see if you can even achieve it so our first node has no parent and no action yet but we know what its desired effects are that's the outcomes we want for reaching the goal so now that we have the very tip of our graph we can start a depth first recursive search and we're going to do the recursion in a method called find path we'll pass in the goal node and all of our available actions we'll come back here and finish this little block of logic after we've done our recursive method here so the recursive method is going to return true if it actually was able to find a complete path if it manages to run through every possible branch and doesn't find a path of course we're going to come back with a false so so let's Loop over every action that we've passed in let's have a new variable that we can mutate that has all the prerequisites of the parent we'll start by removing any of those beliefs that actually evaluate as true because that means there's no action that we have to take to actually have them succeed they're already done we can use the remove wear method to get rid of all of those ones that evaluate as true now if that required effects is now empty then there is actually nothing to do at this point in the graph that means every single prerequisite all the way up the chain has been fulfilled and we can return true because we have a working plan so now let's check to see if this particular action has any effects that actually match up with the required effects if it does then this is a candidate we should explore but first we can remove all of the effects that would be satisfied and we can add all of the preconditions of this action so we'll have a brand new set of requirements I hope that makes sense maybe it's easier just to read the code we've got the list of requirements we remove all the action effects and we add all the action preconditions next I'm just going to remove the action that we're using in this node from our available actions because I don't really want to use it more than once in a particular plan although depending on how complicated your plans get you may need that so you could just leave this part out but only if you want to use an action more than once so now we've decided we're going to use this action let's make a node for it so the node needs to know about its parent this action the new required effects and we're going to start totaling up the cost as we keep going down into the recursion so now we'll make another recursive call using this new node and our new available actions so if that recursive call evaluates to true it means we've found a solution and we're coming out with a positive answer and so we can start adding each node into the leaves of the parent we can also clear out all the preconditions of that node because we know they were satisfied so at this point if there is nothing left in that new required effects variable it means that we have found a solution for every possible pre- condition in this list it means that we have a plan that we can attempt let's return true if we actually finish looping through all the actions and we're unable to find any branches where we can satisfy all the preconditions then we can't meet this goal there's no point in attempting it let's try a different one we can return false here and get out of the recursion that'll kick us back up to the other method where we can choose a different goal in the in the sequence of ordered goals so let's come back up to where I left that work in progress note right where we entered the recursion from the first point we can say that if we came back and the goal node is dead that means that everything was already a success for this goal and there's nothing to do so just pick a different one otherwise let's start making our action stack out of the nodes essentially we're going to just walk from the goal node all the way down to the last Leaf I'm going to add a little bit of code here that will just sort them by cost just in case this search becomes more comprehensive than just the deeper search that we have going on right now we might want to figure out which one is actually the cheapest and use that let's step into that first leaf and then we can push its action into the stack when we reach the end of the graph we're done and we can just return a new action plan pass in our goal the action stack and the cost just one more thing to think about if we iterate over every single goal that we have but we didn't find any plans let's debug out a warning here and we can return null wow that was a lot of code and guess what we're almost finished let's jump back to the agent where we can now use this planner to actually make a plan and then we can test this thing out so let's get a reference to the interface here and in awake we can just create a new one in the source code I'll have a factory that you can inject that will provide this but for now just for this video let's just keep it simple now we need to add an update method here in this mono Behavior to handle the loop might as well do it down here at the bottom where there's lots of room so first of all there's a couple things that I added before that need to tick off during the update and that is first of all I have a timer to update but I also want to continually update the speed in my animation system so that the uh movement animations are playing correctly okay now we can say if we don't have a current action going on let's calculate a plan we can separate that logic out into another method here so I'll just scroll down a bit and we can do that and come back up here for figuring out what to do with the plan after we've calculated it so in calculate plan first I want to know the priority level if I have a current goal going on I want to know what that priority is otherwise we'll say zero and that's because I don't want to supersede my current goal with some lower priority goal for example I don't want to switch to WAND if I'm currently working on attacking the player so let's put all of our goals into a new hash set but we can say if the current goal is not null then let's revise that actually and change it to be all of the goals where the priority level is actually higher than this one now that we have a list of goals to check we can actually call the planner execute the plan method and pass in all the things that we need when we get the potential plan back as long as it wasn't null we can set our action plan to be that new plan okay now back up to the update Loop let's finish this off we've calculated the plan so we can say as long as the plan is not null and it still has actions to perform let's reset our navmesh agent we'll set our current goal to whatever the goal is in the plan we'll set our current action to be the the action that's at the top of the stack and then we'll start that action let's put a little bit of debugging in here for good measure okay so now we've got an action started but what do we have to do to handle a an action that's in progress or one that's going to stop let's do that next here so here we can say if the action plan is not null and the current action is not null let's make sure that we pass in time. Delta time to that current actions update method now at this point if it's complete we can run the stop method on the current action and we can also say if there are no more actions in the stack then we've actually completed the plan so we can set the last goal to be the current goal we can set the current goal to be null and you know what we might as well set the current action to be null as well cuz we're finished time to look for a new plan okay are you ready to see what all that hard work got for us let's quickly have a look at this inspector I've already Dragged In all my references and I've set the health and stamina to be 100 and you can see below that I've got some custom editor stuff going on and this is just going to show us what our beliefs are the current plan and so on and so forth in the scene view you see I've got a few location set up right now we only set up two goals which is idle and wander so we're not going to be using any of that quite yet but we are going to get to that underneath my Cactus agent in the hierarchy you can see I have two sensors one is a Chase sensor and the other is the attack sensor I'm just going to turn gizmos on quickly here the chase sensor is the big wide one so I've set that at 10 right now and the attack one is much smaller I've just set it at two so that's the little sphere around the middle okay well given that we only have two goals to test out right now let's try it out I'm just going to pause for one second and quickly zoom in let's have a look at the bottom of the inspect here you can see his current goal was selected as chill out and his current action was relaxed that was the only thing in the stack so the stack is now empty and he has two beliefs you can see that idle one is true and the moving one is false which is correct let's let him finish relaxing and see what else he gets up to remember that we have logic in place that won't let him select the same goal twice okay there he goes he picked the next one he wandered a little bit and then went right back to chill out and he'll just repeat that he'll go wander somewhere else and and relax for a while now we don't have any goals in place to deal with his decreasing health and stamina and we don't have anything that's going to make him want to be aggressive towards the player or anything else so why don't we add a few more beliefs and a few more actions and a couple new goals and then we'll see if he'll behave in a little bit more of a sophisticated manner I noticed that we were popping actions out of the stack before we're actually showing the debug log so let's just move these around a little bit so they're accurate in in the console there we go now let's go over to the beliefs section and add a few more I want to be able to try a plan that has two actions in it but I already know all the beliefs I want to have for the demo so let's put them all in here quickly and just see how it's all going to work and come together so one belief that we could have is that our health is low so if health is below 30 then that's true but if we think that the agent is healthy maybe that's or greater or equal to 50 do something similar for stamina here stamina is less than 10 we should do something about it or we could say we're rested if it's greater or equal to 50 now some location beliefs are we at the first door are we at the second door are we at let's see the resting position that's the Oasis in the game or are we at the Food Shack so we can just check are we in range of these things lastly let's add some ones about the player is the player within the chase sensor range is he within the attack range and are we actually attacking the player Now by by the same token we could just use the nothing belief but this is a little bit more verbose and we always want to be able to attack the player so we are never going to set this condition to be true let's move on to some actions to support this so in order to be able to stay healthy we need to be able to eat something and to do that we have to move over to the Food Shack so we're going to need a new strategy that's a little bit like wander but it's going to move with intention to a specific place and if we complete that move strategy we're going to believe that we're at the Food Shack another action is if we're at the Food Shack as a precondition then we can we we want to eat but for now we can just idle there for 5 seconds and then we will believe that we are healthy now it's my plan to start using the command pattern to feed into this particular strategy so the strategy will execute the command but for now this is good enough for the demo before we make the move strategy let's define the goal so the goal is to be healthy so let's come down here and right under the last goal here we'll add one more and this goal is to keep the health up and the desired effect is that the agent is healthy which matches with our eating outcome but we're going to set this to a priority of two so this is more important than wandering around or idling okay let's set up a move strategy the move strategy very very similar to The Wander strategy we need to know about the nav mesh agent but we need to know where we're going so the can perform and the complete properties are exactly the same as The Wander strategy we'll set everything up with the Constructor but here in start we're just going to set our destination and when we stop we'll just reset the path that's it very simple one okay back here in unity there's nothing to do except I'm going to turn the health down a little bit just so he we don't have to wait around for his health to tick down here we go he doesn't need to eat yet so he's just chilling out there he goes he's hungry now so he's heading right over to the Food Shack we can see in the inspector that he's got one action left in the queue and there it comes he popped it and now he's eating so he's going to hang out here for a few seconds and then he'll go back to doing whatever he was doing so he's back in the chill out and yeah now he's going to wander so he goes a little ways and relaxes again okay so we have a two-step plan no problem let's take a quick look at this log too because we can see that there was an attempt to try and find a new plan as he was changing actions because we don't have any goals that are more important to him than eating he didn't even try to find another plan he just kept going with his existing plan and popped the next action which was to eat and that's it so this thing is working great but let's keep testing it with something a little bit more advanced okay so next I want to add something that will take at least three steps to do so first of all let's have some actions that'll let him move to the two different doors so moving to Door one is an action and so is moving to door two let's also add two options for moving from door one to the rest area and door two to the rest area the rest area being the palm trees notice that I'm giving the bigger door door two a cost of two so that's the more expensive action to take the agent will always choose or he should always choose option one because it's cheaper we need one more action and that's for when the agent finally gets to the resting position he can actually rest and instead of doing a command here once again we'll just idle for 5 seconds and then let him keep going let's add a couple more actions to deal with the player one is going to be if the player comes into Chase range let's start chasing him we've already got a move strategy so when we have a belief that he's in the chase range we know where he is the outcome of chasing the player should be that he's now in attack range and so we need one more action and that is to actually attack him I haven't made an attack strategy yet we'll get to that in just a moment the outcome of that of course is that he's attacking the player which we've already decided will never become true let's set up two more goals for the agent the first goal should be that the agent always wants to stay rested up so keep stamina up will be the goal this will have the same priority as keeping Health up and the outcome will be that the agent is rested the other one will call Seek and Destroy will have an even higher priority number three which will be that the agent is attacking the player so whenever the agent has the opportunity that should be the goal that it pursu s let's quickly make an attack strategy for now I'm not actually going to do any attacking but I do want to play the attack animation Camp perform will always be true the agent should always be able to attack and will set complete when the animation is finished to do that I'm going to start a timer then let's keep a reference to the animation controller we can pass that in through the Constructor then we can set that but also in the Constructor let's configure the countdown timer so I have a method as I pointed out ear earlier in the animation controller that gets the animation length and that's going to be the length of this timer then in the start method we start the timer we tell the animation to begin and on update let's pass in Delta time so we can keep that timer running just before we go back to testing there's one thing I forgot and that's to order the actions by cost in our find path method so I'm just going to use the order by Method and we'll pass that into our 4 instead of just the actions ordered as they were defined really that's it Okay so we've got two tests to perform this time one is that he will choose the smaller door and execute a three-step plan so first of all he's going to eat here because he's already hungry I'm just going to get out of his way the second test I want to do is I want to make sure that he's actually going to interrupt to a priority two goal to chase me around so there we go he's heading for what does it say moving to Door one yeah perfect okay so he gets to the door door and now he's going to go rest so at this point I might as well think about going to bother him he's probably going to go for food after this yeah there he goes so he's looking for something to eat let's go interrupt his meal okay there I'm in range so he starts chasing he wants to attack he hasn't popped it off the stack yet let's let him get close there he goes takes a swing at me it looked like he took one more swing at me so we might just have to add one more pre condition to the attacking to make sure that the attack sensor uh notes that it has the player as a Target what we could do maybe about that is just verify all the preconditions of an action before we actually start it why don't we add one more line of code to do that and we'll test it one more time and then we'll be done otherwise this is looking great co-pilot kind of had the right idea but not quite there let's see okay it looks good on the else condition we'll just reset everything if the preconditions are not met we better test drive this one more time though okay so I'll just press play and let me go and engage him right away so the idea here is I want him just to go back into Chase mode as soon as I run away not try to attack me again so yeah there you goes chasing okay I'm happy with that I just want to do one more thing quickly before we go and that is I have some comments I'm going to add into the code and I just want to talk about them briefly and about potential improvements or extensions to all of this I think the biggest Improvement that would make this so easy to use is to build a node-based editor for all the strategies beliefs actions and goals if you have a third party tool like node canvas it wouldn't be too hard to integrate with that next and I'm just going to do it right now is I would go over to the GOP agent and I would add dependency injection or use a service locator here and that way it would be super easy to start testing each of these things in isolation I'm going to put the factory into the repository as well but let's just have a quick look at it it's registering the factory both with the service locator and using injection so you could just use whichever one and the last suggestion I have for right now is if you were to jump over to the find path method we're using a DFS but there's nothing stopping you from using something a little more sophisticated like AAR or even darar to figure out the path that you want to use of course there's assets on the store that'll handle that too if you don't want to do it yourself I'm also going to put this custom Spector in the repository as well one last thing is there's two pretty good tools on the asset store that are worth checking out escope is a paid solution that I've had for a long time and I really like but there's another one that's worth checking out as well that's totally free in open source and so have a look if either of those interest you my hope with this video is that you'll either be comfortable enough to start using one of those two tools and customizing it yourself or you'll be able to take the solution we built here today and make it your own well this is the longest video I've ever put on the channel so we'll see how it's uh we'll see how it's received if you made it all the way to the end I salute you and with that I'm going to head for bed soon as I've scheduled the video that is if you're not tired of watching my channel I'll have some boxes up on the screen for you and I'll see you there