IN THIS LEVEL
It’s not all fun and games, but let’s start out having fun!
In Level 1 you’ll learn how to:
Download and Run Godot
Create your First Project
Start Thinking like a Developer
Download Art Assets
Create Import Scripts
Import Floors, Stairs and Walls!
“I’m Gerard the Knight. My friends and I will be here to give you additional information on various subjects. We will also provide links for deeper dives on some subjects we discuss!”
Downloading & Running Godot
We are going to start by downloading and running Godot.
Go to https://godotengine.org/ and click the Download Latest button. You will be taken to the download page for your operating system.
Click the link for Godot Engine we do not want the .NET version. Godot will download, and a pop-up will appear with installation instructions.
Follow the installation instructions for your operating system.
Run the Godot application.
When Godot starts up, you will be in the Project Manager.
“We are assuming you are using Windows, but if you’re using a Mac, the main difference is the Command key will be used instead of Control key for key combos.”
Creating Our Project
Before we can create a project, we need a name. For this course, we are using Dungeon Delvers
Click the + Create button.
Name the project Dungeon Delvers.
Click the Browse button next to the Project Path. The Open a Directory dialog will open. It defaults to the Documents directory, which we do not want.
Click the Up Arrow next to Path at the top of the dialog.
Right-click in the window and select New Folder.
Name this folder “Projects”. (Everything we do is going to be built off of this Projects folder.)
Select the Projects folder.
Click the Select This Folder button at the bottom left of the dialog.
Select Forward+ under Renderer.
Select Git under Version Control Metadata.
Click the Create & Edit button.
Godot is going to open up our new project. You may have been wondering about the Renderer option. In past versions of Godot, you needed to be much more careful about what option you selected. In Version 4+, you can now change the renderer during export to target different devices. Since we are doing this on a computer, we want the best rendering possible while we are testing our game.
We have one more thing to do before moving on. We want to make the Project folder our default for new projects. This way if you import anyone else’s projects to see how they work, or make your own new projects, they will automatically be organized for you.
Go to the Editor menu and select Editor -> Editor Settings…
Find a box that says Filter Settings near the top of the dialog. (These boxes are everywhere and they help you find things when there are a lot of options.)
Type “project” into the Filter Settings textbox.
Find the Default Project Path value.
Click the Open dialog button that looks like a folder to the right of the Default Project Path value. (Under FileSystem -> Directories.)
Browse to your Projects folder.
Click Select Current Folder.
Close the Editor Settings dialog.
Now that we’ve got our new project running, we are actually going to minimize it. Before we can start creating our game, we need assets.
“Hi! I’m Lucy the Mage! My area of expertise is Game Design. Before we can start making a game, we need to come up with a name and a story.
We will be getting more into story later. You probably have your own story ideas, and maybe even your own game name picked out. If you do, use it now!”
“I’m Bjorn the Barbarian, and I’m here to teach you good development practices!
Organization is key! Our project is going to get complicated very quickly. So, we start by putting everything in a Projects folder. It’s paying attention little things like this that will make you a professional developer!”
Downloading Assets
There are lots of places to get assets. For this course, we will mainly be using resources from itch.io. Itch is a site for people to share and sell (and buy) games and game resources. Once you’ve finished your game, you will be able to share and even sell it on itch. So, the first step is to create an itch.io account.
For our assets, we are going to be using free assets when we can. However, we will also be using some paid assets. When we get to those sections, you can either buy those assets or do those parts with the free assets you already have. To start, you need to download your first asset pack and store it somewhere.
Dungeon Remastered by Kay Lousberg
Open up File Explorer.
Browse to the folder that contains your Projects folder.
Right-click on your Projects folder and select Pin to Quick Access. (It will show up on the left-hand side of the window and you can now quickly get to it.)
Open the Project folder.
Create a new folder called “Assets”.
Open the Assets folder.
Create a new folder called “3D”.
Move the zip file into the 3D folder. (Do not unzip it!)
We are going to get a lot of assets and they take up room, by not unzipping the folders we leave everything neatly organized for later. Also, the default for dragging items out of zip folders is to copy instead of move, so we won’t lose anything accidentally and have to find and re-download our assets.
Now we’re ready to import our new assets into our project!
“You are going to have a LOT of accounts by the end of this course. I recommend having your browser manage your passwords, but also having a password manager as backup. Use a unique password for every account. This is basic security and as a developer you are expected to know this. You should also enable Two-Factor Authentication (2FA) for all accounts that allow it.”
Read More: Two-Factor Authentication
Importing Dungeon Assets
We are now ready to import some of the dungeon assets into our project. We are going to start with floors, walls, and stairs.
Open up File Manager.
Navigate to Projects -> Assets -> 3D.
Open KayKit_DungeonRemastered_1.1_(FREE/EXTRA/SOURCE).zip (Whichever version you have.)
Navigate to Assets -> gltf.
Open a second File Manager window.
Navigate to Projects -> dungeon-delvers.
Create a new folder called “assets”. (Note we are creating project folder names with all lowercase letter.)
Open the assets folder.
Create a folder called “models”.
Open the models folder.
Create a folder called “dungeon”.
Open the dungeon folder.
Create a folder called “floor_small”. (You should now have dungeon-delvers -> assets -> models -> dungeon -> floor_small.)
Drag and drop
dungeon_texture.png
from the gltf folder into the floor_small folder. (This is needed in every folder that will contain dungeon tiles.)Drag and drop all the floor models marked “small” and all foundation models into the floor_small folder. (Make sure you get the both the .bin and .gtlf files for every model.)
Create a “floor_large” folder in the dungeon folder.
Drag and drop
dungeon_texture.png
, all the floor models marked “large”, and all the tiles marked “big” into the floor_large folder.Create a “floor_xl” folder in the dungeon folder.
Drag and
drop dungeon_texture.png
, and the two floor models marked “extralarge” into the floor_xl folder.Create a “floor_grate” folder in the dungeon folder.
Drag and drop
dungeon_texture.png
, and the two models marked “floor_tile_grate” into the floor_grate folder.Create a “stairs” folder in the dungeon folder.
Drag and drop
dungeon_texture.png
, all the models marked “stairs” into the stairs folder.Create a “walls” folder in the dungeon folder.
Drag and drop
dungeon_texture.png
, and all the models marked “wall” into the walls folder.Switch back to Godot, and watch as all the models are imported.
While the assets have been imported by Godot, they still aren’t ready to be used. To be useable, they need to have collision shapes attached to them. Doing so will allow our characters to stand on them, and decorations and loot to sit on them (and not fall through into the abyss.) There are manual ways to do this, but we are going to do it the Lazy Way (TM).
There’s an old adage that, “All good developers are lazy.” What this means is that a good developer doesn’t like to do the same boring work over and over. We could import each floor tile, then update the collision data, and then do it again anytime we have to reimport them. Or, we could write a script, attach it to the files once, and let it run anytime we have to do an import (or reimport.) This is actually more work in the beginning because we have to write a script and test it, but our future selves will thank us. So, we are going to create a special script called a @tool script to do the work for us.
Click Script at the top middle of the Godot window (between 3D and Asset Lib) to switch to the Script Editor View.
Go to File -> New Script…
Change the name of the script to
import_floors.gd
on the Path line.Click the Folder icon to the right of the script name. (In the dialog that opens up, you will be in the root folder of your project.)
Right-click and create a new folder named “utilities”. (The folder will open when you make it.)
Make a folder called “import”.
Click the Open button in the dialog. (The dialog will close.)
Click the Create button. (The script will be created and opened in the editor.)
Copy and paste the script to the right into your file, overwriting any text in it.
Press Ctrl + S (Command + S on Mac) to save the script.
Open up assets -> 3d -> dungeon -> floor_small in the Filesystem tab. (Lower-left dock.)
Click the first floor tile file, hold down the Shift key and click the last one. (All of them should be highlighted.) (Don’t select the texture file.)
Click the Import tab. (Next to the Scene tab, upper-left dock.)
Scroll down and find the Import Script section.
Click the File icon. (To the right of the Path text box.) (An open dialog will appear.)
Select the
import_floors.gd
script you just made.Press the Open button.
Click the Reimport (*) button. (Bottom of the Import tab.)
Repeat steps 12 to 18 for the other folders containing the floors and stairs. (We will take care of the walls with another script in a bit.)
Select the Scene tab again.
Now that we have the floors imported, we still need to instantiate them. We will discuss that and get our walls imported below. First though, we have some Bonus XP for you! We’ll be taking a look at the script we just used.
“It’s tempting to import all the assets into your project that you think you might need, but this promotes project bloat. Soon, we are going to be checking our project into GitHub, and we do not want to upload stuff we don’t need. The more assets in our project, the longer our editor spends processing it when opening up, or starting a game for testing.”
“The preferred file format for importing 3D files when using Godot is .glTF. Godot 4 introduced support for .blend files, and while it works, there are still some bugs to work out as of 4.3 beta 2, so we will be sticking with .glTF files for this course.”
Read More: Available 3D Formats for Godot
“Naming conventions (what and how we name things) are very important for developers. Things like capitalization, or using underscores actually convey information to other people working on the project. In Godot, we use snake_case for folder names and filenames to avoid Windows compatibility issues on export. The GDScript style guide contains all the rules (see link below). We’ve chosen to use capital letters for folder names in our downloads to make it extra clear which files are in our project and which are not.”
Read More: Godot Project Organization
Read More: GDScript Style Guide
“There is a whole development philosophy called Don’t Repeat Yourself (DRY) that applies to writing code. It doesn’t apply to every situation, and in some cases we will break it to prototype things, and then implement it later. The DRY principle is at work in our import script. We are using something called recursion, where we create a function that calls itself. We explain how this works in the Bonus XP section below.”
“You may have noticed that after we explain something once or twice, (like how to create a new folder) we just move on and tell you to do that the next time. If you forget how to do something, you can always go back, but we are expecting you retain the knowledge you learn, and the best way to do that is to use the knowledge you have.”
Bonus XP: import_floors.gd
Explained
Our import_floors.gd
script is a script we will re-use and build on as we move forwards. If you want to be a developer, here’s where you start. If you need to, you can come back and review this section later. It is very common as a developer to refer back to documentation or Google answers to syntax. As your experience as a developer grows, you may know what you want to do, but not remember or know how to do it in whatever language you’re working in. This is why we are providing extensive links for further reading. Don’t feel you have to follow every link before moving on. They will be here when you’re ready for it.
@tool # Needed so it runs in editor.
This line starts with what’s known as an annotation. Annotations are keywords that when they precede a line, block of code, or a whole script, they tell Godot something special. They are in a way, very special, built-in functions. Anything that starts with the at (@) symbol is an annotation. Our annotation says that our script is a special tool script that is meant to be run while we are in the editor. It is used to create plugins, test code without playing the game, (or in our case) to run an import script. The default color for an annotation in the GDScript editor is orange.
The line ends with a comment. Comments are there for human beings. The computer ignores them, unless it is compiling documentation, and then there are rules about which comments it pays attention to and which it ignores. Anything that starts with the pound sign (#) (also called the number sign) is a comment. The default color for comments is grey.
Comments should be used when you want to remember why you did something. If you don’t know if you should comment something, do it. The comment on this line was left just for you so you know the annotation is needed for it to run in the editor (and not a run-time when the game is being played.) Normally, we wouldn’t leave a comment on this line, as it’s self-evident what that annotation does. As you get more experience in coding, you’ll learn comments aren’t usually necessary inside blocks of code. We will use them to help explain things sometimes.
extends EditorScenePostImport
This line starts with the extends keyword. This keyword is in red and it tells the editor what properties this script should inherit. (Inheritance is an Object-Oriented Programming (OOP) concept. We will talk about OOP in more detail in the next step.) Inheriting code means we can leverage what other people have written and don’t have to re-write everything from scratch.
The second part is an object type, also known as a built-in class. It is of type EditorScenePostImport. Object types are always in green. This one is specifically used for import scripts.
func _post_import(scene):
This line declares a function. A function always starts with the keyword “func”. It then has a name, an argument list, and a return value. The first line always ends in a colon ( : ). Our function is called _post_import
and it is actually a name that is defined in the EditorScenePostImport
class. It is known as a virtual function, and is named so that Godot knows what function to use, but it is left for us to define it.
The fact that our function name starts with an underscore ( _ ) is important. In GDScript underscore is an access modifier, and it means that a function or variable is private. This means that any method outside of this one cannot see this method. In GDScript, we default to everything being public. This results in less errors while learning to program.
Our function takes one argument, which we are calling “scene”. We could call it anything we want, we are declaring a variable here. You will notice we are not declaring a return type anywhere. This is because GDScript allows us to leave that information blank, and it will return whatever we ask it to.
You can think of the arguments to a function as its input, and the return value as its output. In this function, we are taking a scene as input (in this case the model we are importing), modifying it, and then returning that scene modified scene.
iterate(scene)
This is a function call. We use the name of a function and then pass it any arguments it needs. The iterate function actually hasn’t been defined yet. It’s the next thing we will define. Godot knows to search the whole file for function names, even if we haven’t told it about the function yet when we call it. In this case, we are passing it the scene variable, which is the same one from the previous line. We are passing it from one function to the next.
return scene
This line starts with a reserved word, “return”. Reserved words are pink in GDScript. A reserved word cannot be used as a variable name, a function name, or anything else. It is reserved by the language to mean one thing and one thing only. Return means we are done with this function, and we are returning control back to whatever function called us. If there is a value after the word “return”, we are returning that value, along with control. In this case, we are returning our scene, that we just modified by calling iterate(), to the importer.
func iterate(node):
We are declaring our iterate function here. Our scene is being passed in and we are renaming it to the variable “node”. We haven’t changed what’s underneath. Interestingly, every scene is a node, so this will works out fine for us. But using the name “scene” or “node” doesn’t mean it is either of those things. We could pass it “Hello world” and the function would take that variable, and then fail when it tried to perform an operation on it later. We will talk more about how you tell a variable what type it has to be in a later lesson.
if node != null:
This is an “if statement”. It is a conditional statement. We are comparing our “node” variable to something. “!=” is our condition. We are saying here that we are looking for our variable “node” to be not equal to the next thing. That next thing is the keyword null
. Null is a special programming language term that means “nothing”. This statement is a way to say, “If this node is a real object and not nothing…” The colon ( : ) indicates that comes next is what we should do if this condition is true.
if node is MeshInstance3D:
We are checking to see if this node is a MeshInstance3D node. Our scene, which we have passed, is not, so this is going to be skipped. However, if it was…
node.create_trimesh_collision()
This just calls another function on the node to create a collision mesh. This is the only line in this entire file that changes anything about our imported scene. This is a built-in function that creates a collision mesh of a type called “trimesh”. It programmatically measures our object, and exactly matches it with a CollisionShape3D. We will be talking a lot more about collisions in the future. For now, just know that it will keep other objects from falling through the floor.
This script is just so that we don’t have to worry about characters, monsters, items, decorations, or anything else from falling through the floor. That’s it. But since it doesn’t get called on the scene itself, read about the next line to see how this works.
for child in node.get_children():
This is a “for loop”, another type of conditional statement. It takes a group of things, and iterates (goes one-by-one) over each one and does the code following on each object in the collection. In this case we are iterating over every child node in our scene. The word “child” in this line is a declaration of the variable child that will be used to hold each node - it does not need to be named “child” for this “for loop” to work.
iterate(child)
This is the magic of the recursive function. A recursive function is one that simply calls itself inside itself, typically on its children. In this case, the children nodes in our scene. Each of them will be evaluated to see if it is a MeshInstance3D and if it is, collision data will be added. Either way, it will also see if any of those nodes have child nodes, and if so, it will call itself again on them - until we’ve hot the bottom of the tree. This recursion will become much more important when we start importing more complex models, like character models.
The iterate() function has no return statement. GDScript is a tab-based language, like python. What that means is it knows where it is in a function by how many tabs were used. When we reach the end of a function in GDScript, if there’s nothing else, there is an implicit return of control. This function doesn’t return anything, because it modifies the object directly that it was given as input. This ability to modify a variable or object that is passed is called passing by reference.
Bonus XP Gained!
Congrats! You’ve finished this deep dive into the import_floors.gd
script! We covered a LOT. Give yourself a pat on the back, and let’s go use those imported floors!
“Bonus XP sections are deeper dives into coding. Grab a cup of coffee and get ready to dig in when you see these sections.”
“Ctrl + K
This command can be used to comment out the line you are on, or all the lines selected in the editor. Pressing it again, uncomments all the lines. This is useful when tetsing code.”
“You can right-click on anything in the script editor that isn’t white or grey in color and select Lookup Symbol (at the bottom of the context menu) to be taken to documentation on the word.”
“If any of this is going too fast for you, check out the Godot tutorial, Creating Your First Script.”
Godot Tutorial: Creating Your First Script
“Passing by reference is such a confusing topic that we are not going to link additional resources about it. All that’s important to know is that when you pass a variable or object into a function in Godot, any changes you make to it are permanent and will be seen by the function that called your function.”
Instantiating Floor Assets
In development, instantiation is what happens when we turn a blueprint of something into an instance of that thing. We call those blueprints classes, and the instances objects. We are going to create a blueprint for each floor tile that we imported. In Godot, we call these blueprints Scenes, but underneath they are just classes. (We will see more of this later.)
Open FileSystem -> assets -> 3d -> dungeon -> floor_large.
Right-click on
floor_tile_large.gltf
.Select New Inherited Scene from the context menu.
Click 3D at the top middle of Godot. (Between the 2D and Script view buttons.) (You will switch to the 3D View.)
We have created our first Scene. In the top left of Godot, in the Scene Tab, we can see the Scene Tree. Every scene is composed of Nodes, which are shown in the node tree. Each node is itself an instantiated scene, an object that composes our scene. In Object-Oriented Programming (OOP), we call building a class from other classes composition. We will be using the concept of composition a lot. In Godot, that composition usually comes of complex nodes made up of simpler nodes.
If you take a look at our scene tree, you will see something like this:
If you hover your mouse over each node, you will see the details of each one, including their types. Our first node is of type Node3D, and named floor_tile_large
. (If yours is named floor_tile_large2
you can double-click on it, change its name, and press Enter.) This node was created as the Root Node of our scene automatically by Godot when we instantiated it.
Node3D is the base node or class that all 3D nodes inherit from. Inheritance is the idea that a class or object inherits all its properties from its parent. (Thus, we sometimes call them children.) It is everything that it inherited, but more. Every other node in our current scene tree inherits from Node3D. The other main base types are Node2D (which we will use for our UI), Node (which every node inherits from), and Object (which is the parent class for almost everything in Godot).
The second node is a child node of our first node, which is why it is indented underneath it and connected by a line. It has the exact same name, but is of type MeshInstane3D. This node was created by the data contained in the .gltf file. It contains all of the information about the shape of the item in 3D space (measured in meters) and its appearance. The gltf file also contained a reference to the dungeon-texture.png file we added in, and was used to create the skin (or texture) of the object so that we can see it in color. (For our current floor tile, it’s all one color, but in subsequent files you will see more details and colors.)
Our third node is a child of our MeshInstance3D node, and is of type StaticBody3D. This allows our object to interact with the physics engine. In import script on line 13 we said: node.create_trimesh_collision()
This told our script to create this StaticBody3D because we wanted it to be able to be used by the physics engine. It’s part of what will prevent characters and items from falling through the floor.
Our final node is a child again of the previous node, and is of type CollisionShape3D. This creates the actual collisions with the object, and is separate from the mesh instance so that it can be a different size. However, in this case we created a Trimesh collision shape. This is a collision shape calculated for us based on the MeshInstance3D above. (Later on we will see collision shapes both larger and smaller than our 3D items for various reasons.)
Now that we’ve taken a good look at our floor, it’s time to trash it. We’re actually going to instantiate our floors another way.
Click the x next to the tabe name [unsaved](*).
Press the Don’t Save button.
We’re going to switch over to importing walls now, before we actually instantiate everything.
“Instantiation is a concept that is a part of a popular way of programming called Object-Oriented Programming (OOP). If you go into a job interview as a developer, you will be expected to know the four main concepts of OOP: Encapsulation, Polymorphism, Inheritance, and Abstraction. You will also be expected to explain what they mean. As we go through this course, you will be getting hands-on experience with all of these concepts.”
Read More: Object-Oriented Programming
Read More: Instantiation Explained
Read More: Applying Object-Oriented Principles in Godot
“When we talk about composition, we will say something “has a”. When we talk about inheritance, we will say something “is a”. Typically when you use Godot, you can think of built-in nodes and scenes as using inheritance, and scenes and nodes that you create being made with composition. While neither of those will be 100% true, we will let you know when we are breaking those rules.”
Read More: What Is Inheritance
Read More: Inheritance in Godot 4
Read More: Composition vs. Inheritance
“When we introduce a new Godot term, you may have noticed we give you a link to the Godot documentation for that term. You can use these links to get more information. The Godot docs are an invaluable resource. If you haven’t yet, you might check out the 2D and 3D tutorials in the documentation. These quick projects will help familiarize you with some Godot-specific concepts that we are moving through quickly here.”
Read More: Godot Docs
Read More: Tutorial: Your First 2D Game
Read More: Tutorial: Your First 3D Game
Walls, Walls, Walls…
There’s one thing we want to talk about before we do the wall import script, and that’s Collision Layers and Masks. We will be going in-depth about layers and masks in Level 6. For now, remember that StaticBody3D node that was added to all our floors? It has a property group called Collision. If you go open up a floor scene and look in the Inspector tab, you’ll find it. (Remember the Filter Properties box can help you find it.)
You can think of the Mask as a transmitter and a Layer as a receiver. The mask transmits on any channels you set it to transmit, and the layers receive only on channels they are set to receive. We left floors on the default of 1, because we don’t want anything falling through the floors unless we tell it to. However, just in case, we want to give the walls a different layer. Maybe we want to implement a walk through walls power or spell later on. Who knows? But doing this now means not having to deal with it later. So how do we do that? With a script of course!
Open FileSystem Tab -> utilities -> import.
Right-click on our
import_floors.gd
script.Select Duplicate.
Name it “
import_walls.gd”.
Click Save.
Double-click
import_walls.gd
to open it.
We need to add some logic to our script from lines 12 to 14. Then we just update the comment on line 4. Take a look at the script to the right for the missing lines. In addition to changing the mask value for our MeshInstance3D node, we are changing the collision layer and mask for our StaticBody3D node.
Using the skills you’ve learned, attach your new import script to all of the walls and reimport them.
Level Up!
Congratulations! You’ve leveled up! You’re ready to move on! We’ll be learning how to use the assets to build a GridMap in Level 2! By the end of Level 2, you will have your first dungeon room!
Then in Level 3 we will go on a quest to learn about GitHub! We want to make sure all your hard work is safe, and we want to start preparing you to collaborate with others like a professional developer!
“Welcome to Level 2! I’m so proud of you!”