Top Down DnD & GML Tutorial in Gamemaker #4 Enemy A.I.

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

In this tutorial we continue our top down journey towards Gauntlet as we create an enemy which detects the player, and then uses path finding to follow them, while avoiding walls. We also add code to destroy the enemy with our arrows.

👍︎︎ 1 👤︎︎ u/Slyddar 📅︎︎ Dec 05 2021 🗫︎ replies
Captions
if you want to learn how to make a top-down dungeon crawler something inspired by classics like gauntlet and even diablo something that has path finding enemies an area of effect explosions something that was the beginning of my own dungeon crawler game a top-down couch co-op action rpg with randomly generated levels and multiple player characters then keep watching and i'll show you how g'day gamers and welcome back to our drool gml and drag and drop top down dungeon tutorial we've added a player and a bow so now we can add enemies who chase the player when we get close enough and we'll also add the ability to fire on the enemies to destroy them so the first thing i'm going to do is just go over here and create a new group by right clicking selecting create group and we'll just create an enemy group now i'm also going to right click go to create group and create an entity group an entity means a player or an enemy so if i select both of those by holding down shift we can drag them under there and then we've got all our entities contained in this group now for our enemy if you look in the resources folder in the description i'm going to hold down control and select the sprites for the enemy and i'm just going to drag that into the enemy group so if you go to the idle sprite i'm just going to change the origin which is the x and y position and i'm going to set it to 16 and 24. and that will move the origin position down to here which will place it around the center of the enemy now we also want to change the collision mask and this is where collisions will occur for this sprite so i'm just going to change it to manual and i'm going to drag it and just make it a little bit smaller now each sprite that we have over here can have its own collision mask but i'm going to set it up so we'll only use this idle sprite mask so we don't have to resize all the others but what we do need to do is just set the origin and by having the origin the same it just means when you jump to another sprite the sprite position will remain the same so let's close that down and let's go to each one of these and just set 16 24. now we're actually not going to use the sprites today but it's good to set them up so we're ready for when we use them in the next tutorial now let's make an object to represent the sprite in the room and we'll just call this o underscore enemy now i want to drag the enemy idle sprite across and place it here and also we want to change the collision mask like we said and we'll change this to under entities to the enemy and we'll make sure we select the idle enemy and that way that mask is what we'll use for all collisions now another thing i'm going to do is just come over here to enemy i'm going to right click on the object that we already created create another object and call it o underscore enemy pir and that represents the parent now game maker allows for parent and child interaction which means that that we can have a parent object and we can have enemies as the child objects and anytime we want to refer to all of those enemies we just need to refer to the parent it's essentially a hierarchical structure so in this case we go to the parent section of the o enemy parent and we can set the children for this and i'm just going to drag across the enemy and drop it as a child so if we go into our room and come and have a look at what we'd set up in the last tutorial i'm just going to go over here and i'm going to drag this up just so we can see these now we only were placing the player on an instances layer and the layers enable us to have things in front of other things within the game so i'm going to create a player layer and press f2 and edit that and then i'm going to take the player from the instances layer and do a control x and then click on the player layer and do a control v and paste it in and the same way i'm going to click on instances and make a new instance layer and edit that and just call this enemy and therefore over here we can take this enemy and drag it into the room now because we have the enemy layer under the player layer when the player moves over towards the enemy they will appear on top of it because of the way we've set up the layers so let's just click play and have a look what that looks like so there's our enemy in the room obviously nothing happens at the moment but what we need is when we get close enough to the enemy we need them to react and i'd like them to become alert and essentially start to chase us so let's add some code to enable that to happen so inside the enemy parent i'm going to add the code here because that way when we make a new enemy they will also have the same behaviors so what we need them to do is when the player is close enough we need them to start chasing so let's add a step event and i'm going to create a script where they can check for the player and that'll enable the enemy to then ensure that they start chasing the player when they're close enough so let's right click on scripts let's go to create and we're going to call this check for player and just like we said what we want this to be is we can check if the player is close enough to start chasing them so the first thing we need to do is check how far the player is currently away from the enemy so i'm going to add a function call and i'm going to call a function called distance to object and what this takes is just another object and it'll return the distance to that object and what we need to pass is the object that we want to check so we're just looking for a player and it'll return a value and we're going to store that value in a variable that is a temporary and it's called underscore dis so this is to get the distance to the player now i want to use this dis variable a little later on so let's go back to our o enemy parent and let's add a create event and let's drag across our sign variable code block and let's create some variables here the first one is called alert and if i just go and add a description here we'll say that our alert variable is for when we are chasing the player i'll put a question mark because if alert is true then we currently are chasing the player if it's false we are not now we also need to know when we want to chase the player how far the player can get to us before we start chasing them so let's add an alert distance and i'll just set it to 160 and we can also come over here and just ensure we know what that means so alert distance is distance we can start chasing the player i'm defining these now but we'll use these a little later on so back over to our check for player what we need to think about is what happens when the player gets too close what does the enemy do where we want them to start chasing and one way you could do this is calling another function and i'm going to call move towards point and this takes an x and y and a speed so this will move towards a particular point by a certain speed so if i run this and i search for o player dot x for our x position and o player dot y for our y position so what i'm doing is referring to the instance o player and i'm referring to the x variable of that instance so because there is only one o player i can write it like this if there were multiple o players we wouldn't be able to do this because the game wouldn't know which one we're referring to but because there is one player only we can write the o player.x o player dot y and therefore we're referring to the player's position now we need to set a speed that it's going to move and i'll just set it to one so we could actually run this and test it but we need to call this check for player script in the enemy step event so back here let's go to the step event and let's do a execute script and we want to execute that check for player so we can actually run this and the enemy will actually start to chase the player and there we go job done except it doesn't quite work as you'd expect because the enemy just runs through the walls so anytime you get a wall between you and the enemy it doesn't really care it's just moving towards the player's position so this might be handy if you have like a bat or a flying enemy that doesn't care about solids but for us that's not going to work we want the enemy to walk around the solid objects when there's a solid they need to find a path to the player they need to be able to identify solid walls and avoid them while moving towards the point well game maker offers motion planning functions for this so let's look at our room and let's look at the collision layer just here now motion planning will break the room into a grid and it will go through every grid position and determine if it is occupied or not and it will use that to calculate if a path between two points can be made successfully using this method it allows it to move from one point to another so an enemy could use this grid to navigate around solid instances and chase the player so let's set up our grid and use it for the enemy's motion planning now i want to be able to create a grid variable to hold the data for the motion planning grid so when it scans the room i need a grid variable that'll hold where these solid instances are and since that grid is not really related to the player or any particular enemy i want to create a game object to hold such information so let's go over here and i'm going to create a control group and inside i'm going to create an object called o underscore game and let's go into there and add a create event and in here we're going to create a variable called global dot mp underscore grid so global means we can access this variable from any instance and mp stands for motion planning now i don't need to set a value yet because we'll set that in a little bit now every level of the game will need to scan the solids and create a new motion planning grid every time the room starts and game maker gives us an event for this called the room start event so let's add a room start event and this will run at the start of every room now our motion planning grid can be any size but i'm going to use 16 by 16 for now as that's the size our solids are set to so if you type in macro you can actually set a macro i'm just going to call it ts which stands for tile size and i'm going to set it to 16. and a macro is just like a shortcut so whenever i write ts it will replace it with the value 16. so it's kind of a way for words to represent numbers it just makes it easier for us to understand now let's declare some variables that we're going to use and if we look at our room we need to know how many tiles we have for the width and how many tiles we have for the height and one way to do that is to divide the whole room with by the size of the tile and that will tell us how many we have this way and we can do the same this way but in this room we're going to have this little gap at the end so what we want to do is if we end up with a fractional value from the division we want to ensure we round up so it caters for any fractional values at the end and we can round up by using seal so what i'm saying is that i'm going to store a temporary variable called underscore w and this is going to be how many tiles wide our room is so we say our room width divided by our tile size but we're going to use the function seal and that will make sure that it is rounded up and i want to do the same thing for the height underscore h and it's going to be our room height divided by ts or tile size and we want to take the ceiling of that and now that we've got the width and the height we can actually use a function called mp grid create now i'm doing a function call because there is no code block for this so mp underscore grid create and here it is here and it takes a left atop the number of cells horizontally the number of cells vertically and the width of both so for us we want to have 0 and 0 because we want to start at the top left now our width is going to be underscore w our height is underscore h and our cell width and height is just ts and we're going to store that in our target variable which we called global.mp underscore grid so this will create a motion planning grid with our dimensions and we'll store the grid in this variable so we can just write here a comment we want to create our motion planning grid now the great thing about a motion planning grid is it's very easy to add all of the instances of the solid that we have here because we want to scan every row every column make sure that we're adding any time we have a solid and we can do that with one single function call motion planning comes with quite a few functions that we can use and this one's called mp underscore grid and you can see a bunch of them here and we're looking for add instances and this will take the id of the grid the object you want to add and whether we want to use precision so our grid is global dot mp underscore grid put a comma and we'll add our solid which is o underscore solid and i'm going to set false for the precision because our solids are all squares we're not going to use precision checking for this it's not required and that's all that's needed to add all of those solid instances into our grid now if i run the game you're not going to see the grid or any difference at all and that's because the grid isn't drawn by default but we can draw the grid to have a look and see what it looks like so in our game i'm just going to go down and add a draw event and in here say if i want to hold down the alt key then i want to have a look at the grid it just enables us to check it sometimes and we don't have to have it on all the time so if we hold down alt and i'm going to do another function call and we're going to output the grid and we're just going to call mp grid draw and this just takes the id and that is the grid name so global dot mp underscore grid so the only thing left to do is in our room let's go and make sure under our instances that we drag across our own game and i'll just place it up the top here we don't have a sprite assigned to it so it just shows up as a question mark so let's press play and have a look at that so now if i hold down alt you can see that the green is where there is no grid and the bright red indicates that a motion planning grid has been set there and as you can see it's exactly where our solid instances already are so now we have our mp grid we can get the enemy to use it to calculate a path to the player's position the enemy will then follow a path to that target location so we'll need to create a path variable to store the path that they use so over in our enemy let's go to our create event here and let's do another function call and i'm going to create the path variable and we do that via path underscore add now this doesn't take any arguments but it will set a path to a particular variable and we're going to call that variable path so this creates our path resource now the thing about a path is it's an allocation it allocates a resource to this variable and when the enemy dies we need to clean up that resource because otherwise it doesn't know that the enemy doesn't exist anymore and we can do that in a cleanup event so just in here i'm going to do another function call and i'm going to do a path underscore delete and then we just need to say what is the index that we're trying to delete well that's where our path variable comes in and now the path will get deleted and cleaned up correctly now i don't like having to use a lot of function calls in drag and drop but unfortunately there are no code blocks for these kind of functions so that's when i have to use a function call if using gml these are the actual gml function names that you can use directly so now we can create a path to the player when the enemy is alerted in the check player script so back over in our check player script we don't want to do a move towards point that was just demonstrating what we didn't want to use but instead i'm going to drag across a function call and i'm going to add here our mp underscore grid underscore path and what this function will do is calculate if it can reach a particular position we give it the id of the motion planning grid the path that we want to use the start and the end position and if we allow diagonal movement so let's pass those in so we have our global.mp grid we have our path variable our starting position is just our x and our y and we want to move towards the players x and the players y and for the last thing which is diagonal movement that means essentially will the group motion plan just use left and right horizontal or vertical movement or we will allow some diagonal movement as well and i'm going to actually put a choose in here and say zero or one so it'll choose whether it allows diagonal movement or not and that'll make sure that 50 of the time it does do that it just gives some diversity in the way that the enemies move so what we can write here is can we make a path to the player now i ask that as a question and the reason is that this function will return true or false true if this path can be completed so when that returns a true or false i'm going to store it in a variable called found player and we'll just make that a temporary so was the player found can we get to the player and if we can we'll drag this down here and we'll say that if found player is equal to true then if it is we want to start the path so if we can reach the player so how do we start the path well there is finally a code block that we can use and it's called start following path and all we have to do is give it the path we want to follow so we can just type in the word path there for the variable and we'll start moving toward the player at the speed of 1 and this is what happens when we reach the end of the path and absolute we can leave it false that's just referring to the type of path in the room now to enable enemies to move at different speeds i'm going to set a variable here called move speed instead of setting it to 1 and with that move speed variable we can just go back to our enemy in the create event and let's just add another variable called move speed i will set it to one and it just means that if we have other enemies they can have their own new speed variable and they'll travel at different speeds so let's go back to our room and let's just go to our enemy layer and i'm going to just add a bunch of enemies if i select enemy over here and hold down alt i can drop more than one and i'll drop a few around there so let's press play and test this out now we can see that the enemies all move towards the player and they also take a different path they're not all following that same path towards them now you see though when they eventually reach the player they just go straight to the x and y position and they just stand there now one thing that's handy is it'll be nice to be able to see the path that they're taking so what we can do is just go into our enemy parent let's add a draw event and once we add a draw event then the regular draw will no longer happen so if you ever add a draw event you have to go and draw the instance using draw self now i'm going to do the same thing we did before i'm going to look at if our key down is being pressed so if we're holding down let's say under letters i'm going to hold down p for path and if that's happening i'm going to execute some code and the reason being is there is no code block for what i'm going to show you so here this is pretty large i'm just going to shrink this down and just make it so we can see it all in one location and what i want to do is just draw path and i'm going to draw the path and just the x and y and 0 for our absolute now for that path function if i hold down p you'll be able to see the path that the enemies are taking so every time i run around you can see the way that they're trying to move towards the player's position now one problem is calculating paths is not something you want to do every step like we are currently doing it can be fairly cpu intensive when you have many enemies and the room is larger and it's just not needed so instead we can add a timer and only calculate the path a few times a second also we don't want to have all the enemies doing their calculations on the same step as each step needs to complete within 1 60th of a second so we can ensure the timer is random for each enemy and that spreads out the load on the cpu so in the enemy parent let's go to the create event and let's add an assigned variable code block and let's have some variables for the path calculation and i'll just call it calc path delay and we'll set this to 30 and that's going to be the time between when we calculate a new path and for the randomness i'll make another variable and we'll call this calc path timer and that will just be the variable that actually counts down and we can set it to i random let's say 60. now what that means is it will come up with an integer value from 0 to 60 it'll just choose a random one of those and that way our timer for each enemy will start at a different time so let's go back to our check for player script and we can use that so just here where we're doing our function call and we're checking to see if we can see the player because this takes up cpu processing i'm going to actually put in the if check just before that so we will now check in here and we'll look at our calc path timer and if that is less than or equal to zero then we can go ahead and do our calculating of the path so here we're checking should we calc our path now we're checking our timer and we need to decrease our timer every time we check it and we can do that very easily by just adding a minus minus to the end of the variable name so now every time this variable is read it'll actually decrease it by one and when it's less than or equal to zero then what we want to do is set it so calc path timer gets reset to our calc path delay so basically we're setting it to 30 every time it's less than or equal to zero now if this is true what we want to do is take each of these i'll hold down shift and click on them and just drag them under the calc path timer there so they'll only run if our calc path timer is less than or equal to zero so let's press play and make sure that that's working so now you'll see our enemies start at a different time they all didn't start exactly at the same time and if i hold down p you can see that they're not calculating like before as much there's a little delay in the time so we're not seeing them flash up so much because they're all calculating at slightly different times great so one thing you'll notice is when they get close to us they just clump up around us instead i just want to stop just away from the player so they can then launch their attack so let's set a variable which is the distance they will stop from the player so back over here just among our alert here i'm going to add another variable and i'm going to call this attack distance or attack dis and i'm going to set it to 18. so when they are within 18 pixels then we want them to stop so over here i'm going to add that this is our attack distance and this is our distance we stop from the player so now we can use this new variable in our check for player to ensure we stop our path movement once we are within that attack distance so back over and i check for player now after we've found out the distance let's add a check in here and i'm just going to grab the if variable i'm going to drop it just above the current if variable that's there now you'll notice that all the code blocks that are underneath don't get dragged and included in the if check so instead we actually need to select all these i can hold down shift and select all of those and we'll just drag them and drop them there so the first thing we're going to check for is we only want the enemy to move towards the player if we're within that alert distance that we set up so if our distance is less than or equal to the alert distance i'm going to put that in brackets or we are already alert which means that we were within that distance beforehand so if that equals true then we want to go ahead and let them calculate a path to the player now what we can also do is use this alert variable and make sure that we set it just here to be true so once the enemy has been alerted we can set it to true and that will then enable this to be true so once it's alerted it'll always be able to calculate a path to the player even if we end up outside of the alert distance and that's what this check is doing now with our new variable attack distance we don't want to calculate a path if we are within that attack distance so we can have another condition here and say and that distance is greater than the attack distance so we're now calculating a path if we're already alerted or we're within that alert distance and this end ensures that we are only doing it if we are outside of the attack distance so if this check fails i'm going to add an else here and i'm going to drag the else and just drop it underneath this if variable code block and we're only down here if we are within the attack distance or we're not even within the alert distance or not alerted yet so down here we can do another check and we can say is our distance less than or equal to the attack distance and if that equals true then what we want to do is stop and we can do that with a function call called path end so essentially what we're doing is just stopping at that attack distance so let's press play and test that out now these enemies are chasing because they're within that alert distance and these enemies are not now let's see if they stop and there we go they're all stopping at that attack range so then they can actually attack the player now these haven't been made alerted yet because we're not within range but if we get a bit closer the range is detected and then they start to come after us and once they're coming after you you can't get rid of them because they're always going to be alerted and here we have all of them coming after us and if we stop they all should stop at that 18 pixel distance away from us so what else can we do well we need to ensure that the enemy faces the player when they are alert if you notice i go over here they're not facing the direction that the player is standing in they're always facing to the right so that's something we can fix we also want to ensure that they are not all playing their animation in sync with each other and you can see that they're all moving at the same time so we can do that by randomizing their image index in the create event so you might try that yourself i'll take a look at that at the next tutorial and lastly ensure that animation changes to the walking animation when moving and then they go back to the idle animation when they're stopping at the moment they're playing their idle animation and even when they're chasing us it's still actually just their idle animation so these are things we'll look at in the next tutorial as well as the ability for our arrows to damage them so at the end of this video i'll show you some quick code that we can add to do just that if this tutorial has helped you or you just want to support the channel and even get the source code for these projects you can do so over on patreon thanks this month to these legendary patreon supporters kaiser ho andy kaye colin litchfield and bb samurai thanks as well to this month's epic supporters sky devil palm dan half edward lyko paul r leblanc joking frohold robert churches sinforaga sudo luke banay sirpita yudhomoromitsu dafushing firasaratidan tom shervington and lastly kudos to these rare supporters for their support as well now if you just want the arrows to damage the enemy we need to create a collision with the arrow and the enemy parent so we go over to the o arrow and we do the collision with the enemy parent so any future enemies will also inherit the collision code as well so let's add a collision here with entity enemy parent now in the collision code we want to destroy the enemy and play the arrow dying animation if you look in the solid collision we have a function called to the arrow die so let's take a copy of that and we'll just paste it in here and that's the great thing about having a script that does this because we can use it in multiple locations now we also just want to destroy the instance and we want to destroy the other instance in this collision which is the enemy parent so if i do the little drop down here i can select other and that'll ensure that the enemy itself is getting destroyed now let's test that out and now we can at least hit the enemies and they disappear when the arrow hits them so we need to improve on this which we can look at in the next tutorial so thanks for joining me i'll talk to you in the next one [Music]
Info
Channel: Slyddar
Views: 864
Rating: undefined out of 5
Keywords: gamemaker, studio, platform, platformer, drag, drop, dnd, dragndrop, drag n drop, tutorial, peter morgan, one way, oneway, fall, make games, shaun spalding, heartbeast, spalding, yoyo, gms2, gms, advanced, learn, drag and drop, collision, make game, jump on head, enemy ai, horizontal, collisions, coins, collectible, items, objects, slyddar, peter, morgan, 1.4, top down, first game, movement, gauntlet, arpg, diablo, action rpg, bow, projectile, bullet, arrow, shoot, aim, lerp, pathfinding, motion planning
Id: QOrBkf3GeZc
Channel Id: undefined
Length: 36min 18sec (2178 seconds)
Published: Sat Dec 04 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.