Make an ENTIRE Godot Game in 1 HOUR (GDScript)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
today is the day you finally sit down and make your first game was that scripted and awkward when I launched this channel I started with a six video series on how to make snake and gdau and just for funsies I've cut that 90minut series down to a tight 1hour course that will take you from zero to gamedev in an afternoon thought I was going to say from Zero to Hero didn't you I'm better than that I've been making games for over 20 years and I still love it as much today as the first day I started so what are you waiting for let's get you started right [Music] now since most games are built on the same fundamental building blocks we can use a simple game like snake to make it easy to demonstrate and recognize those blocks without the game itself getting in the way let's get started as you can see here I've got an empty gdau project set up and the very first thing I'm going to to do is just create our gameplay scene rename this to gameplay and then we're going to save it we want to save this in our gam playay folder by the way I've set up a couple of folders off camera that I know I'm going to need in just about every project we'll create a couple more together along the way the next thing we're going to do is attach a script to gameplay we're going to leave it named gameplay and this is going to be where the majority of our game logic lives we're going to give it a class name of gaml the first thing we're going to do is work on detecting user input from within our process function we can use the is action pressed and look for UI uncore up and for right now let's just print out up is pressed Let's test our game you'll see this pop up the first time you run any project it's asking which scene do I start with since we only have the one gameplay scene we're going to select current we can and we'll change this later so now that our project is launched you can see down here in our output window if I press the up button we're detecting that the up key is pressed so it works so let's go back and repeat this for each of our directions and save now if we test it again you see it can detect left up right and down perfect although with these print statements we're not doing anything super valuable so let's add a variable up here called move uncore dur for move Direction and we're going to store a vector 2 in it and by default we're going to set that to Vector 2. right which is the same as Vector 2 1 comma 0 we're going to default it to Vector 2. right because we need a direction for the player to start and most games will start you moving left to right so we're going to set this in here we're going to set this to Vector 2 do up that's all we're going to do with user input for right now the next thing we're going to do is create something to actually move so we're going to create a new scene and we want it to inherit from area 2D we're going to base this node on area 2D because it's going to allow us to detect very simple collisions between the snake and the food that it eats and the snake and itself we need to resolve these angry yellow warnings in area 2D we'll expect a collision shap shape 2D and a collision shape 2D is going to expect a shape let me switch back over to my 2D view you can see when I've added my circle shape it appears at 0 0 the other thing we're going to need for this snake's head is a Sprite so that we can see it on screen so let's do that now and of course we don't have any Sprites in our project yet so let's right click on our gameplay folder let's create a new folder called Sprites and with our Sprite folder selected let's drag them over into our project and just like that we've got Sprites in our game now we can use this devil's workshop logo as the head of our snake and there we have it now you may notice that now that we have a Sprite I can no longer see my Collision shape well that's because the bottom of your scene tree is actually the top of the stack so you can think of it like this Sprite 2D is the closest to the monitor so if I drag this up to the top of my scene tree now I can see my Collision layer again let's name this AE because this is going to be the head of our snake and now we can save and of course we are going to need a little bit of code on this node so let's click the attach script create a new head script of class name head and again save now if we go back to our gameplay scene we can add that head by clicking this link button down here that's going to allow us to bring in another child scene here's the head scene that we just created click open and shows up in our scene tree now when we run our project very little happens but there's the head of our snake let's go back and drag that into the scene a little bit better so that we can see it doesn't really matter where right now actually since this is going to be a grid based game what we're going to do is go into our configuration snap configuration we're going to set this to 32x 32 then we're going to turn on our grid and now when I drag it around it's actually going to snap right onto the grid which is what we want because in snake your character actually moves in increments on the grid just like this so we'll just put him right here for now if I click play we've got our character here so far so good okay so now we've got our snake we've got our grid let's add some basic code to move this little guy around so let's click on our gamepl scene and I'm actually going to create a function called update snake because I know eventually we're going to be moving more than just the head around but for right now let's just get some basic stuff on the screen so that we can feel good about ourselves within this script we're going to want to reference the head I'm going to click let's set this up to access as a unique name what that's going to do is in the event that we Nest this under something else and change the path to this node it's not going to break our onready variable reference we now we're going to drag this over and this is actually going to be of type head and we're going to go as head just to make doubly sure that all of our code completion works now we don't actually have any code in our head right now really but let's for example let's just say we created a function called say hello that just printed hello I come back into my gaml script and type head dot now you can see I'm getting my say hello if I remove the as head a lot of times the code completion doesn't actually work yeah see no say hello so besides it just being a good idea to statically type as much as you can because it makes your code cleaner and easier to read it also makes it a lot easier okay we don't need that back to gameplay we're going to move in 32 pixel increments because we want to stick to a grid before we do that we need to figure out on what interval we want to move this snake so I'm going to create a couple more variables one called time between moves which is going to be a float we'll set that to a th000 we're also going to need a variable called time since last move that's going to represent how long it's been since the last time we moved we can also create a speed variable set this to th that's going to allow us to increase the speed of the snake as the game gets harder and harder so that'll do it for right now since the snake is going to be comprised of area 2DS that we move around and area 2DS or physics objects we're actually going to want to update them not in our process Loop but in the physics process Loop instead so what we're going to do is create a very simple mechanism in here for determining on what tick we're ready to move the snake we can do that by doing time since last move plus equals Delta times our speed variable and then we can say if time since last move is greater than or equal to our time between moves we want to update our snake and we want to reset our time since last move we can get rid of our pass and to show this in action we can print move the snake and if we run this you'll see on regular intervals we're getting that message if our speed went up let's make it say 10,000 to make it noticeably faster you can see we're going to be moving significantly faster so that's going to work for a mechanism to move the snake but now we have to actually do something with the update snake method so if we want to move our snake on every tick we need to know the direction that it's going to move in and by how much so we have the move Direction stored up here as these vectors and I'm actually going to type these out so you can see see what these look like so to move our snake we can actually multiply our move Direction by the size of our grid so in our update snake function we can do head. position equals head. position plus then we can take our move Direction and multiply it by 32 which is our grid size you see if we play this now it should start out moving to the right very slowly and if I press up nothing happens why doesn't anything happen when I press up because I'm pressing up on the w s and D Keys If I press up on the arrow keys now we're going up if I press left now we're going left let's fix that real quick so we can go into project project settings choose our input map turn on show built-in actions and find our UI left right up and down right now these are default mapped to the arrow keys but we can add our WD keys by clicking this plus button this is left so I'm going to press a and then okay and now you can see that the a key has also been mapped rather to UI left and we can repeat this process for right up and finally down so now if I play this I can move my character with the w s and D keys so a couple of things before we move on I want to do a little bit of refactoring first things first it's very possible that I might want to know something about our grid size somewhere else in the code I also might let's say maybe our grid size become 16 or 64 I want to be able to change that in one place we're going to do that with an autoload file so I'm going to right click on my autol loads folder go to create new make a new script and we're going to call Global now I can expand open this up I don't need any of this we're going to create a constant called grid size that's all we're going to do with global for right now now that we've created our Global autoload file we need to make it accessible to the entire project by registering it as an autoload we can do that by going to project project settings autoload and then click this folder navigate to our autol loads select Global it's going to pull the name in which is the name we want we can click add and by default it's enabled so now instead of hardcoding 32 what I can do is type global. grid size which is going to be the same as 32 and that way if it changes down the line you only have to change it in one place and right now head is extending from area 2D which means by default it has all of the properties and methods of an area 2D but what if we wanted head instead to inherit from another class called say snake part that both the head and the tail could inherit from and that they would have shared functions for things like how to move them around on the grid how to keep track of where it was on the last tick because remember this snake is going to move in a chain and the first section of the tail is going to follow the the head the second section of the tail is going to follow the first section of the tail so there's going to be some commonality between the heads and the Tails so let's make our lives easier let's create a new script that inherits from area 2D and we're going to call it snake part and let's open up our snake part script let's give it a class name of snake part and we don't need any of this so snake part itself is inheriting from area to 2D so that when the head and the tail pieces inherit from snake part it will still be an area 2D and have all those properties but it's also going to have the properties that we add the properties and methods of the snake part class you only need two very simple things to make this class worthwhile the first thing is going to be a record of our last position which is going to be a vector 2 and then we're going to need a function defines how we move it from where it is to where we want it to be so we're going to call this move to and it's going to take a vector 2D called new position and this is going to be really simple we're going to store the last position as its current position so that we have a record of where it was before we moved it and then we're going to set its position the new position and that's it it's very simple so we can actually close our snake part class now and go back to the head and instead of extending from area 2D we're going to extend from snake part and you'll see that when I come in here if I type self. move to there's our new method that we're inheriting same as self as last position there it is now from here we can continue to add things to the Head class that are specific to the Head itself and we can add things to the tail class that are specific to that so that they become different and discret objects in and of themselves but they will always share anything that we put in the snake part so we don't actually need any of these in the head class so I'm going to delete them and then we're going to go back to our gameplay script and instead of doing this head do position equals head so on and so forth what we're going to do is we're going to say head. move to and then we're going to give it this new position so now I click play oops nothing's happening why is nothing happening well that's because we've left out its position so what we need here is and actually we're going to make this a little bit cleaner so let's say variable new position Vector 2 equals head. position plus move Direction time grid size and now instead of that we can put new position in here now if we play it and it looks the same but we're a little bit more flexible [Music] now we got our character moving but once it goes off the screen it's gone forever in order to make the snake wrap from the left side of the screen back to the right we're going to build a little helper node that's going to keep track of where our boundaries are and we're going to be able to Ping it to find out whether we've gone outside of them so let's start by adding another node 2D we're going to call this bounce and to that we're going to add two marker 2DS a marker 2D is just going to give you a Crosshair Gizmo that you can drop into your game scene to visually Mark something in X and Y coordinates I'm going to name this one upper left and I'm going to duplicate it with command D we're going to make another one called lower right if I select these and go to our move tool you can see we actually have two little gizmos that we can drag around here's one here's the other and for right now I'm going to position this as close to the bottom right hand corner as I can and that's going to give us the boundaries of our screen this is our upper leftmost position this is the bottom right and between them we form an entire rectangle let's add a script to our bounds node give it a class name bounds and let's get references to our two boundary markers let's create four variables that we can use to store the boundaries of our game in the on ready function we can access the positions of our two markers and store those values as our minimum and maximum positions this isn't strictly necessary because we could access these positions directly however these short variables are easier to type and they read more like what they actually are the next thing we're going to do is write a function called R Vector which is going to take a vector 2 and it's going to return a vector 2 now there's not very much to this function we're going to use this wrap Vector function to pass in the position we'd like to move the player to and it's either going to return the vector that we gave it if it's within bounds or it's going to return the leftmost X coordinate if we've gone too far to the right the rightmost if we've gone too far to the left and so on and so forth so let's see how we Implement that first we're going to need to grab a reference to our bounds so let's drag that in here release with a control click and let's force it to type bounds so that we get our code completion now down here before we set the position let's check it against our wrap Vector so we can feed it new position if it's within bounds it'll just return the same position we gave it and if it's out of bounds it'll return the WRA position so now if we test our game our player wraps nicely top to the bottom left to the right the next thing I'd like to do because this window is a little bit gigantic let's change the size of our game so flipping back over to 2D the other problem this is going to solve is if you take a look at our marker our game size is not exactly in in 32x 32 Dimensions so we're going to go up to project project settings window and right now we've got this giant window that is in 32x 32 increments and I don't know the exact number we want but I do know the ratio so I can actually use a shortcut by multiplying 25 by our grid size and hit enter and 14 by 32 to get our height so we're going to have a window of 800 by 448 now when I come back you can see here we've got a smaller window size that fits directly onto the grid and now all I have to do is move this marker into the bottom corner and if I run it again everything still works exactly as we wanted it to let's go ahead and add a tile map so we can put a very basic background Grid in place in snake especially when you're wrapping from one side to the other it helps to have a visual guide to show you what row or column you're in when you're lineing lining up your moves we can select gameplay search for tile map and we're going to want to drag this up to the top because we want it to be behind everything and tile naap is going to require a tile set so with our tile map selected we can click on this empty dropdown and select new tile set we can click on that to change some of the settings now we know our tiles are 32 by 32 so I'm going to change the default from 16 to 32 now of course our tile set needs some actual tiles so let's grab in this Grid it's going to ask if we want to automatically create tiles in the atlas for this very basic example we can just click yes now I can click over to my tile map select my tile select the rectangular paint tool and you'll actually need the selection tool up here selected for this to work and now I can paint my tiles in to fit the size of our window and let me click off of that so we don't see the orange anymore now when we play this you can see we'll have a grid that gives us a visual guideline where we'll come out when we wrap [Music] around let's create the tail scene by going to scene new scene we'll select other node because you'll recall in the first video we wanted this to inherit from snake part because snake part is an area 2D we're getting this warning that it needs a collision shape and the Collision shape you'll recall requires a shape we're also going to need Sprite 2D and for now let's just drag in our apple and move that back up so we can see our Collision shape let's rename it tail and save it in our gameplay folder because this inherits from snake part it already has our snake part script attached to it later we're going to want to add a little bit of code to customize the tail so rather than using this script directly we're going to rightclick on our tail and select extend script we're going to leave it defaulted to tail and give it a class name tail for right now we don't need any additional code to make our tail work but later we will come back to the tail class and add some code that only the tail needs we can delete this default code and save now let's go through a similar process to create our food scene new scene our food can inherit directly from area 2D because we're only looking to detect collisions between the snake and the food we don't need anything else special because we're in area 2D we're going to add our Collision shape and our circle like we've done before and we're also going to add our Sprite and drag in Our Star graphic we're going to rename this to food and save our food class actually doesn't need any code so we're not going to add a script but now that we have our food and our tail scen setup we can go back to our gameplay and start working on our spawner go to gaml add a new node 2D call it spawner and attach a script called spawner our spawner class is going to be responsible for dropping food at a random location inside of our game field let's let's start by creating a function called spawn food that we'll call each time the snake eats the piece of food that's already in our game scene though this spawner class is very simple it's going to make use of a couple of very common gdau design patterns signals export variables and instantiating packed scenes when spawning a piece of food we need a few elements where to spawn it or its starting position what we're spawning this is instantiation and where we're going to put it or who is the parent object of what we've spawned we can start by creating a vector 2 that will hold our spawn point since we need the food to appear somewhere within our gameplay field we'll need to know the bounds of our screen which you'll remember we've already created for the wrapping we can create a reference to our bounds node and reuse that existing code that's going to be our export variable we can type at export VAR bounds and give it a type bounds now if I click on our spawner and look at our properties window window we can see that export variable bounds here if I click this assign button it'll bring up a window allowing me to select the bounds object that I want to reference now within our spawner class we have access to our bounds object we'll set it to Vector 2.0 to start but we're going to end up setting each X and Y position separately in a moment we'll start by picking a random x value for our spawn point somewhere between the X minimum and X maximum from our bounds CL class we can use a built-in function called randf range which will return a random float between two floats in this case bounds. xmin and bounds. xmax to make sure our food never spawns directly on the edge of the game screen let's add our global grid size to the minimum value and subtract it from the maximum this will make sure that the food never spawns any closer than 32 pixels from the edge of our screen let's copy this line and duplicate it below now I can hold the option key and insert multiple cursors hit the delete key enter Y in all three places at once now we have our y component with the same constraints now that we have the location we're going to spawn the food at we need a way to access the thing that we want to spawn that's our instantiating pack scenes so we're going to create a variable called foodcore scene of typed packed scene and we're going to set that to our food scene which we can drag in and if we hold control and let go we're going to get the path to our food scene wrapped in this preload function this will preload our food seed into memory so that when we go to instantiate it it's immediately available now let's instantiate our food instantiating the food creates it but doesn't add it to our scene tree that's what we're going to do in this next step we're going to get a reference to our parent in this case the parent of our spawner is our gameplay scene we're going to call get parent. addchild and pass in food get parent in this case will return a reference to our gameplay node because spawner is a direct child of gameplay in a simple game like ours using get parent is fairly safe because we know that our scene tree isn't going to change very much throughout the course of development however in a more complicated project down the line we may end up moving spawner somewhere else in the scene tree which would change its parent for example if I drag spawner into our bounds node bounds is now the parent and this code would mean that we would be adding our food to the bounds node which is not what we want but like I said because this is a fairly simple project we can get away with this before we can test this we need to set the position of our food to our spawn point now we can return to our gameplay script let's grab a reference by clicking and dragging remember to control click before you release let's force it to spawner as spawner to make sure that we get our code completion and in our on ready function we can tell our spawner to spawn food so when we test the game you see we get our first bit of food granted when we run into it nothing happens because we haven't written that part yet and also and this is the thing I wanted to show you you'll notice that it's not nicely fit to this grid the way we would want but that's an easy fix let's go back to our spawner class before we make this assignment let's round down whatever random number is stored in our X and Y values so that it fits on our 32x 32 grid we can use this built-in floor F function that will take a float and round it down to the nearest integer so we'll take our spawn point divid it by our grid size this will give us an integer representing the column that the random x value has fallen in to convert it back into our grid size we can just multiply it by grid size and again we can copy and paste and then convert this to Y if we run our game now the food will always fall nicely in our 32 by 32 grid next let's work on detecting when the head of the snake has collided with one of the food bits we'll start by going over to our food and we're going to add it to a group called food which we can do by selecting the root node coming over to node inspector tab select group we're going to type food and add this means that every piece of food we generate will belong to this food group groups tend to be a little bit inefficient so I try to use them sparingly but for a game of this this size this is going to work perfectly fine now with that added let's come over to our head class we're going to click on head and then next to where we had groups selected we're going to come over here to signals since both the head and the food inherit from area 2D we can make use of this area entered signal if I double click it's going to allow me to connect this on area entered signal to my head class right now this won't do anything but if we come in here and type print collide Ed with area. name and run our game see when the head collides with the food we get collided with food great so our collisions work now let's wire them up to do something meaningful first thing I'd like to do is differentiate between running into food and anything else in the game Snake the head can either collide with a piece of food or its own tail in the case of the food we want to remove that food and spawn another in the case of the tail we've crashed into ourself and the game ends so we're going to run a Check against this area which is the area of the thing we've collided to and we're going to say if area do is in group food we've collided with food else we've collided with something that isn't food in our case that's going to be the tail because there's only two things that we can collide with normally to remove a node from the scene tree we can call Q free on it so in this case we can type area. Q free when that Collision happens which will work but there's a better way to do this you'll see if I run into this it does remove it but since we're removing a physics object it's generally safer to defer this call until after that physics frame completes and before the next one starts so instead of calling area. Q free we're going to call area. call deferred and we're going to pass in the name of the function that we want to call this is going to cue that to be freed up at the end of the physics cycle rather than in the middle of it which can can cause unexpected issues but we can't just delete the food from the game we need to be able to tell the gameplay scene that a piece of food is been eaten this will allow us to cover that third common design pattern signals let's define a signal called food eaten that we will use to report to gameplay when we've eaten a piece of food to do so we can call foodcore eaten. emit right before we delete this to let the rest of the game know that a piece of food has been eaten of course emitting this signal won't do anything unless we tell what things to listen for that signal so let's go back into our gam playay and connect the food eaten signal to a function that lives in our gameplay class we can do that by calling head. food eaten. connect and then we're going to pass in a function that we're going to create now on food eaten we're going to get this angry warning because the on food eaten function doesn't exist but we're going to create it right down here let's add it print statement in here for a quick test food eaten if I run the game and we eat our food you can see we get the food eaten message suggesting that we dropped into our on food eaten function but of course nothing has happened because we haven't put any game logic in there yet we're making progress there are four things that we're going to do every time a piece of food is eaten for starters let's make sure that every time we eat a piece of food we spawn more food so that we can keep playing so we can call spawner do spawn food if I run this when we eat food we get more food but you'll notice we're getting errors and that's because we're trying to add a physics object while we're in the middle of a physics Loop we can fix that the same way we fixed the Q free by calling spawner do call deferred spawn food and that's going to wait until this physics tick ends then it'll add it before a new one begins if we run and collect food you'll see we're no longer getting that error great starting to feel like an actual game now [Music] the first thing that we're going to need to do is add an array called snake Parts you may recall that in an earlier video we decided to make the head and tail classes inherit from a base class of snake part that's going to allow us to store those together in this array statically typed as snake part in GD script you can mix different types of objects together in an array however you won't get code completion for example with this being a statically typed array if I try to access a member of our snake Parts array you'll see that I'm getting last position and move to suggested with autoc completion that's because we've told gdau that everything that's going to be in our snake Parts array will be a member of the snake part class which includes both our head and our tail since both our head and our tail are area 2DS we could statically type this array as an area 2D however we won't be getting our last position and our move to functions we could still call them but gdau is not going to help us with code completion and if for some reason we put something in that array maybe accidentally that isn't of class snake type then we're going to fail when trying to call that method statically typing things in this way is both a quality of life thing but it will also make our code more readable if someone else tries to come in and understand our logic with our snakes part array in place let's hop over to our spawner and create a function that will handle spawning our tail pieces in a larger game we might have one spawner for our food and one spawner for our tail but since our game is fairly basic it's going to make sense to put both of those responsibilities into this one spawner class in order to spawn our tail we're going to need a reference to our tail scene our spawn tail function is going to be fairly simple it's going to take a vector two that represents the location at which we want to spawn our piece of tail it'll instantiate move the tailpiece to that position and then like we did with the spawn food it's going to add it to our parent if we go back over to our gameplay script we can scroll down to our on food eaten method and use this to spawn a new tail piece again we're choosing to defer this call because we're adding a physics object coming back into our spawner you'll remember that the spawn tail function expects a vector 2 the location at which we want to spawn our new piece of tail will be the last stored position for the last item in our snake Parts array which will look like this snake Parts array snake parts. size gives us the length of the array and we're going to subtract one because the array is zero relative this will give us the last element in the array since we've added a new tail piece what we'd like to be able to do is type snake parts. push back and then add our tail piece here unfortunately we have two problems one we don't have a reference to the tail piece that we've just created we could set up our spawn tail function to return the piece of tail that we've created but that would pose another issue since we're deferring this call the tail piece that we'd like to add to our array won't exist until later however that's not a problem we already have the tools to fix this let's go back into our spawner class and add a signal called tail added and we're going to give it an argument of tail because when we emit this signal we're going to send along a reference to the tail piece that we just created we can add that down here by typing tail added do Emit and give it tail coming back into game playay just like we set up a connection to the heads food eatting signal we can do the same here with our spawner tail added and we'll call it on tail added and now let's declare that function we know it's going to receive a tail object so we can add that as an argument and then all we need to do in order to add our tail piece to our tail array is take this and move it down here we want to add the head to our snake Parts array because the first tail section needs something to follow did anyone spot the mistake that I made if I scroll down here to our spawn tail call we're mistakenly passing in the snake part object what we want is the last position stored within that snake part now if we test our game you'll see that we do get a new tail piece at the last location of the head however if we continue eating food all of the tail pieces are going to appear in the upper leftand corner at 0 because we haven't started updating those locations yet let's do that now in our update snake function right below where we move our head we will Loop through our snake Parts array starting at index one which is the second index or the first tail piece calling the move to function on each snake part element in the array passing in the last position value of the previous element in the snake part array with these two lines of code the first tail piece will follow the head and all other tail pieces will follow the tail piece that precedes it let's test our game and see if our tail [Music] works now that we have a working tail we need to be able to crash into that tail let's go back into our head class since there are only two things that we can collide with in this game food or the tail if we've entered the else condition that means that we've collided with a piece of the tail to notify our gameplay class when a tail Collision has occurred let's create another signal and emit it down here once again back in our gameplay class we need to tell gameplay to listen for this signal down here we can declare this function as a placeholder let's just print out game over Let's test this [Music] out we doubled back on ourselves and got our game over message to make this game a little more challenging let's increase the speed each time we eat a piece of food since we set up this speed variable at the beginning of the project the only thing we have to do is increase that each time a piece of food is eaten let's do that by adding 500 to our speed each time we eat a piece of food and if we test our [Music] game you'll notice that the Stak speeds up a little bit each [Music] time earlier in the video I promised that we would come back back to our tail class to add some code specific to that class let's do that now right now there's nothing here because the only code we're using is being inherited from snake part let's add an export variable that's going to contain an array of textures that we can randomly pick from instead of always spawning an apple as our tail piece you may notice that since we've added this export variable our textures array has shown up in the property inspector we can click on this array and add elements to it at the beginning of the project we imported six different textures that could be used for our tail right now we're only using the hard-coated Apple we can fix that with this texture array let's add six elements and drag our six Sprites [Music] in and in our ready function let's randomly assign a texture to our Sprite 2D from our textures array pick random is a built-in method of the array array class that will return a random element from that array if we test our project now we should get a random Sprite each time we eat a piece of food now our snake is moving more quickly with each bit of food and the tail is looking a little bit [Music] snazzier at this stage our game logic is mostly complete but we still have to create menus and a score before we can call this a fully functional game since we ended the last video by detecting when the snake crashes into itself let's start this one by building the game over screen we're going to use a node that we haven't used yet select other node and search for canvas layer we'll start by adding a panel this panel node is going to serve as the background for our menu but it's also going to Define some layout properties we'll start by setting this anchor preset to full wct which will make sure that it takes up our entire screen in the inspector let's expand the visibility section click on modulate and drag the alpha down this will make our background a little bit transparent so we can see through to the game behind it next let's add an hbox container and also set this to full wct then we'll add a vbox container and set it to expand horizontally we'll set our vbox container to centered and our hbox to centered as well this will Center all of our control nodes both horizontally and vertically inside of our menu let's add a label this will be our menu's heading we'll enter game over under theme overrides let's select font sizes and make it nice and big under colors let's turn on our shadow and under constants let's make the shadow more prominent we're going to add add two more labels and two buttons one label to show our final score another label to show whether that final score is a hide score as well as two buttons one to restart and another to quit we'll want to make sure these are coming in as children of the v-box container not the label so let's select vbox container and add our nodes now let's call this score lay we'll call this high score label and these will be restart button and quit button for now let's default score to score poen zero we'll make high score label read new high score and let's give the restart button H text and the quit button it's text while designing a very intricate game over screen is outside of the scope of this project I want to do a few things to clean this up a bit first let's let's Center both the score labels let's make the score a little bit bigger let's change the color of the high score label if we'd like our buttons to look more like buttons and sit side by side we can put them inside a grid container select vbox container search for grid let's drag both of our buttons in here and with them both selected let's come into layout and set a custom minimum width of 150 if we want them to sit side by side we'll need to give the grid container two columns and to Center them select the grid container and set the horizontal alignment to centered the last thing I'd like to do is put a little separation between our buttons and our text because everything's feeling a little bit cramped right now select the vbox container and let's search for margin container we can drag our grid container inside of our margin container then with the margin container selected we can come into theme over rides and give it a little bit of margin on top let's say 25 pixels it may not be the prettiest game over screen that you've ever seen but everything that we need is here let's rename this to game over let's save this in our menus folder I'm going to get rid of this exclamation point because it feels too angry and then with our root node selected let's attach a script I'm going to name it game over now that we have all the nodes we need let's start wiring things up first we're not going to need any of this next I'm going to select the four nodes that we'll be working with so that we can bring them in as on ready variables I'm going to command click and set all of these to Unique name I'm going to drag in hold control before I release and now we have our four nodes I'm going to shorten the names to save us some typing because none of these nodes are using custom classes we don't need to worry about typing as label or as button to cast them our code completion will be intact let's start by connecting to our two button signals select restart node and look for the Press signal we want to connect it to our game over script let's do the same with our quit button in a simple game like this if we want to restart we can reload our current scene which we can do by calling get tree. reload current scene this solution won't work for all projects but in a single scene game like this reloading the scene will reset everything and to quit we can call get tree. quit which will close the window it's worth noting that calling quit on something running in a web browser will have no effect next let's set up our get score function we'll call this when the game ends to pass our score variable into the game over screen inside of our set score function let's set our score text equal to final score colon the score that we' passed in which we'll need to cast to a string because we're adding it to another string later this is where we'll compare our final score to our saved high score and display the high score field if necessary and also overwrite the older higher score now let's return to our gameplay scene and look at how we can pull this in you could include your game over screen and have it hidden by default but I prefer to instantiate it as needed let's create a constant call it game over scene let's go into our menu grab our scene drag control click and release I'm also going to create a variable called game over menu of type game over when we instantiate our game over scene we're going to store it in this value that will allow us to to check whether a game over scene already exists and if so not spawn another one or recall the existing one now let's come down to our on tail collided and check whether our game over menu is still null if not game over menu meaning if we haven't added anything to this variable then we're free to instantiate a new one just like we did with our food we're going to instantiate it and cast it to game over store it in our game over menu and add the child to our gameplay node since we're not actually tracking a score yet let's go back up to the top of our class create a score variable of int initialized to zero and each time we eat a piece of food let's add one to our score now when the game ends we can call our set score and pass in our [Music] score and there we go we seeing final score seven however you'll notice the game is still going let's fix that now going back into our game over script to pause the game while the game over menu is up we're going to make use of gau's buil-in notification method gdau objects are prubs cribed to these engine level callbacks the ones we care about are enter tree and exit tree these notifications fire when a node is added to the scene tree or removed from it the notification function receives an integer in the form of this what variable that corresponds to these more human readable constants in this case notification enter tree and notification exit tree if you've never seen a match statement they work a lot like if else blocks this is roughly the same as this in plain English this will pause our game when the game over scene enters the scene Tree by setting get tree. pause to true and setting that back to false when it's removed there's one more thing we need to do in order for this to pause our game when the game over screen comes up first let's make sure we're in our game over scene let's select the root node come into process and change mode inherit to mode always if we don't do this the game over screen itself will be paused as well and we won't be able to interact with any of the buttons to restart or quit the game now we can save and test the game if few moments later now when we crash the game is paused you'll also notice that our restart button is working as is our quit button great so our game over screen is working now we can repeat this process to create our start screen let's go to scene new scene other node and again search for canvas layer let's rename this to start screen add our panel and set it to full wct since our starten screen will never be appearing over anything else we don't need to change the modulation so that we can see through it let's fast forward through this part since we just did this for our game over screen now that we have our start screen set up let's save it in our menus folder and let's add a script again let's drag in our references we don't need our process but we are going to use our ready function once we have our save game set up this is where we'll find out what the highest score is and set it here for right now we'll just hardcode a value of zero and of course let's wire up our signals our quit button is going to be the same here as it was in our game over screen our start button however is going to be a little bit different since our start screen is going to be responsible for loading our gameplay scene we're going to need a reference to our gameplay scene and because in our game over screen we're using this built-in function to reload the current scene we need to make sure that we use the built-in function change scene to packed in order to replace the current scene with a new scene this method works fine for small to medium siiz games for larger projects you're likely going to want a much more robust scene manager that handles things like background loading Transitions and passing data between scenes good news everyone since I first recorded this I actually made my own scene manager and I'm going to link it right here now back to our reg now back to our regular now back to your regular never mind when we've been starting our project it's been going directly into our gameplay scene now that we have a start screen we want to go into our project settings under run we're going to want to change this from gameplay to start screen now when we run our project instead of loading the gameplay scene it's going to load our start screen and if we click our start button our start button is going to load our game and our game over screen is going to allow us to restart or [Music] quit we're going to set things up so that if the player presses the Escape key it will pause the game and allow them to quit or return we'll also set the game up to detect whether the game has lost Focus meaning the player has clicked outside of the game window so that we can pause the game in that scenario as well one more time we're going to go to scene new scene find our canvas layer add a panel we do want this one to be transparent because it's going to show up over top of our game when paused and I'm going to speed through this as well since since now we've done this twice that's all we're going to need for our pause menu let's go to Canvas layer rename this to pause menu and save it in our menus folder let's add our script grab our references we don't need either of these let's wire up our signals on the resume button we're just going to call Q free which will just remove this from memory and the next time the pause menu is needed we'll just recreate it actually let's make this a restart button someone's probably more likely to want to restart from that menu than quit so we'll just put our same reload current scene here and the same as we did with the game over screen we're going to leverage these notifications to pause the game we have to remember to select our pause menu and in the inspector set our process mode to always so so that we don't pause the pause menu in our gameplay scene we can use the notification function again to detect when the game has lost Focus since there's going to be more than one way to pause the game Let's create a new function called pause game and we'll declare that function down here we're going to use the same technique of checking whether we've assigned something to our pause menu variable if we haven't we can instantiate our pause menu scene and add it to the scene we can see this in action by testing our game and clicking outside the window now the game is paused I can hit resume to replay and we do have to go back into our pause menu and rename this restart the last thing that we're going to do for this video Hey listen when we rename this quit button we broke this so let's let's just rep this in there we go the last thing we're going to do is let the player call and dismiss the pause menu with the Escape key first we actually are going to use our process function so let's grab process here and we're going to say if input action just pressed UI cancel which by default is mapped to the Escape key we're going to Q free and the other place we're going to need this is in our gameplay script anywhere in our process function which you can do the same thing except instead of calling Q free we're going to call pause game now if I run this hit start and press the Escape key the game pauses if I press the Escape key again it resumes we'll start by creating a new script to manage our save data we'll go to create new script and this one's going to inherit from resource let's create an export variable of integer called high score where we'll save our highest score let's also Define a constant called save path with the path to the file where we're going to store this data this user colon SL slash is the default save location the name you choose for the file doesn't really matter we'll use gdau built-in Singleton the resource saver to call the save function passing in this class including our highscore variable and the path to where we want to save the file let's create a new static function called load or create that will look for an existing save file and load it or if one isn't found it'll create a new one we're going to make it a static function because that will allow us to call load or create directly from the save data class rather than having to create an instance of it to call that method this is all we need to do for our save data file to make sure that this save file is always available to us let's come into our autoloaded Global file create an instance of save data called save data and in our ready function let's call Sav data. load or create and store it in save data this will ensure that the second our game loads it will either pull down our saved file or it'll create a new one if this is the first time the game has been loaded this is also all we need to do in our Global file now if we come over to our start screen where we had previously hard-coded high score let's access our save data if we run the game this shouldn't look any different because we don't have a high score yet however just as a quick test we can set and save a new high score value reload the game and we'll see that it's now there running this we'll fire our temporary test code and now if we reload there's our high score of 10 to clear that out we can come up to project open user data folder find our save file and delete it this will reset our high score to zero let's clear out this test code and add some logic to our game over screen to detect for a high score and save it if we set a high score first we'll check whether our score is greater than our high score on record if it is we're going to make sure that our high score display is visible we're going to save this score in our high score variable and then we're going to call Save if on the other hand our score is not a high score we're still going to set our final score text but the only other thing we're going to do is hide our new highscore display Let's test this out as expected our high score is zero I'm going to collect [Music] one now you can see our final score is six and we're getting our new high score display if I restart [Music] and crash after four we're not showing our new high score but we do see the correct final score let's quit and come back into our game over screen and make our panel a little bit darker because I think that final text is a little hard to read and that's all we need to do to save our high score but the player still doesn't know what their score is while they're playing nor do they know what the high score is that they're trying to beat so let's create a HUD to display that information while we're playing by going to scene new scene choose other node canvas layer as we've done before we're going to add a panel but this time we're not going to set it to full wct we're going to set it to top wide let's add an hbox also set it to top wide and instead of begin which will push items to the left let's set it to end because we want our scores in the upper right hand corner now we can add two labels we'll default it to score colon zero duplicate it call this high score and set this to I score for consistency let's set our color to Something in the orange range or high score color rather and because this is looking a little bit cramped to me let's add some margin containers let's select theme overrides constants and put a little bit of padding on the right and for our high score just so it's not touching the right side of the screen we'll give that a little bit of padding as well this will give us a transparent HUD if we want the HUD to be an entire bar we can give it a minimum height of 32 to make it fit nicely in our grid with a visible background now our text is no longer centered vertically let's select our margin containers come back into our constants and add some padding on the top there we go that looks pretty good let's name our layer HUD and save this in our menus now we can go back into our gameplay scene click the link to add a child scene search for HUD and add at the bottom which is where we want it because we want this to be on top of now if we test our game you'll see that our character does move under the hood as EXP expected which might be what we want however this is a good opportunity to demonstrate the value of our bounds system if I don't want the character to show underneath the HUD and I don't want food to spawn there either we can simply grab our bounds grab the upper left and move it down one grid position now if I replay the game you can see we wrap just at the hood all right let's finish up our Hood we'll go back in here and add a script let's give it a class name Hud get our references control release and in our ready function we can set our high score text now we're showing our high score of six great we don't need our process function but we do need a way to update our score as we collect food now we can go back into our game play grab a reference to our HUD let's remember to cast it to HUD and every time we update our score let's reference our HUD update score and pass in score now when we play our game every time we eat a piece of food the HUD updates okay we are almost done before we wrap up this video series I want to do a little bit of cleanup and I also want to refactor this so that we can make use of a getter and Setter function we don't really need it again because this game is so simple but in a more complex game this design pattern is really helpful so let's take a look at Getters and Setters if I scroll back up to my score variable instead of setting this zero here I can add a colon and add these two sub definitions this is our getter which is the same as accessing that variable directly it simply returns its value and this Setter function which will accept a value same as doing score equals what whatever number but then every time we set the score it's also going to call our hud. update score and this is powerful because you won't have to remember to call hud. updat score every time the score changes imagine that this game also randomly spawned bonus items that only lasted on screen for a few seconds if you collect them you might get more than one point when you do collect those you'll be updating the score in more than one place and then you would also have to remember to call hud. updat score in this case you can simply call score plus equals whatever value and the HUD will automatically be updated regardless of where you call that from now that we've set up our getter and set we can come down here and we actually no longer need this line Let's test the game again and even though we're no longer calling that after updating our score our score is still updating in the HUD because our Setter is now handling that for us before we finalize our game let's do just a little bit of cleanup I'm going to go back through and remove all of our print references going to tidy up some gaps I generally like to have all of my variables together so let's move these down here I had paired them in that video with the constants so that you could see that relationship I like to have my constants at the top my export variables next my onready variables and then all the rest of my variables at the bottom it's sort of a logical order of operations for me constants are what they are preloading happens first the export variables in my mind happen before the onready because you're doing them in the editor then the on Ready's fire and then your garden variety variables are stood up at the end again you don't have to do them in this order lastly let's clean up our ready function I'm going to put a space here to separate this code from all of my signal connection code and I'm going to change this up just a little bit first I'm going to add this line of code here that will essentially Force our snake to start moving immediately rather than waiting one full tick before it moves and I'm going to get rid of that space we could go through the rest of our files to do similar cleanup but I will encourage you when you follow along or take this project further to find an organization system that seems logical and comfortable for you with that we've got a fully functional game with a score a high score system a pause menu a way to restart and along the way we've covered more than a dozen common fundamental design patterns in gdau hey you did it before you go a couple of housekeeping items first of all congratulations I hope you enjoyed that and feel more confident starting your own project if you need help please get in touch this completed project is available on itch which I've Linked In the description down below and for those of you who have written your own version of snake you should check out this video next it includes five coding challenges that you can use to test what you've learned if you've stuck with me for an hour that's got to be worthy of a like and a subscribe and look if it's not we can still be friends until next time please be kind to yourself be kind to others and I will see you in the next [Music] video
Info
Channel: Bacon and Games
Views: 2,722
Rating: undefined out of 5
Keywords: godot4, godot game tutorial, godot beginner tutorial, godot 4 game tutorial, godot 4, godot 4 tutorial, full game godot 4, learn godot, learn godot fast, learn godot today, easy godot tutorial, easy godot 4 tutorial, free godot course, gdscript tutorial, godot gdscript, full game tutorial godot, godot game resource, godot 4 game resource, learning godot, my first godot project, full godot game tutorial, godot for beginners, game development course, indie dev, game dev
Id: c7HQwxs5y8w
Channel Id: undefined
Length: 66min 46sec (4006 seconds)
Published: Fri Mar 01 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.