Intro to GDScript 2: Procedural Map Generation Tutorial

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
this tutorial is a follow-up to our introduction to GD script in guru 3.1 in this one you are going to see how to use what we learned in the previous video link on the screen in the description to generate a procedural game world I invite you to get the source project you will find it in our Kickstarter quest 3 repository you want to go down and grab the GD script intro procedural time map folder there you will find a start project a start folder that can import in Godot like so so open Godot go to import browse to the start folder for me it's going to be in repositories Kickstarter quest 3 intro to procedural tilemap start and you want to import the project that guru file once you've done that you'll be able to launch the project in Godot I have it here and you will get some base assets to get started the it's all set file that we're going to use to generate the map from you will have some font and the tone set stalled as a resource edited with the new tone set editor so that we have created the cells for you as well as a template script I'm not going to write every single line from scratch we're going to save a bit of time for that so you do have some documentation for what this class does and methods do we're going to generate a map with a border around it the border is the set of rocky tiles that you can see all around the map and you have the center which is the main map that the player could for example navigate in a full game the big takeaway from this video what's most important is that you can see that with what you learned already with just Jesus Crypt basics you can create something as interesting and as complex as a procedural map generation class at the start you might get a view like this when you import this stamp project we want to switch to the 2d workspace as we're making into the game so at the top of the screen you can click on the 2d button or you can press the f1 key we're going to create a 2-d scene our root node will be a note 2d and we're going to call it something like game world this is the name I gave to the script so far while going to need a tall map node in which we will plug the tile set from our assets folder select the game world press ctrl a to add a node and type tall map to find it's our map node enter to add it and so it takes a tall set resource that we need to drag and drop on it so drag and drop tile said that T res on the slot then you will see that the cells are too small for the tile map the textures that we have so we want to go into the cell category and change the sales size to 32 by 32 the size of our texture here we are going to add a little something extra that we have in the final game that you don't necessarily need it's a panel with a label inside of it so some UI elements to tell the user that when they press space or click we're going to regenerate the map so add a panel and we're going to use the W key the move tool up there to place the note W you can click and drag to place the note can click on the snap icon to snap to the grid G to toggle the grid and so that way you can move it by 10 pixel increments by default give it a bit of margin then cue to select the select tool here and we're going to drag to extend the panel a little bit we're going to add a label inside of that panel so press ctrl a to add a label node type label to find it this is I know that's going to contain text press ENTER to add it and with our UI element selected we're going to go to layout for rect to make it take the entire space of its parent the panel the only space it can then in the inspector on the right we're going to add some text so click to regenerate the map for instance and we can align our text horizontally you can Center it and same thing vertically now because our label is set with a layout option to follow the full rectangle of its parent when you select the panel and you resize it you will see that the text will always Center both horizontally and vertically inside of it you can also select the label go down to custom fonts we need to create a font resource in order to to change the font so click on the arrow or on the empty next to the font property create a new dynamic font click to expand the font expand its settings and its font here we're going to drag and drop the font file ATT f4 ODF file into the phone data slot to use this font then you can change the size of your phone to fourteen points or 14 pixels in this case if your panel is too small the text will overflow a little bit you can then click to reset the sizing and if the text is too big if it's going to overflow from the panel can increase the panel size and the label will follow it as soon as there's enough space for it press ctrl s to save the game world scene we're going to save it at the root and from there well going to drag and drop the game world script the template onto the game world note now we can get coding so let's click on the script next to game world to access it I'm gonna press ctrl shift F to expand my script editor to have a bit more space here the first thing that we are going to add right below our node we're going to use the new class name keywords' in guro 3.1 class underscore name lets you define the name of the class that you are creating or the name of the node that you want to create so you can use it in other scripts the script is called game world Gd we're going to use the same name here for consistency then we want to start by defining the signals public variables and the private variables of our script all the signals were going to have to started and finished for the node when it margin ratio starts and finishes in a full game this can take a bit of time so you want to have signals so that other systems are the scripts in the game can know when the process starts and when it ends we are going to have a few values to define how our map works we're going to have an enum for the cell types that we have in the game for that you use the enum keyword you name your enum it's a list of constants if you want it's going to generate constants for you so the three that we are going to have is we're going to have some obstacles we're going to have ground tiles and we're going to have the outer cells the border around the map before the public variables we're going to have some exported variables so you create them with the export keyword followed by the VAR keyword create a variable and we are going to have the inner size of our map it's going to be a vector - it's going to represent a grid so you we need both the x and y axis to represent the size of our map and by default can set it to 10 by 8 when you export a variable like that and give it a type if you go back to the 2d editor I can do that using f1 when you select the node in the inspector you will see your exported variable this is what the export keyword does and it supports most of the types in Godot you could export an array you could explore a dictionary you could export a floating-point value an integer or make it a slot to load another node you can use that to make your load more editable in the inspector okay so we're going to add two more exported variables going to paste them here we're going to have a parameter size it's going to be the size of the border around the map and then ground probability will give us a probability 10% chance to have obstacle versus ground tiles when we generate the map next up are our public variables we are going to calculate the total size of our map here from the inner size and the parameter size so the calculation is as such our size is going to be in a size plus two times the parameter size y2 times the parameter size because while giving the size of our border but it goes all around our map so we need to multiply it by two because it's going to be both on the left and the right and on the top and the bottom of the map Leslie who are going to have our private variables variables that we can access from within that script but that we don't want any other developer in our team to access we're going to append an underscore in front of the name of the variable like so we're going to have two the first one is a reference to the Tal map node in this script we have it it's a private variable so we can put it here or another thing that we do in our GD script guidelines is to put that one at the very top of the script right under the signals and we put all dependencies all the nodes or other scenes that this script depends upon at the top but for now as it's a private variable we're going to put it down here so the time map node is of the type tall map and it's going to get the tarmac node it's going to store a reference to that tile map node now you will notice a new keyword here already and in Godot you have a method that can override in the node class called ready it's going to be called only when the children of the node this script is attached to have been created and added to the scene tree so Godot starts and goes from top to bottom when it creates the nodes but it also come starts with the deepest nodes and then creates their parents so when you put code in the ready function here or if you use the unready keyword before the VAR keyword you are sure that this style map node has been added to the game world and one last note this dollar notation is a shorthand for the get node method that all nodes have so get node followed by the name of the node is the same as writing dollar tal map you can see that we get the same completion option the only difference is we don't have to use the quotation marks here if it's just the name of a node the 2nd private variable we have is a random number generator random number generator is a class that we have in Godot that will generate random numbers as its name suggests so we create one instance of this object and we store it in a variable at any time you can control click on a given symbol here what we call a symbol that is the name of a variable for example it also the name of a class if you do so good-o will open the documentation in the script editor as you can see right here and so you can see the methods that this class has and our instant has can generate a random floating-point number it can generate a range it can generate the same with integers you have a randomized method that we're going to use to randomize the map every time we generate it so right click on that you can close it or use ctrl W to do so and with that we have our base to start coding our node so the way by the way you come up with all these variables first it's not always linear when you are creating your own classes you will see that you think about how it's going to work and you can write down on paper some of the variables that you will need and as you code and add new features to that script you will need often to add new public private variables unready variables like these adding new nodes etc but that is all that we will need for this example next up I'm going to remove a ready function and willing to work on the set up one in the setup function we're going to make the window two times the size of our tile map so the tower map we've scared will scale with the window the the game viewport if you want we're going to use this code here so first we want to calculate the size of the map in pixels it's going to be our size public variable from up there here we're going to multiply it by our tile map nodes cell size so then we use the get tree method to get our tree node it's the node that contains all of your nodes in the game all of that century plus everything that you would load from the code when you are running the game and we use the set screen stretch method on it it's going to change the stretching options for the screen you have some constants on the scene tree class in order to set that the first argument is going to be the stretch mode we're going to use 2d then we're going to use the aspect option stretch aspect keep so it's going to keep the aspect ratio of our game it's going to if you expand the window it's going to add black borders and we are going to use our map size in pixels as a base for the screen ratio then we use the OS class here this is a global class we have access to from any node any script in Godot and has a method to change the size of the window can set it to 2 times the map size in pixels if you were to try the game nothing would happen so if you press f5 you can try out the game and then you'll want to select a map to load by default when you do so you can select your game world at t sen when you do so nothing happens the window is 1280 by 720 by default for this method to be called we have to call it from somewhere we're going to do that from the ready method so let's type function and it's call ready good who should autocomplete that we can call setup from here now if you press f5 again it's going to save your script for you then you will see the window size is different now it's equal to two times the size of the map and the text is a little too big but we will fix that a little later the next thing that we want to do is to code our generate function and already if you look below it you have two other methods generate parameter and generate inner we're going to call these two methods one after the other from the generate function so generate function is going to be a main function that generates the entire map we can replace that by we're going to call first generate parameter we're going to generate the outside of the map and then generate inner ok and it's always interesting to break down your functions like that so that for example the map generation process is clear to the user and maybe you want a simple function that your teammates can call on that node to regenerate the map but inside as the code is going to get quite complex in a real game you might want to break it down into multiple functions so that the code is encapsulated you create functions with a leading underscore like that so that we are private so that you tell your teammates don't call these functions directly but also so that when you read the generate function it's self documenting you first generate the parameter then you generate the inner part because you could remove these two functions and have many lines of code in a row instead but it can make the code harder to read anyway we're going to leave them as public functions and we're going to add something else you know that we have signals at the top of the script to tell when the map generation starts and finishes and it's going to be instant in this example because this is a very simple map generation algorithm but procedural generation can take some time and so you want some signals to make the process asynchronous to tell the rest of the game systems or a loading bar or something like that when the process was finished and to do so back to the generate method before you call the functions you want to use the emit signal method yet another method that's available on the node class and you're going to add quotes and if as you type the signals that you have created in your script Godot will offer you to autocomplete them we're going to omit the started signal before we start generating the map and after we are done generating it we're going to call the finished signal next up is the function to generate the parameter of our map let me paste some code in here and I'm going to explain how it works so while creating two nested loops that's all going to fill first the columns on the left and the right sides of our map and then the top row and the bottom row the first three lines are going to fill the columns left and right sighs and the next three lines are going to fill the top row and the bottom row now let's explain what's happening here so we are going to create an array with two coordinates 0 this is going to be on the if we take our map as a grid on the x-axis 0 is the first column and size that X minus 1 is going to be the rightmost column this is due to the fact that when you access elements in arrays in a computer counting doesn't start at 1 it starts at 0 then we have a second loop so we are going to first X here is going to first be our first column and then it will in the second iteration it will be the last column here and so for the first column we're going to loop vertically so we're going to use the arrange function to generate an array of all the cells that are in one column of our map so we create a range from 0 to the size of our map on the vertical axis and be the last value is going to be sized at Y minus 1 range is going to generate an array with the values 0 1 until our size was 8 no it's going to be 10 so until 9 so in total you will have 10 elements in the array then for each of these cells so it's going to loop 10 times here in that little loop for the first column we're going to set each individual cell using our x and y cell coordinates here to the index of a tile texture if I go back to the map here select my tall map in my tile set if I were to expand it you can see that you have a list in the inspector of all the textures it contains so it all set is a grid of texture and each of the tiles each of the little textures has a number associated to it so 0 is going to be the first one here then one is going to be I should select detail set and remove names so that you can see the tile so this might be I'm not sure about the order but 0 1 2 3 4 5 etc and we have 28 of them in this case so right now it's not going to generate random tiles it's just going to use the first texture we are going to replace that zero here with a function that's going to pick a random texture but first the second loop is going to be it's the same structure as the first one except that it's going to generate the tall's the other way around so it's going to loop over all the first row from the second cell to the size dot X minus 1 so to the cell that's right before last going back to the map again to show you this second loop is going to do that so the first loop is going to generate the tall's like so vertically in the top left in the left column and in the rightmost column and then the second loop is going to do something along those lines it's not going to start at the first cell because it's already been filled it's going to start at the second and up to the one before last it's going to do the same at the bottom that's what the two loops do in our code back to our code now we are going to replace that zero here with a function that we will call pick a random texture among a certain type so we are going to pass in going back to the top of the script one of our cell types and for the parameter of our map we're going to pass the outer type CEL dot alta and we're going to do the same in line 60 the second time where we set the texture for the cell here now this function you will see an error it does not exist in the start script I'm going to add it here link to copy it from the other file and I will put it at the bottom of my script go down a little bit and paste it here so the pick random texture function is going to take L a cell type the cell type argument is an integer because each of the values that our enum generates at the start of the script obstacle round in outer are going to be integers obstacle will be 0 by default ground will be 1 and outer will be 2 what matters is that we are going to use this with conditionals with conditional statements to change to calculate or set an interval of textures of tiles that we want to use so the Alta tools in our tile map are going to be all the textures from the first one to the 10th one from ID 0 to ID 9 if we go back to our tile set resource it's going to be from the first cell here to the ninth one I don't know which one that is exactly it's going to be maybe it's going to take the rocky ones you can see on the left side it depends on how you create your own tile set the IDs are going to be a bit different then the obstacles will be the grass area here the next five or six styles and finally the ground tiles that the player is supposed to be able to walk are going to be the remaining textures in our it's all set so going back to the code that's what's happening here we create an interval variable that's going to be vector two just so that we can have two values inside of it from zero to nine from ten to fourteen fifteen to twenty seven then depending on the cell type that we request we are going to give a different texture so we're going to set the interval to correspond to these tile textures with a if Elif statement finally we return a random tile between the interval using our rng random number generator private variable so our private instance we call rent integer range it's going to return a random integer value within a range and the range we pass it we start and end value which are going to be our interval vectors x value to the interval dot y this is going to give us everytime a new random tall so going back to our generate parameter with that we're going to get a random parameter and we can already test that so to do so we need to call our generate function from our ready function go back to ready we're going to call the randomized methods on our air our ng variable this is going to randomize the map generation every time then we all set up and finally we are going to call generate now when you press f5 you should see the maps border only the map boards are being generated you can at any time go back down to generate parameter and you can comment out a few of the lines one of the two nested loops to see how the code is generating these sides of the map if I uncomment the second loop and comments the first one you're going to see how we generate the first and last rows guess how we are going to generate the inner part of the map in the generate in a method little hint you have to look up there yeah we're going to use the same type of nested loop so I'm going to paste the code in here we're going to range this time on a grid so instead of creating an array with only two values inside of it that represent the first and the last column for example this time we're going to create grid for that you nest two loops together and you're going to use the range function in both cases to create a sequence of values in this case you're going to have 1 2 up to size dot X minus 2 like so - - and in this case it's going to be - to the last value is going to be 8 I believe because the size is 10 and same thing on the y-axis right you create two arrays so for each element in this sequence of eight values for example you're going to have you're going to loop over entire columns every time on six cells is it it should be six different cells and for each of the cells in this grid that we essentially loop over we're going to get a random tile we're going to add that function in a second using our ground probability or our obstacle probability here maybe you should rename that variable I have to check the code it's either of these and then we're going to set the cell on tall map exact same thing that we did up there in generate parameter using the cell that we got the random tile texture that we store in a variable it's going to be an integer value it's going to be the same kind of thing as I will pick random texture function so let's create that function writes and generates inner you need to create a new get random tile function it's going to take a probability as an argument and this is going to be a floating point value then we are going to return the index corresponding to a town in our town map so an integer and this one is going to return or is it we're going to use pick random texture to pick a random texture but it can be either a ground tile or an obstacle so we're going to return the result from pick random texture and we're going to pass cell dat ground we can use conditional statements in an expression like that if a certain condition is met we're going to use our random number generator and generate a random float if the random value that we generate is lower than the probability that we pass to the function else we are going to return pick random texture but this time we are going to pass the obstacle type get random tall is a utility function that's going to call the Krenim texture with a different arguments based on a random number every time you call get random tile if you are going to get a new random tile if you test the game at this point you're going to get a full map now we can not click to regenerate the map just yet in order to do so we have to go we're going to go down to the bottom of the script and we're going to add some function to listen to the players input and react to it this function is underscore unhandled input this function is defined in the Godot engine and when you override it on a node Godot is going to send input events for example moving the mouse tapping on a key clicking things like these it's going to pass them to this function and it's going to pass them under this event argument which is an input event and then you can listen to that event and if it is something that you want to react to you can put some code here to react to the event so you do that with conditionals if the event and this input event class so V event variable gives you access to a number of methods like is action pressed and then in the project we defined an action called click which is mouse clicks it works if you click with the mouse so you can listen to this one the is action press method is going to return a boolean value as you see from the return type here and the completion hint and it's going to return true if the event that was passed to this function corresponds to the action here the click and then if we did click we're going to call the generate function again if you do that press f5 now when you click it will generate a new map every time note that these input actions are defined in your project settings you go to the project menu project settings and input map to set them up you have a few by default UI underscore and a number of things to navigate the user interface these correspond to the input actions that the godel editor itself uses because the godel editor works the same way with the same UI nodes and then go to its of Godot the editor is a good game written in C++ if you go at the bottom you have the one that we define for you but click and it listens to the mouse the left button to create a new in production you type the name in the action bar so for example could say space or confirm you call it however you want it's the name you will use in your code to listen to that input action or react to it or check for it then you click on the Add button or press ENTER to create it it's going to create an entry in the input map after that you have to click on the plus sign on the side here to add an input that you want to map to that in production for example for confirm you may want to have a key on the keyboard the space key for instance so you press the space key here and press ok it's going to register it in the in production and you may want to listen to a mouse button the left mouse button like we did with click once you've done that I can delete the click input action and instead of click here we're going to listen to ferm if I play the game you can see my inputs at the bottom and came on here if I press click it's going to regenerate the map like press space as you can see down here it's going to regenerate the map as well because my confirm inputs action listens to both the click and the space if we want to scale down the click - originally the map here can do something nice it's if you go to the debug menu make sure that sings scene changes and script changes are checked and you can if you launch the game after doing that I'm going to restart the game here lower the size of that bottom panel it's a bit big I can go to my panel I can scale it down I can go back to my font and make it a bit smaller here save and if I go back to my game instantly you can see the text is already smaller so that is it for this tutorial please tell us in the comments if it was easy enough to follow the idea is not is mostly to show you things you can already do with everything you'll learn with GD script obviously if you are just getting started with programming this goes a bit fast and you will need to spend more time with each and every component of the girdled game engine to start to be able to code those kinds of programs on your own but the idea is to show you a real world example how everything you learn in the previous video can work together in practice to create an interesting little program in the video description you will find a link to the kickstarter projects repository where you can find the source code it's everything we do is open source here so you can download the the projects to look at the final example and study our code here reading code is a great way to improve your programming skill but for now I want to thank you kindly for watching so be creative have fun and let's see one another in the next one bye bye
Info
Channel: GDQuest
Views: 67,514
Rating: undefined out of 5
Keywords: gdscript tutorial, procedural generation, godot gdscript tutorial, godot gdscript beginners, godot procedural generation, godot engine, godot tilemap tutorial, game creation tutorial, godot engine procedural generation, game development for beginners, godot engine tutorial, game design, godot game engine, open source, godot tutorial for beginners, godot 3, godot gdscript, game programming, game development, game dev
Id: 1JcrXlD-iCU
Channel Id: undefined
Length: 39min 42sec (2382 seconds)
Published: Sat Mar 23 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.