GOAP Enemy AI FULL IMPLEMENTATION | AI Series 50 | Unity Tutorial

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hold on to your butts in this video we're going to do a full implementation of an eneme AI using go hey Chris you're from LL Academy here to help you yes you make your Game Dev dreams become a reality by helping you see full implementations of enemy AI using various Frameworks such as go I'll still be using the free under the Apache 2.0 license which is very permissive GOP system for Unity by crash conagen there's going to be a little the Nuance differences on how you implement different things with any system so you can use this as a template or you can use this as a real concrete implementation if you're using this Unity GOP system really quickly in case you just didn't watch the last video AI series part 49 where we did an introduction to GOP I'll go over the different highle concept things really quickly right now using GOP we Define goals that we want our AI to achieve not States we Define all the actions that a particular agent can do and then the scope system uses the actions along with the cost of doing a particular action the effects that that action will have on the world State and determines which plan of action should they take to achieve some particular goal with all AI Frameworks we also have sensors that inform the AI about their knowledge of the world state or memory so it can make decisions based on if you're using a state machine or behavior tree it can make decisions on which behavior to go to or which state to transition to the GOP system will use that data to determine which actions to per form if you haven't already I highly recommend you go back to AI series part 49 that I released a couple weeks ago to get a little bit more understanding about all of these topics before you watch this full implementation by the end of this video we're going to have an enemy AI llama that can do three different goals it can have a wander goal a kill the player goal and an eat goal which is really about as small of a number of goals I would recommend for implementing anything in GOP because as you'll see in this video there's a lot of things you have to do in a GOP system because it's a more complex system and a much more robust system than some of these other Frameworks the enemy AI will be able to wander it will be able to eat it'll be able to do two different types of attacks a melee attack and a ranged attack the ranged attack which is obviously spitting will require that it has saliva and if the Llama gets too hungry it'll give up trying to kill the player and go eat so it gets less hungry and gains some more saliva let's get started since we're using this GOP system we need to install it with the package manager and the installation section on GitHub we can just copy this URL and in the package manager click this plus button add by get URL and paste the URL this requires that you have get installed it'll take a second install all this stuff and you should also have some samples in your assets folder we already know all of the components we're going to need so to keep ourselves organized in the scripts folder we can create a bunch of folders for all of the things we're going to create like actions behaviors factories goals sensors targets and World keys I highly recommend you follow a folder structure similar to this where we have GOP and then all these subfolders with all the things we're going to be creating that helps keeps us organized as we go through this process I'm going to be creating all of these in my IDE because it's a little bit faster than doing it in the editor we're going to start off with a wander goal which is a little bit of a weird thing because it's kind of a fallback first thing we'll do is create a goal so in the goals folder weekend create ourselves a new class and call it wander goal we're going to want this to extend the goal Base Class there's not really a lot going on in the goal it's just an identifier for what our goal should be so we don't need to add any code here if our AI wants to be wandering they need to know where should they wander to we Define where something should go with a Target so let's create a wander Target we're going to make it extend a Target key base and much like goals targets don't really have a lot going on in them they're just an identifier if we want to wander we also need a wander Target a wander action and a wander Target sensor cuz we need to know where should we be going what should we be doing and how should we know where we're going to go we may naturally want to start with an action but it's really easier to start with a Target sensor and then move our way down to the actions CU we end up needing to know how do we get to that Target before we start working on our actual action so after our Target I like to go to the sensor and let's make a wander Target sensor the sensor is how the GOP system understands the world around it and there's two kinds of sensors there's Global and local local means that it applies to this specific agent or this specific AI unit in this implementation we're not going to be using any Global sensors we could technically maybe make a player a global sensor but if you had multiplayer something like that later you need to convert that to a local because there could be more than one so if you have some Global State such as maybe if it's data time or nighttime or some factions are Waring and they have different things going on those might be Global sensors if I want to find a Target to move to that's going to be a local sensor so for a Target sensor where we want to find a position we're going to extend the local Target sensor base this has some key events that we need to implement created update and sense created is obviously called when it's created update is called much like the unity monob Behavior update and sense is called only when the GOP system is trying to plan a new action that tells you that the world state that the GOP system has internally may be a little bit out of date because it's only going to sense so often it doesn't sense every single frame which is actually a lot better for our performance and most of the time we don't care that it's maybe a little outdated information because the AI only needs to know about what's going on when it wants to make a decision about which action it should take for wandering I don't think we need anything in create or update and for sensing we're going to want to pick a position and then return it so the ey Target will return a new position Target with some Vector 3 and how might we determine where we should go well we just pick a random position around the agent but because we're going to be using a nav mesh we need to make sure that we pick a viable location because if we're really close to the edge of the nav mesh we may end up picking a location that's not feasible so I'm going to do this maybe a little bit more complicated than you might initially think to do just to make sure we don't run into a situation where we try to pick a location where we can't go anywhere or at least try to mitigate that so we'll call get random position passing in the imono agent the imono agent has information such as the transform position of the current agent in here we'll Define a count and I'm going to say while count is less than five so we'll just try up to five times and if we can't find one we'll just give up and use the agent's current position we'll pick a vector 2 random equals random. inside unit circle times I'm arbitrarily picking five here which will give us somewhere between a very small number and five units away from the agent we'll take the agent's current position and add in this random amount on the XZ plane only and then we'll do a nav mesh sample position based on the position that we just calculated passing out a nav mesh hit and we'll only sample with a radius of one and say Naf mesh all areas for the area mask you'd most likely want to use the area mask that this agent can travel on but in this example we only have one type of agent so we can just use all areas if that returns true that means we did find a location on the nap mesh and we'll use that position if that doesn't find anything we'll increase count and we'll go through this Loop up to five times and if we end up not finding anything we'll just use the agent's current position next let's make our wander action it's important to remember that the actions are discrete things that an agent can do in order to achieve a specific goal this should extend the action based class and really this should also have some data type provided we're going to make a class called common data let's define that before we go any further common data should implement the I action data interface which gives us an IT Target and in most cases we're going to want a timer so I'm going to go ahead and add that in here this is just going to be a float that we subtract the time. Delta time from every frame let's hop back to wander action now we'll see it's complaining because we didn't implement and all of our inherited members so let's go and Implement those there's four created start perform and end much like we just saw created is called when this action is created so you can do any initial data setup we're not going to need to do anything there so we can leave that alone on start but want to assign that timer we can pick a random value 0 to two this is how long we'll wait before we try to make a new decision so actually maybe 1 to two would make more sense in start this is called whenever we enter into wanting to do the action so every time we transition into The Wanderer action where the agent's going to start executing this we call start and then perform later will be called every frame down here in perform we will subtract equal context. Delta time I put equals we're going to address that in just a second should be minus equal context. Delta time and if the data. timer is greater than zero will return action Run State continue indicating to the GOP system we're still running this thing keep running this action and at the end we'll do return action Run state. Stop meaning if we've gone equal to or lower than zero on that timer we're going to say hey we're done with this action which will indicate that we should call end and for this action we don't need to do anything here so now we have a goal a target a Target sensor and an action so we're done right no we haven't done anything to make the agent actually move and this is a little bit why wandering is slightly weird like I was talking about earlier normally the action is where you perform whatever Behavior you want them to do but moving is something that most actions require you to do so instead of having wander do move and have every single action handle moving in some capacity what we can do is have a generic Behavior agent move behavior that will handle the movement based on the targets so let's make that agent move behavior and then we'll set up the GOP system to actually use these actions and stuff that we just created I'm going to say that the agent move behavior requires an AP mesh agent in an animator and we're going to have it be a mono Behavior we'll Define a nav mesh agent nav mesh agent and an animator animator and assign those on a wake there's one other component that we're going to need on this agent move behavior and that's something called the agent Behavior so let's also make sure that's required we'll define a class member variable for that and assign it on a we the agent behavior is a really critical class for us it knows about our GOP set Behavior which events will be raised what our current state is this is basically how we interact with the GOP system from an AI agent perspective you can think of it similar to a nav mesh agent where we use the nav mesh agent to interact with a navigation system and to read values about how far it has to go what the current path is that kind of stuff the agent behavior is like the nav mesh agent for the GOP system it tells us all about everything we know about this agent as it relates to the GOP system importantly it has some events that get raised such as when we receive a Target that shows up in range when our target has changed and when our target has gone out of range let's assign some handlers for those on enable on disable we will remove all of these handlers let's start with when we get a Target is labeled as in range we want to keep a reference to this target so we can do something like set the destination to that current Target position similarly when the target has changed we'll probably want to do that same thing and we'll probably want to set our destination to that position and if we're we're going to set our destination we also want to make sure that our AI is animated so we'll set an animator ghoul walk to be true we can use animator string to Hash to make it where it's a little bit faster to set these parameters maybe when the target is gone out of range we'd want to set the walking Boolean to be false you know what I don't think we need this Target in range let's delete that cuz when the target changes we can set that destination we'll be okay this target is most likely going to be a transform Target so if the player enters the range we're going to want our AI to chase the player not just to move to where they were when they first got assigned right we probably want our AI to follow the player which means we need to check periodically if the player has moved some distance and then set the destination to that new position I think then we need to use the update function for this we can make private Voight update check if current Target is null if it is just return because that means we don't have a Target we don't don't want to do anything and then set the destination to that new position and we probably also want to make sure that we're not saying that we're walking if we're standing still so we can set the animator bu walk to be a value of if the nav mesh agent velocity magnitude is greater than 0.1 this is a little bit inefficient though because we're going to be setting the destination every single frame even if the player hasn't moved so we can put some kind of a throttle here a private float Min move distance maybe we can set that to 0.25 by default this also means we need to keep of the last position so let's make a private Vector 3 last position whenever we change targets we'll set the last position to be the current position and instead of setting the destination every frame and update we can check if the Min move distance is less than equal to the distance between the current Target and the last position if so we assign last position and then set the destination so this will make it where if the player is not moving we don't set the destination of the agent anymore now we have a generic way of handling movement that's that's not necessarily tied to our wander config let's move on to something we call a factory which is where we'll set up our goals our actions and our sensors we're going to make a class called GOP set config Factory and make that extend the GOP set factory base implement the missing members which is just create it's important here to talk about what is a go set Pig Factory so a go set config Factory is a factory so if you're not familiar with a factory pattern it's basically something that creates objects for you and you can see here we have create that returns an iope set config a GOP set is like an agent type so in this case we have a llama so we're going to call it the Llama GOP set something like that which will Define everything about this AI about the Llama AI it'll have the goals the sensors everything that the go system needs to know about for this llama AI for their action sensors effects everything basically everything you see here on the left side so that's why it may be easier to think about it as an agent type again similar to the nav mesh agent we have different agent types with different configurations like different radiuses different turn speeds different speeds here we're defining the AI configuration about the goals the actions the effects the targets and how they all relate to one another so in here we need a GP set builder we'll give it the name of llama set because it's a GOP set and at the end we will call builder. build this is a common pattern here we have a factory that uses a builder to build something and outputs you the result at the end these are really common design p patterns that if you're not familiar with I'll have some links in the description that talk about what is a factory what is a builder and how do they generally work actually when I went to college is when I first learned about the gang of four patterns and several other design patterns and that's where I'd like to talk to you about today's video sponsor Southern New Hampshire University or snhu snhu provides lowcost affordable game development and computer science degrees that can help you level up your game dep skills maybe you may not know I have a software engineer engineering degree and I feel like it's added a lot of value just going to college getting that degree help me understand how does software work how do things like State machines work for example and how do video games work which is why I'm partnering with snhu to bring you information about how you can get a college degree from an accredited University with two accredited degree programs that I want to talk to you about today one is a computer science degree which is very similar to the software engineering degree that I have and a game design and game development degree if you're more interested in specializing in game design and development in that degree program you'll learn things like what we talked about on this channel about how to implement things with gameplay AI physics all of these things that we talk about on this channel you'll get structured lectures from University professors who have real world experience you can get either these degrees online through snhu at a really affordable price if you're interested in learning more about these degree programs you can go to snhu.edu LOM Academy fill out the form there and a real person will contact you to help you understand how these degree programs can specifically help you again that's snhu.edu LOM Academy go there fill out a contact form and someone will contact you with more information about the specific degree program that you're interested in because we know we're going to have multiple goals actions and sensors we have to hook up I'm going to make some functions here just saying build goals provide the Builder build actions providing the Builder and build sensors providing the Builder we'll start by defining build goals build actions and build sensors we'll start with the goal and we'll add a wander goal you might think okay great I have a goal but every goal needs a condition attached to it indicating when the GOP system should consider that this is successful I've successfully completed my goal so what could our condition be again this is one that's a little bit weird because we're wandering and you don't really successfully I mean you can successfully wander but we want to keep wandering I'm going to add in here something called is wandering we'll say comparison greater than or equal to one and let's make that is wandering that's what we call a world key which should just extend the world key base class again this is much like our goals and our targets there's not really a lot going on there there's just an identifier the world Keys Define the conditions or the world state that we should be comparing some stuff to to determine if our goals were successful or not now we can import that is wandering here and we've successfully added a goal in build actions we can do builder. addaction saying it's going to have the wander action it's going to set the target to be that wander Target we created and the effect would be to increase is wandering which would tell it that this is the right thing to do to achieve this condition we can set a base cost and an in range for wander they're a little bit less important but setting a base cost of five means it's a little bit expensive and in range tells us when are we in range to do this action I'm picking 10 for fun and we'll see in a second that 10 is not a good value for this and we'll talk about why that is in a minute for the sensor We'll add the wander Target sensor and we'll set the target to be the wander Target so hopefully here you can see this is this Factory is how we link all of these things we just created together we have our goal that we can tell if we meant or not with our world key we have an action that will be using a wander Target and it will have some effect on the world setting is wandering to be increasing and every action should have a cost and a range whenever we get to some other actions these will make a little bit more sense on the sensors we say hey we have a Target sensor and it's going to be that wander Target sensor so this is going to be the thing to execute and we're going to end up setting a wander Target so we've linked the action to the sensor and the effect to the condition on the goal we're almost there where we can have our AI finally wander we're just missing how do we set the goal with this system we have a concept of a brain which is basically how do we set goals so in our behaviors let's make a llama brain this is just a normal mono behavior and a key component is we need that agent behavior on awake we can get a reference to that and on start let's just set the goal to be wander goal the Boolean you can provide here tells you whether you want to end the current action or not so we can say false it doesn't really matter on this one cuz they're not going to have a goal yet since we only have the one goal so far and we don't have to do anything else that should be enough for us there's one more thing before we go into the editor in the behaviors we need to create something called a GOP set binder this is just a normal mono Behavior what this is going to do is hook up our agent Behavior to our GOP set you could do this in the brain maybe if you wanted to but it's a little bit nice to have it separated out so we'll get a reference to that GOP Runner Behavior call it GOP Runner and on awake we're going to get a reference to the agent Behavior using git component so in a second We'll add a required component to that at the top and just set the agent. goope set to be go runner. go set passing in the ID that we created in our GP set can Pi Factory llama set that way the agent knows which GOP set it should be using this isn't ideal on assigning the go Runner this way because if you need to spawn agents dynamically at runtime you'll need a different way of handling this you can't serialize them with reference to the scene if you're just spawning prefabs in so whenever you spawn in your agents you'd also have to hook up the go Runner so let's hop back to the unity editor and to get started we have to create a couple of objects let's start with the new empty object we'll call it GOP config move it towards the top of the scene and we need to attach a couple of things here the GP set config factory make sure we're using the one we created if we click validate it'll tell us we didn't mess up anything in the configuration so far if you get a warning or an error here that means that our GOP set config Factory was missing something and you usually get a pretty good error message telling you what's wrong we also need a GOP Behavior Runner give it a GOP set config Factory and hook up our GOP set config Factory here that's all for now on the Llama GOP that has our nav mesh agent our animator all that kind of stuff we need to add a couple of things first let's attach the agent Behavior then the agent move Behavior and the Llama brain and of course also on the Llama we need the GP set binder and we'll hook up that go Runner now we should be good to go let's give it a shot check it out the agent's moving picking random locations around them and wandering very quickly the reason he's moving around so much is we set the range to be 10 so as soon as our timer runs out he's just picking a new if you set the range to be much smaller maybe the same as the stopping distance of your agent then whenever they get close that's when they'll be marked as in range which would then say that our action has been successfully completed next let's make the enemy AI have an attack we can of course do this in a few ways the way we're going to do it is pretty similar to the other full implementation videos where we did State machine and behavior trees where we'll have a sensor slightly different from the go sensor that will raise an event whenever the player comes into range that way we don't have to use one of these GOP sensors that we have to run something like physics overlap sphere every frame which makes it just a lot more efficient we could do it with a normal sensor but it just wouldn't be quite as efficient and be a little bit harder to set the goal from the Llama brain remember that we don't want to pick a goal like Melee the player is my goal meleeing the player is an action that we'll take to potentially achieve the goal of killing the player so why don't we make a kill the player goal remember that the set a goal in our GOP set config Factory we need a condition and that's tied to a world key so maybe we'll make a world key like player Health remember to make that extend the world key base and we're going to need a player Target to track our player so let's make one of those next we're going to do our player Target sensor this isn't the sensor I just talked about this is a GOP sensor and I'm going to make this again a local Target sensor base not a global Target in this particular example because we only have one player this could probably be a global Target but if you're thinking about multiplayer at all then probably makes sense to be a local Target so the enemy only knows about the players nearby them we again don't need to do anything in create or update and when we want to sense for the player we're going to want to do a physics overlap sphere to find the player within some range if we can't find it then we'll know that the player is not nearby so we can't do this action and here's where we start getting maybe into a little a bit of a pickle you may have noticed so far we've been just hard coding values into our actions and our behaviors and our and our sensors all these things we've been just putting in random numbers that's not very good because we don't have an easy way to change those so before we go any further here adding in our radius and our layer mask why don't we start making some progress on allowing us to configure these things I'll make a new folder called config and why don't we make something like an attack config scriptable object if we provide the create asset menu attribute that allows us to create it from the assets menu we can provide a menu name of AI attack config a file name of attack config that's just a default name and maybe an order of one this is just going to be a data container it's just going to hold values for us and it'll allow us to edit it in the unity editor instead of having to recompile our code every time we want to try to make a change I'm going to put in a couple of key things here and all of these are going to be public a float sensor radius that'll set to 10 by default that's going to be how far away from the agent we can sense the player the melee attack radius which will be how far away from the player we can be and perform this attack the melee attack cost which will feed to the GOP system a layer mask to try to find the player on and of course an attack delay so we know how frequently we can attack this introduces an interesting new challenge how do we have this attack config get linked up into our player Target sensor because remember we'll never see this player Target sensor in the inspector we're setting everything up up in code we can use something called dependency injection here to solve that problem this is just a fancy term for assigning variables with values and references that a class needs without having that class having to figure out where to get that from you can think of the unity editor as providing dependency injection to Mono behaviors by allowing you to set values and drag references directly in the inspector it just so happens that we can't do that with classes that aren't available through mono behaviors so we need a couple of things and the g system here helps us out in the GOP folder we can create a new class called dependency injector and it's important to make this extend the GOP config initializer base and implement the iope injector those of you who are paying particular attention will have noticed that on our GOP Runner Behavior there was something called a config initializer which accepted a GOP config initializer base let's go ahead and Implement all of the available functions and in here we'll make a public attack config scriptable object attack config so we'll be a able to use our dependency injector and have this attack config available to any classes that need it on a knit config we just need to set the config GP injector to be this in order to inject something though we're going to need to know if it's the right type of thing that can be injected to so we make a new class well a new interface called I injectable which will just have a public void inject that accepts our dependency injector that allows us then in our inject functions here which are called automatically by the GOP system to check if for example the action should have something injected into it we can check if this action is injectable and get a reference to that injectable and then inject this and we're going to do the exact same thing for each of these functions what that does is then within an i gold base I World sensor I Target sensor whatever this type is will have a reference to this class which will have our attack configs set up that means back in our player Target sensor we can make this injectable Implement that interface where now we have a reference to the dependency injector and get a reference to that attack config now when we want to do a physics overlap sphere non alic we can pull this radius directly from the attack config scriptable object we need to provide some colliders as well for results and since we know there's only going to be one player right now we can make that a really small array we'll just check if that overlap sphere returns us a number that's greater than zero if that's true that means we can return a new transform Target based on the transform of the collider that was hit what we've done then is allowed us to not have to hardcode some numbers in here for the attackable layer Mass the sensor radius and all that kind of stuff we'll go back and do something for the wander config like that in a minute let's finish up attacking the player first now it looks like we're missing our action so let's create a melee attack action we'll do something really similar to what we did before making it extend the action base with an attack data type and it should be injected so we can get that configuration from our attack config scriptable object we're going to need a little bit more information than what we got on common data so we're going to make a new class attack data we'll make the attack data extend common data so we're going to have that Target and the timer still and additionally We'll add in a public static readon int attack which will be the parameter name from the animator animator do string to has attack and we'll want a reference to an animator which we can use the git component attribute which comes from the scope system public animator animator and we'll provide a g in set this G component attribute makes it so we don't have to go and do the G component ourselves it'll get populated automatically coming back to the melee action let's go ahead and Implement all the stuff required here created we don't need before we do start or actually do anything with any of these other ones let's go ahead and set up the injection we'll Define that private attack config script of object attack config and when we get injected we'll assign a reference to the injector attack config then on start we can set up our time based on the attack delay now here remember we shouldn't need a behavior for attacking it should be performed within the action because this is an action that the AI should be able to do so within perform which is called every frame that the action is running let's subtract our timer from our context Delta time we'll assign a Boolean should attack to be if the data Target is not null and we're within the melee attack radius we can then set the animator buol attack to be if we should attack or not so we play our animation if we should attack we're going to make sure that the agent's looking at the Target and then we'll return either action Run State continue or stop depending on if our timers run out this allows the agent to continue to sense periodically even while it's traveling towards the player because once the timers run out we said hey stop this action sense again see if you should be doing something else at this time in end we're going to set set the animator Bole attack to be false because we don't want to be attacking anymore and that's all we need to do in this action so we don't have any hardcoded stuff in here it all comes from a scriptable object that gets injected automatically for us by the system and we can refer to that while we're performing the action pretty cool I think that's enough for us to hop into the GOP set config Factory and let's see if we need to add anything else when we're trying to set up these goals actions and sensors adding in a goal we say we want to kill the player and the condition should be that the player health is less than or equal to zero for setting the action in here we can add an action the melee action set the target to be the player Target add an effect that the player health will be decreased so we know to choose this action to achieve the Kill player goal and of course we need to add a base cost and an in range but we need to get those from our attack config scriptable object at the top we can require component type of dependency injector and Define a private dependency injector injector and get that on create set the base cost to be the melee attack cost and we're going to set in range to be the sensor radius why because if we set in range to only be the melee attack radius this won't get chosen unless we're within melee range so we want them to start trying to melee when they come within sensor radius and then be marked done whenever they get Within in melee radius and have performed some attacks that looks good let's move on to the sensors simply add a player Target sensor and set the target to be the player Target so that's very much same as what we just did a new goal setting the condition to be player Health less than equal to zero says that we've completed that goal saying that we have an action called melee action that uses a player Target which is a signed from our player Target sensor and the effect of that will be to decrease the health so this action will be chosen to try to accomplish this goal and we set the base C and in range based on the radiuses and the melee attack cost something I've covered several times so far so I'll just instead of writing it out I'll just go through it really quickly with you is the player sensor which has a sphere collider on it it's a required component this will just raise a player enter and exit event based on the on trigger enter and on trigger exit call backs so these are raised by the physics engine whenever something comes in if has a rigid body or character controller because this is expected to be marked a trigger this allows us whenever we go to the Llama brain for example to add in a reference to that player sensor maybe also this would have a reference to the attack config and on enable we can assign listeners to that call back and on disable we can remove them for now whenever the player enters let's just immediately set the goal to be kill the player interrupting our current action so it happens very quickly and the AI becomes very responsive on player exit we can set the goal back to wander again interrupting the action on disable we'll just remove these callbacks let's bring start up a little bit and it might be useful to set up the radius of this collider based on our tack andig scriptable object let's come back to the unity editor the assets folder we can create a new folder called config and in here if we just write click create AI attack config let's call it llama attack config this has probably pretty reasonable stuff at the beginning maybe melee attack cost should be a little bit higher like three and let's say that they should only be able to attack things on the player layer and I realize now I haven't I reuse a project from a previous time so let's look at the layer setup if you don't have any layer setup you can go to Project settings we can go to tags and layers and add in player enemy sensors rooll impact sensors not really useful maybe enemy and grass because we're going to eat grass later so go ahead have all these set up you can also configure in the physics how everything collides with one another I don't think any of these are super important other than maybe let's say enemy sensors and player should interact with one another and nothing else we can make sure to select our go config add in our dependency injector select our attack config and hook up our config initializer to the dependency injector on the Llama GOP make sure we set up our attack config and our player sensor needs to also be configured which is just going to be a sphere collider on the enemy sensors layer where we attach the player sensor script and make sure we have that collider there well it gets it on aw so you don't have to assign that really importantly make sure is trigger is true and this layer enemy sensor really important make sure it's also a child of your object so whenever your llama moves that also moves with it it's important to note with the way that we're setting up our player sensor that our base model needs to be of scale one if we scale it up to something like 1.5 with a radius five on our player sensor then that scales up our attack config radius by 1.5 which makes it too large I actually recorded most of this video with the Llama at 1.5 scale which makes our attack radius too large in most cases and finally make sure also that your player that has the character controller or some kind of collider is on the player layer otherwise it's not going to collide properly with our enemy sensor then our llama brain we can hook up that player sensor let's take a look at the animator now cuz I think I didn't talk about that yet in here we have three Boolean parameters walk is eating and attack idle a goes to walk eat and so these are a triangle and then any state will go to attack and then from Attack we'll go to walk or idle these I think are pretty self-explanatory Idol will go to walk if we're walking true walk will go to Idol if walking is false if is eating is true idle a will go to eat if eating is true and from eat we'll go to walk if we're moving and eating is false if eating is false and walk is false we'll go to Idol any state will go to attack if attack is true also importantly attack cannot transition to itself and depending if for moving or not so if walk is true or false we'll go to idle or walk if attack is also false that should be good let's see what happens if we click play our llama is idly moving let's get within range and now they're chasing my player when they get close they start attacking we probably need to increase the stopping distance a little bit of the Llama so he doesn't get so close and do that but he's attacking and then whenever we're slightly out of range he just chases you again once he gets close attack perfect working as designed okay so our nav mesh agent let's give them a stopping distance maybe of like one and next let's create our wander config scriptable object in our config folder let's go ahead and make that wander config scriptable object We'll add that same create asset menu just changing it to be wander config in order of two make sure it extends a scriptable object and add in two two public variables a vector 2 weight range between wanders give it a default of 1 to 5 and a public float wander radius default of five to hook it up let's start with a dependency injector We'll add it with a public wander config scriptable object wander config I means in our wander action we need to receive that so let's make that wander action implement the I injectable interface Implement that inject and back at the top we can Define our reference to the private wander config wander config and assign it whenever we get the injection then instead of having this timer as a random range from 1 to two we can go based on the wander config that also means that our wander Target sensor needs to get that injection and do the same thing assigning the configuration and instead of getting a random Point within the unit circle times 5 we'll replace that with the wander config you can set up like the agent type nav mesh areas all those things in the wander config as you need to next let's add in hunger this is an example of having like a needs system for your AI so they do more than one thing besides just try to kill a player of course we're going to be doing a really simplistic implementation in this video but this should give you at least that concept of how you can Implement something like this and integrate it into the GOP system we're doing that instead of the next attack of spitting because we're going to want that spit to rely on eating in some capacity instead of starting from the goal for this I want to start with the behavior the hunger Behavior we'll make it require a type of animator and agent Behavior add in a field serialized field public float hunger with a public git and a private set that means that nobody else can set the value of hunger but they can read it and we can see it in the inspector we add the private agent Behavior agent behavior and private animator animator and also a private static readon int is eating to be the animator string to Hash is eating on awake we'll assign the agent Behavior and the animator using git component and let's assign hunger to a random range between zero and what let's say 20 but here we're hard coding something again so maybe it makes sense for us to make another config scriptable object before we move on let's go ahead and do that so we don't have to go refactor bunch of stuff later I'll call it bioscience scripal object we'll do the same thing we've been doing adding the create asset menu making an extend scriptable object and adding in a bunch of public variables and we're going to add more than you might initially think we might just need for Hunger we'll do a float food search radius a layer mask food layer a float hunger restoration rate which will be when we're eating how quickly do we feel full a hunger depletion rate which means when we're not eating how slowly should we start becoming more hungry a float Max hunger which will Define once we hit this much hunger it's too much I need to go eat eat and aoat acceptable hunger limit meaning if the player comes near me when I'm eating how full do I have to be before I'm going to chase them let's really quickly add this to our dependency injector so we don't forget about it and we can use it in our GOP system and add public bio signs so bio signs everything looks good there let's come back to our hunger Behavior we can add a serialized field private bio signs so bio signs so we can assign it in the inspector and we'll pick a random range between zero and Max hunger then on update we're going to want to do some things because we need this to get more hungry every frame right because we don't have anything about our actions and that kind of stuff done yet this is really all we can do so far we'll just get more hungry every frame all the time with this in place we can start working on our GOP system as usual we can start with World Keys we just add that hunger World key base a Target should maybe be a grass Target something that we should go eat that's going to be a Target key base as well the sensor we're probably going to need need two different sensors right we want to sense how hungry we are and where grass is let's start with that hunger sensor this one is a local World sensor base remember we've been using Target sensor base but hunger isn't a Target it's something that's happening in the world it looks very similar but instead of sensing a Target we're sensing a sense value we don't need to do anything on created or update but when we sense we're turn a new sense value doing references. gach component hunger behavior. hunger that gives us a reference to our hunger behavior and then we can get that hunger value but this is a float value and since value wants it to be an INT so we can do math F seal to int on that to make sure that it's an integer value so this will now tell us how hungry we are whenever we sense and we also need to sense where a grass Target would be the grass Target sensor will tell us again something in the world a position or a Target so that's going to be a local Target sensor base and we want this to be injectable we're not going to do anything on created or update and let's create our private variables bio signs so bio signs and a private collider array colliders to be a new collider array of size five so we'll just find up to five nearby pieces of grass and just pick the closest one don't forget in the dependency injector to go ahead and put that in and when we're sensing let's first get a reference to the agent position calculate how many hits using physics overlap spere non alic we'll iterate over all the colliders that are beyond the number that we hit so we'll do in I equals colliders length minus one I greater than hits iusus and we'll set the collider at that index to be null because we're using an array and overlap sphere non alic these don't automatically get cleared so previous time that we sensed may have leftover colliders here so we want to make sure we null those out it's also why we want to use a relatively small array here and probably before we do that we should check if hits equals zero and just which are null there's no grass nearby if we may it through all of that what we want to do is order the colliders by distance so we can do colliders equals colliders do orderby passing in a delegate function here colliders fat Arrow checking if collider null then give it a value of something big like 999 or maybe something better is float. Max value and if collider is not null we'll get the square magnitude between the collider position and the agent position and we'll give that back into the array format that way we can return a new position Target using only the first Collider transform position this is important because physics overlaps fear non alic doesn't give you objects necessarily in order of distance from the origin they just give you them in whatever order they felt like giving you now we have our world key our targets our sensors maybe we need our eat goal to make that eat goal base we shouldn't need an eat Behavior it should just be an eat action so let's make that next and this eat action will make it extend the action base of type eat action data so we're just going to Define data right here because only the eat action is going to need this data so it can be a class defined within this class in there we'll make it extend the common data and make it have a public hunger Behavior hunger with a public git and set with the git component attribute so it'll automatically get populated for us let's go and Implement all those missing behaviors and I'm going to move the data back to the bottom let's make this injectable as well I wasn't planning to do this I do this a little bit differently so we'll see how this turns out this never goes well when I'm like 3 hours deep into recording and decid to do something on the Fly okay so inject the bio signs here and on start what we can do is disable the hunger behavior that will stop our hunger from going up every frame then we can set up our timer I'm going set the timer to be just one second this can be configurable in bioscience but this way we'll check once per second if we should keep eating or if we should do something else now in perform this is what we're actually going to eat right we'll subtract the context Delta time from the timer and we'll check if the data Target is null or the hunger is less than or equal to zero meaning we're full full we can stop running this action otherwise we want to keep running this action but we also need to do some animation right so it looks like we need another git component on our data for animator so when we end the action we can set the animator Bo is eating to be false which probably I need to pull that from the Hunger Behavior we probably don't need either of these in here anymore that simplifies hunger Behavior so we can bring this is eating in here and whenever we start eating we can set is eating to be true when we stop we can do the inverse of start that is eating to be false and enable the hunger Behavior again I actually haven't tested this we're going to see if this works together let's go to our GOP set config Factory and set up our goals so again add the goal eat goal with condition hunger is less than equal to zero meaning we are no longer hungry means this goal is achieved in the actions we have the eat action with a target of grass Target adding in that hunger will be going down or decreasing let's set the base cost to be something like eight and that the in range is something like one so then we're really close to that grass before we start trying to eat it I think it's important to highlight why we're using a very small in range value here but we're using using very large values for the attacks setting in range to be very small means that this agent is going to move all the way to the grass Target and start eating before we count ourselves as in range on the melee ones we want them to start chasing the player and we want them to count as in range so they don't have to get within melee range before we start re-evaluating what actions should we take if we used a very small value like the melee attack range as in range for melee you'll notice that the Llama will get all the way up to the player and they will never re-evaluate what action they're going to take until they make it in range so that's why we have melee action performing differently with very large in range value but on this one we want them to make it all the way to the grass and start eating we don't want them to re-evaluate what they're going to do because they're so hungry they need to do that now now for the sensors we have two for this one right the grass sensor and we have a different type of sensor a world sensor for Hunger that will set the key to be hunger in the Llama brain let's make it set the goal to eat whenever the hunger has gone beyond that maximum hunger threshold so let's add a hunger Behavior hunger let's check and update if current hunger is greater than Max hunger let's set the goal to each goal I need to pull the bio signs as well so let's go ahead and add that up here as well else if the hunger is less than the acceptable hunger limit and the current goal is eat goal we're going to need to also check if the player's in range we'll set the agent Behavior goal to be kill a player and say let's not interrupt the goal let's finish what we're doing and then go attack the player so it'll happen within a second of when the player comes in range and our last case on this update will be if the hunger is less than or equal to zero and the current goal is equal and the player is not in range then we'll go to wander meaning we've fully satisfied ourselves the players's not nearby we'll just go back to idy wandering so let's create that private fool player in range and when the player leaves we'll set that to be false when the player enters we'll set it to be true back over in the unity editor on our llama let's add the hunger Behavior we have to create bio signs let's go to config create AI bio signs let's make that wander config as well so let's create AI wander config and let's just leave all that stuff alone food should be on the grass layer so in the scene we've got this grass and it is all needs to be moved to the grass layer on the Llama brain we need to hook up our hunger and our bio signs our dependency injector hook up all of that wander config bio signs let's click play and see what happens we can see the Llama now wanders and stops instead of constantly just immediately wandering whenever this timer runs out on the right we can see the hunger is approaching 20 so let's see what happens something's wrong he's eating there's nothing nearby and the hunger is not moving okay so on eat action we missed a couple things so start is called even if we need to first move to a location before we start doing something so we look kind of silly if we're eating and moving at the same time so let's remove this and move it down here to our perform we'll still stop eating whenever we end so that should be okay we also want to update the hunger here and we want to do that by the delta time times the bioscience hunger restoration rate but we made this private set so we're going to go ahead and open that up do public set and that should fix all the problems we just had let's see this in action the Llama is wandering when we get kind of close they'll start chasing start attacking great so he's trying to kill me we see his hunger is hitting almost 20 now let's just wait a second okay he gave up killing me cuz he's too hungry he's going to go eat now he'll start eating hunger Behavior becomes disabled and he started wandering because I left the range if I come back in he'll start chasing me and attacking me again up until he hits 20 and he'll go back to eating so something important that we just saw there was I left the range and he immediately stopped eating so something on our llama brain isn't quite right yet so when the player leaves we always immediately set our goal here which probably isn't good instead whenever the player enters and leaves we want to perform form similar logic to what we do here so we might be able to refactor this out and call set goal instead of just doing this we'll make sure to set the goal after we set players in range and we can see if I come and leave in this player sensor he doesn't start changing his goal until his hunger is low enough to actually do something about it perfect I think One Last Action to add in to really tie all this together would be to add in a spit action and before we just jump into that if we think about spit that's probably going to be a ranged attack we don't want to be right up next to the player and start just spitting on them that'd be a little bit weird so from a distance that can be our ranged attack and if I'm going to spit I probably need some kind of saliva to spit so we can add in something similar to what we did with Hunger but the inverse where it go down over time and we need to have a certain amount before we can do that spit attack so this means we need to keep track of both how much SLA we have and the distance to the player to determine whether we should or should not spit those will be conditions and effects that will be adding in whenever we're building our action let's take a look at it let's start with the saliva which will behave similarly but inversely to hunger where over time it decreases and let's just say that whenever you eat you gain more saliva in our bioscience scriptable object let's add in some stuff about the saliva we add a public float spit restoration rate give it a default of 0.25 and a public float bit depletion rate give a default of 0.01 importantly we also need a game object that will handle this spit that projects out I've covered this a couple times so let's just take a look at it in here we're going to have a just basic mono behavior that has an autod destroy time so after this thing becomes enabled we'll start a c routine to delay disable it which will just create a wait for seconds based on the auto destroy time wait a frame add Force to that rigid body based on the forward and the force amount wait for that wait for seconds and then finally disable the game object so we're expecting these are going to be pooled objects on disable we'll make sure to reset the velocity so it doesn't go anywhere and we don't have any residual velocity whenever we enable it again whenever it has on trigger enter it'll set itself to be false and maybe cause some damage so this is a self-contained projectile that just goes in a straight line after it becomes enabled let's start setting up our world Keys we'll want something like saliva and we also probably want something like the distance from the player we already have our player Target so we don't need to create a new Target in terms of sensors we probably want a saliva sensor which will be a local World sensor we haven't implemented the saliva mono Behavior yet so we'll come back to this but we're also going to need a player distance sensor remember because we're looking for a value not a Target this will be again a local World sensor base and we're going to need the attack config in here most likely so let's make it injectable as well and here we can make our private attack config so attack config we don't need created or updated so we'll just remove those exceptions and collapse them when we inject we'll just get that reference to the attack config from the injector and for sensing of value we'll just do if physics overlaps spere non alic agent transform position attack config sensor radius we need colliders so we can go back up to the top make a private collider array of colliders to be a new collider array of size one because we only have one player right now we can provide the attack config attackable layer mask and if overlaps fear non Alec returns a value greater than zero and the first collider trigate component out player returns true true meaning it does have the player component on it which it should then we can return a new sense value calculating the distance between the player and the agent if we couldn't find it we'll just return int max value saying they're really far away that's all we need to do in this class so let's make our saliva Behavior this will just be a normal Model Behavior We'll add a reference to our bio science that we'll hook up in the inspector and we need to have much like what we had previously with our hunger a field serialized field public float saliva with a public G and set because we're going to adjust the saliva when we're eating and a public transform spawn location which will be the spawn location for the spit prefab which means in the attack config we're going to need to add that spit prefab I love having the spawn location on the saliva behavior on awake we'll assign saliva just to be a random value and on update we will subtract the saliva based on the bio sign's spit depletion rate we'll clamp that between zero and Float max value so we never go below zero I came up with a solution we're going to actually remove this spawn location we're going to move this to a separate Behavior so we just have a little bit better separation of concerns here let's make a spit Behavior where we'll add that spawn location and this spit behavior is going to be something that catches an animation event from our animation because whenever we're doing the attack animation the Llama gets a little like headbob and whenever they hit the forward part of that is when we're going to want to spawn the spit so we're going to raise an animation event and we need a mono Behavior to catch that so that's going to be this behavior and that's going to be called begin spit so what we'll do here is create a delegate function that we can invoke called spawn spit event and we'll raise that event whenever this animation event comes in that way in our spit action we can have everything to do with spitting done in that action but remember actions can't receive things like animation events or whatever so this way we're propagating that event to the GOP system so our action can have everything to do with the action of spinning we can make it accept a vector 3 and provide the spawn location so it knows where it should place this thing maybe we also need to know the forward I think this is going to be a lot better than what I initially did let's make that spit action and hop in let's make that extend the action base but we're going to need a little bit more data than what we have in the attack data so let's call it spit action. data as a internal class for this class we're going to need a little bit more than whatever the attack data had so we'll make it extend the attack data in there we'll add a reference to the spit Behavior and the saliva behaviors using the git component attribute because we're going to need both of those in this class we can then implement the missing members for Action base and ey injectable going to move data back to the bottom and we're actually going to need to do something in created because our spit object is supposed to be pulled right so we're going to use the object pooling system built into Unity that came in unity 2021 by the way I have a video that goes in depth into everything about this object pooling system if you want to see more about that I've got a link in the description to that video that accepts two functions whenever we create an instance create object and get object the first one is whenever it's going to need to create a new object what should it do that should just return an instantiated object of the prefab but that prefab needs to come from the attack config so I don't have that yet so let's go back to the attack config scripal object and we actually don't have anything to do with our range attack here so let's go ahead and hook it all up here we'll add a public spit spit prefab a public int range attack cost and a public float range attack attack radius coming back to our spit action we're obviously going to need a reference to that attack config so let's go ahead and make that hook it up in the injection now when we're going to create an object we'll just return game object. instantiate that prefab now when we get an object that's just we're getting it from the pool we're not creating a new one you know what I don't think we need to do anything here let's go ahead and remove that because we're just going to get it and then we need to set it up based on information we get from that event so there's nothing that we can do on get here now on start we're going to want to set up the attack delay as a timer much like what we did in the melee attack what we do want to do is register a call back to our spit Behavior whenever we are going to get that animation event so that way whenever it actually happens we'll be able to catch that so whenever we're going to do on spawn spit that's when we actually want to get an object from the pool we'll get that instance from the pool we'll set it based on the spawn location and the forward but now how do we subtract our saliva I think what we can do is whenever we call perform and whenever we trigger the attack is when we can subtract the saliva and we'll just actually spawn it at the correct time let's come down to perform we'll subtract out the Delta time much like what we've done so far we're going to tell if we can attack by a really similar process to what we did in the melee action where we're going to check if the data Target is not null and our distance is less than the range attack radius but remember we want to subtract out the spit if we've triggered it for the first time in this run which is why it would be cool to do that up here in the spit behavior on spawn spit I think the best way to approach this is to just subtract the spit on start on start we can do data. saliva behavior. saliva minus equals 1 that's one usage of our spit coming back to perform this then can only do the things about performing the attack action that's setting the agent to look at the Target and to set the attack to be whether we should attack or not finally we can return data. timer greater than zero action run state. continue else action Run State stop very important and end we want to set this Boolean back to false so we're not having a residual attack animation after we leave this action and we also need to unregister our behavior of the spit Behavior with data. spit behavior on spawn spit minus equal spit behavior on spawn spit that way we won't do anything if we ever receive that event and we're not trying perform this action let's go to the saliva sensor we don't anything oncreated or update and when we sense the value we can just return references. get cash component saliva Behavior saliva but remember this needs to be an in value so let's floor it pretty straightforward that should be good let's hop over now to our go config set Factory in our goals we can add a secondary eat goal for if we need to get saliva so if we're out of saliva we will want to go eat goal we'll also consider eat goal to be successful if we have five saliva if we start looking at all these actions we added in a new resource effectively so whenever we're wandering or doing anything besides eating we can inform the go system that whatever we're going to do is going to decrease the saliva this happens over time this is optional it's not totally required you can do the same thing with Hunger stating that all these things increase hunger except for eating which would increase our saliva now besides the melee attack we also have now have a spit attack so let's add that into our actions for this action we're going to have a lot of stuff going on we're going to add multiple conditions because we need to have saliva being greater than equal to one so we can have the ammunition we need a condition that the player distance is less than the range attack radius but greater than the melee attack radius we're going to set the target to be the player Target and we're going to add some effects that the player Health would be decreased and saliva would be decreased we can set the base cost to be the injector attack config range attack cost and the set in range to also be the sensor radius both the melee and the spit action are marked as in range when we can sense the player and the actions handle whether or not we should actually perform the spitting or the attacking finally for our sensors we can add in the player distance World sensor setting the key to be the player distance and we can add in a world sensor for the saliva sensor setting the key to be the saliva we already have the player Target through the player Target sensor so that should be good we're going to change one more thing in our llama brain to make sure we're going to set the goal appropriately whenever a player enters or exits we're calling this set goal function that checks if we're using the eating goal to determine whether we should set the Kill player or wander goal and that's not going to exactly work for us anymore instead I think it would make more sense if we checked if the hunger was greater than the acceptable hunger limit and the current goal was equal to return and if we don't hit that then we'll set the goal to kill the player this way we'll much more responsively set the goal to kill the player on player exit we can do a very similar thing if the hunger is greater than zero and the current goal is eat goal we'll just return and in any other case we will set the goal to be wander let's hop back to the unity editor we need to attach those new behaviors to the Llama that's the spit Behavior we can hook up the spawn location and the saliva Behavior setting up those bio signs in our attack config we'll need to set up that spit free Fab which I have already created I'm using the same base project the spit is just a rigid body on the enemy sensors layer just because that collides with the player already has a rigid body with some Mass you want to make sure that it also has a trigger collider on it whatever type you want and it has that spit script attached we'll leave the rest of these configs alone the bio signs looks okay I don't know if you caught that but a second ago I assigned the player sensor to the spit spawn location not the spit spawn location so this spit spawn location is just a transform position at the nose of the Llama we need to be using that so the spit spawns at the correct location to catch an animation event we can select our fbx or model that has all the animations select our clip for example we want to use attack for this one if we scroll down something called events in this window down here we can see at which point we would like that event to spawn so I chose right around here you can click this little plus add event button name your function and this needs to exactly match what you have in the code and I'm not providing any of these things that's why we use the int discard parameter on that function let's go ahead and click play and importantly we can go to tools GOP node viewer which will show us the current available goals and the plans that the agent has available as well as the current conditions and the world State as the GOP system understands it we can see that we are not going to use the spit action because we don't have enough saliva right now but he will perform the melee action if we cheat a little bit and give him some saliva and move out of range we'll start seeing that he spits at me until he runs out of saliva then he goes back to the melee action once the hunger hits 20 or 21 he'll go and start eating because he's both out of saliva and he's too hungry he'll eat until either the saliva or the hunger goal becomes fulfilled and we'll see once his hunger reaches zero he'll turn look at me and spit that's perfect that means he's doing look at spitting and the spit spawns after he's performed the right amount of attack once he runs out of spit or becomes in melee range then he transitions over to the melee attack if you made it to the end congratulations this was was a really long video I know but I hope it really helps you understand everything all the things that go into making that GOP Ai and as you can tell it's a little bit more complex than just doing a state machine or a behavior tree but it also adds a lot more power into what your AI can do the workflow is a little bit different as well but once you get a hang of okay this is how I work in a GP system it really becomes not that challenging to do that mindset shift generally when we're getting more powerful tools it also adds complexity because we need to abstract some stuff out and make sure we have really solid separation of concerns that's I think where a lot of the time went into this video was just making sure we hooked up all the things and explaining why did we move things over here over here or whatever using this system was a really great experience it was pretty straightforward to understand how do I do all these things and I actually was talking with crash conagen some about the system and some plans in the future it sounds like some really great things are coming in the pretty near future to make this an even better system so I appreciate a that this is a repository available to us to use in our projects for free and all the time and effort that he put into making that system make sure you go over to GitHub and at least leave him a star and you can also sponsor his repository if you're going to use it in a big project and it's going to help you out a lot that can be either a onetime or a recurring contribution depending on the scope of your game and what you can financially do and if you got value out of this video make sure you've liked and subscribed to help the channel grow reach more people and bring more awareness into how to do AI with go you can also show your support for the channel by checking out the affiliate links in the description that gives me a small percentage of the purchase price and no additional charge to you and helps me out a lot actually there's more Revenue that comes through there than from YouTube ads so thank you everyone who's already doing that if you want to show your support directly you can become a YouTube member or go to patreon.com/crashcourse you get access to a patreon exclusive dissolve Shader at the awesome tier there's Ivan rulin ifis and Perry and Mustafa there's also all of these great supporters thank you all for your support I am so incredibly grateful
Info
Channel: LlamAcademy
Views: 9,012
Rating: undefined out of 5
Keywords: Unity, Tutorial, How to, How to unity, unity how to, llamacademy, llama academy, video game development, development, ai, goap, goal oriented action planning, goal, oriented, action, planning, plan, unity goap, enemy ai, enemy, unity goap system, goap system, goap system unity, goap implementation, implementation, full
Id: 85kogmzcLXw
Channel Id: undefined
Length: 70min 13sec (4213 seconds)
Published: Tue Dec 12 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.