building the board, part #2 of godot-roguelike series
Sun 18 September 2016 2D Godot roguelike Unity3D , 0 comments
Sun 25 September 2016

disclaimer

Although I have quite a bit of programming experience in high level languages, I may not be qualified to teach you anything about game development as I am learning myself and I probably do not have a sense of how code should be written just yet. So please keep in mind that I am very new at this as well and don’t take it as the definite way to do it. It is only my way.

updates

2016-09-25 14:20: after talking with the Dean of Engineering at S4G School for Games and he pointed out the proper use of Dictionaries I modified the code to use a inner class to store the tiles from TileSet instead of a plain Dictionary with an inner Dictionary so be sure to grab the updated project files! if you went through the tutorial before 2016-09-25 14:20
2016-09-24 19:23: added accordion menus for the extra information sections () so they don’t break the flow of the tutorial

overview

with Godot v2.1.stable.officiali-godot
project files

Before you continue make sure you have the project files from the first part as you will need them here if you want to follow along!

In the previous part of the tutorial series we prepared the assets and included them in our Godot project with the proper flags (specifically the filter flag) so that the pixel art won’t get blurry when we stretch the game window.

In this part we continue by creating the board of our mini-game. Now, there are two main ways we can go further here:

  1. we use the built-in TileMap node where the process isn’t exactly straightforward. The TileMap node uses a TileSet resource which needs to be created by converting (Scene > Convert To.. > TileSet..) a scene made specifically for this purpose. The TileMap functionality is built for optimizing the creation of levels using repetitive tiles like this. But unfortunately, with this performance increase, we also loose the powerful node functionality because the tiles aren’t real nodes any longer since they are converted to a resource usable by TileMap. This way they loose some of their flexibility. As an example, while the tiles can have collision shape information attached to them (so they can physically interact with other objects in the game), as it is in the moment of writing this, TileMap isn’t capable of using the Area2D node, which is a special type of node that also registers collisions but doesn’t physically interact with the other objects (doesn’t stop them or change their velocity in any way). Why is this important? Well, keep on reading. By the way, if the Godot TileMap docs aren’t to your liking, I know two other tutorials that might help with this one:

  2. the other option is to use create and use nodes directly without making use of the TileMap node. This is the way we are going to be building this mini-game because it isn’t as complicated of a game as to need physics collisions and since we can use the powerful node system to the fullest we will make a hierarchy that is going to help us simplify the generation of the grid and placement of elements on it (obstacles, items, characters etc.). Games that are built on girds and especially turn-based games don’t usually require any physics. One way of using physics here would be for building the tiles that represent the obstacles, thus stopping the characters from moving in certain directions without us having to manually check. But this isn’t always the best or simplest way. It might simplify things for certain functionality, but it can complicate things needlessly in other ways. For example we need the characters to be at specific locations (integer values) in the our gird and this would be very hard to achieve if not impossible (due to sub-pixel errors, rounding errors, etc.). In a game like this it is just much more easier to calculate manually the movement as you shall see

tilemap vs full nodes Fig. 01. TileMap mode (on left) as opposed to full featured scene tree (on the right)

So to recap, the way forward for us is to build a scene which is going to have all of our tiles laid out in a nice hierarchy which is going to help us on one hand to be very tidy and on the other with the selection and placement of tiles on the grid. One other downside (over using TileMap) is the fact that we’ll need to manually calculate locations for the tiles with a bit of math. The TileMap node on the other hand does this for us and we just need to worry about integer x, y grid locations. But a little bit of math didn’t hurt anyone!

preparing the tile set scene

This is going to be the most cumbersome part of project because it’s the set-up phase, but this being a mini-game we can bare it no problem!

the static tiles (Floors, Walls, Obstacles, Items & Exit) - the Sprite node

The first thing we need to do is to create a new scene (Scene > New Scene) and a new node of type Node (by pressing the icon from the scene panel, by right clicking on the scene panel area - Fig. 02 - or by hitting CTRL + A and selecting it from the list). Rename the node to TileSet. Before we move on save the scene in the Scenes folder and use the default name of TileSet.tscn.

the Node node

This is the base node type from which every other node inherits in Godot, inheritance being one of the building blocks of OOP that allows objects (classes to be more specific) to share some base functionality, removing the need for code duplication, something that we will directly use in our implementation as well, in GDScript. It is beyond the scope of this tutorial to explain basic OOP and programming patterns (such as inheritance, classes, instances etc.), but a good place to learn about it is the C++ language tutorial. Yes, I am in fact encouraging you to go read it at some point (all of it!) and understand it even if it isn’t directly related to what we are doing here! Any serious game developer shoud at least have an idea of how C++ and OOP work!

When adding a node to the scene we are presented with a list of nodes that have a parent-child relationship. The relationship from this list directly represents the OOP inheritance tree. The Node node doesn’t have any purpose other than constructing hierarchies in our scenes. They can’t be rendered and they can’t even be moved (as they don’t have any dimension or position information), but that doesn’t mean that they are useless. Just something to keep in mind.

scene add node Fig. 02. Ways to add a node to the Scene using the mouse

scene container hierarchy Fig. 03. Scene tree hierarchy for container nodes

Let’s create another node of type Node and name it Floors. We want to build our hierarchy, so next, before creating the new node be sure to select the TileSet node by clicking on it in the Scene panel (if you don’t then the next node will be a child of Floors and we don’t want that) then create another node of type Node and rename this one Walls. Do this three more times for Obstacles, Items & Enemies. Following these steps you should have the hierarchy from Fig. 03.

notations

I will be using this type of notation: Node1/Node2/Node3 to denote the path to a specific node in the scene tree hierarchy. This is consistent with Godot‘s style of retrieving a node in GDScript when using the get_node function. For example, to point to the selected sprite from Fig. 04 I will write TileSet/Floors/8 and you will immediately know where to look at with the precision of a computer programming language!

scene tree after adding floor sprites Fig. 04. Scene tree after adding the floor Sprites

Now the fun part comes: we are going to prepare the actual tiles (images). First make sure that the Floors node is selected in the scene panel so that the new nodes that we add to the scene become children of this node. Now add a new node of type Sprite (search for it using the search text field, it’s much faster) and rename it to 1. After this go to the Texture property in the Inspector panel and click on where it says <null>. A menu will appear, from the menu select Load, navigate to res://Assets/Sprite/TileSet and double-click on floor01.png. You should see the image appearing in the 2D editor.

scene tree after adding floor sprites Fig. 05. Scene tree after adding all of the Sprites except the player and enemies nodes

To speed things up, instead of adding the nodes one-by-one like we did before, make sure you have the node TileSet/Floors/1 selected and hit CTRL + D. This will duplicate the node and it will increase the number from 1 to 2. Make sure TileSet/Floors/2 is selected and from the Inspector panel, press on the image showing in the Texture property and select Load. Double-click on floor02.png. Don’t worry if the nodes are in the same place visually, this won’t make any difference later when we build the grid. Repeat this process (of duplicating the last Sprite node and selecting the new Texture) six more times until you use all of the floor textures (so up to floor08.png).

At the end of this you should have a hierarchy like in Fig. 04.. Now do the exactly same thing for Walls, Obstacles (for Sprites under this node use obstacle01.png, obstacle02.png and so on, we’ll use *_dmg.png later), Items (food.png & soda.png and give them the names Food and respectively Soda) and leave out Enemies for now, we’ll come back to it a bit later. Finally select the TileSet node and add a new Sprite and name it Exit, give it the exit.png texture. I won’t go over it again since you should be an expert at it by now! Once you finish you should have the hierarchy from Fig. 05.

Don’t forget to regularly save your work, did you save yet? If not it’s a good moment to hit that CTRL + S!

Before we move on to the coding bit, there’s one last thing we need to take care of: select all the Sprites under Floors (by clicking on TileSet/Floors/1 and SHIFT-clicking on TileSet/Floors/8). In the Inspector panel you’ll notice that at the very top it says MultiNodeEdit. This means that we are able to modify properties that are shared by all nodes with a click of a button! Under the Node2D section in the Inspector panel search for the Z property and set it to -1 for all of the selected Sprites. This will ensure that all of the other Sprites will be drawn on top of the floor tiles. We wouldn’t want for the enemies for example to not be visible because they have been drawn under the floors for whatever reason.

a faster way to add the Sprites & set their Texture property

We haven’t used the FileSystem panel at all up until this point, but Godot has an interface with drag and drop support. Can you think of a faster way to add the Sprite nodes to the scene and set their Texture property in the Inspector panel?

solution
In the FileSystem panel navigate to where we have the relevant *.png files: res://Assets/Sprites/TileSet. Next, after we duplicate the Sprite node (CTRL + D), click & drag the appropriate image file from the FileSystem panel to the Texture property in the Inspector panel. It’s much faster! Try to experiment as much as possible! drag & drop for fast Texture replacement
the Sprite node

The Sprite node, as you can imagine, is used for loading images from our hard drive. It’s Texture property, which we will go over in the following section in some depth, is used to indicate which image to load for us. By only changing this property, the default set-up is to show the image in its entirety. But this node has some other useful properties that simplifies the work flow when dealing with sprite sheets (which we won’t be doing here, but it’s still good to know about it).

In the Inspector panel, there are three more interesting options: Vframes, Hframes and Frame. Can you guess what they are for?

Time to experiment! Here, take this sprite sheet (right-click and Save link as...) that we split in the previous tutorial using Krita (if you followed along with the optional section).

Now add a Sprite node to the scene just for testing purposes (maybe make a new scene first so this doesn’t interfere with our work so far), and for the Texture property select the sprite sheet you just downloaded (you didn’t forget to copy the sprite sheet to the projects folder first did you?!).

Looking at the sprite sheet we can see it is made out of 7 vertical frames and 8 horizontal frames. Maybe Vframes and Hframes properties have something to do with this?! Set the Vframes property to 7 and the Hframes to 8 respectively. See what happened? Now the Sprite shows only a portion of the Texture, only one frame from the sprite sheet. But how do we cycle through the frames? Remember the Frame property? Let’s change that and see what happens! We see that by changing this property we can select a different part of the sprite sheet to show in our game. Now, with the combination of the AnimationPlayer it is possible to use such sprite sheets to create animations, we will however use a simpler node for our mini-game, in the following section.

At this point we are done with this test scene/node so we can delete it.

bug

As I was doing the steps from this part, I noticed that the number from the Frame property doesn’t update in the Inspector panel upon changing it. It remained 0 even though the Sprite image would update with the correct frame.

This is a bug! But don’t worry, it is quite normal for such large projects as Godot to have minor bugs like these. Whenever we encounter a problem like this we go and report it at the Godot github issues tracker: here is my report on this issue. Only by being a pro-active community can we improve!

the rest of the tiles (Enemies & Player) - the AnimatedSprite node

Alright, it’s time to add in the Enemies and the Player sprites. Unlike the other elements in the game, these sprites need to be animated. There are two options:

  1. use a Sprite node in conjunction with an AnimationPlayer and add in key frames for either the Texture property or the Frame property (if working with a sprite sheet)
  2. use an AnimatedSprite node. The downside with AnimatedSprite is that it isn’t able to use a sprite sheet (as the Sprite node), that’s why we had to split the sprite sheet in part one of this series. We will be doing this one because it’s simpler, there’s only one node to work with and because it calculates the animation speed in FPS (unlike the AnimationPlayer which works directly in seconds) which I think is more natural when working with this type of animations

Let’s first populate the Enemies node. Make sure the Enemies node is selected and then press CTRL + A and search for AnimatedSprite and double click on it. Name it Enemy1. Next, go to the Insepctor panel and from the drop-down menu of the Frames property select New SpriteFrames. Then click on the > icon near it. Now Godot opens up a new panel at the bottom of the screen. This is used for making our animations from provided frames. Rename the default animation to chop and add a new one and call it idle (refer to Fig. 06) for reference.

Next, navigate in the FileSystem panel to res://Assets/Sprites/Enemy01Chop and make sure the chop animation is selected. Then select both 01.png and 02.png in the FileSystem and drag and drop them in the open space where it says Animation Frames in the bottom panel. Do the same for the idle animation with all of the sprites from res://Assets/Sprites/Enemey01Idle. Make sure that Loop is set to OFF for chop and ON for idle. Take a look at Fig. 06 if you’re stuck.

AnimateSprite set-up Fig. 06. AnimatedSprite editor bottom panel. Make sure that Loop is set to OFF for chop and ON for idle

Before moving on, make sure that the Animation property is set to idle and that Playing is ticked in the Inspector panel.

To get the view for the properties of the AnimatedSprite node back into the Inspector panel (as opposed to the SpriteFrames view) just click on the TileSet/Enemies/Enemy1 node in the Scene panel.

Now that we have our first enemy nicely set-up it’s time create the second one. But instead of repeating all of the above steps, just duplicate the TileSet/Enemies/Enemy1 node (select it in the Scene panel and hit CTRL + D). We need to be careful now because if we go and modify the SpriteFrames resource (from the Frames property in the Inspector panel) by pressing on the > symbol, then these changes will be reflected in Enemy1 as well! That’s because both Enemy1 and Enemy2 make use of the same resource! To overcome this, make sure TileSet/Enemies/Enemy2 is selected and press on the property of Framse in the Insepctor panel and select Make Unique from the drop down menu. Now we can modify it (press the > symbol). Delete all the frames for both chop and idle (using the X symbol from the Animations bottom panel - see Fig. 06) and re-add them using the appropriate ones (res://Assets/Sprites/Enemy02Chop and res://Assets/Sprites/Enemy02Idle).

If you did everything correctly and switch off the visibility of the TileSet/Exit node (by pressing the appropriate eye icon in the Scene panel) you should see the animations playing for Enemy2 which is on top of Enemy1.

Finally time has come for our player to be added to the scene! Make sure TileSet is selected in the Scene panel and add a new node of type AnimatedSprite and name it Player. Do the same as you did for Enemy1 for the Frames property in the Inspector panel, I won’t go over the steps again, they are identical almost. The only difference is that the Player needs one more animation. Add it from the Animations bottom panel and call it hit. Use:

  • res://Assets/Sprites/PlayerChop for the chop animation and make sure Loop is OFF
  • res://Assets/Sprites/PlayerHit for the hit animation and make sure Loop is OFF
  • res://Assets/Sprites/PlayerIdle for the idle animation and make sure Loop is ON

Make sure that Animation is set to idle and that Playing is ticked in the Inspector panel for this node as well. At this point you should see the Player animation playing in the main 2D window.

i-godot
Godot fun facts

Did you know that the Godot editor is itself a game made with Godot? This is the reason why Godot has an incredible amount of Control nodes (used for GUI creation) compared to other game engines.

pre-implementation details

coordinate system Fig. 07. Coordinate system in Godot (and most game engines).

Now that we have the TileSet scene with all of the artwork done let’s take a look at how to deal with a grid system. For this we first need to note that the origin of the 2D coordinate system in Godot is located at the top left of the viewport and the positive y axis points downward (Fig. 07). This is something common in computer graphics and it’s just something you need to be aware of when doing anything involving math and coordinates.

The other important bit that we need to figure out is how we want to handle the placement of the tiles. Do we want the pivot point to be in the middle of the sprite? Or do we want it to be placed at one of the corners instead? These are the (logical) options in our case. But depending on how we choose, the difficulty also differs. In the case of this mini-game it isn’t really as important, but it is something that is usually best dealt with from the start. Fig. 08 shows the two options, in the left side, the center of the sprite would coincide with the origin of the coordinate system so to have the sprite placed correctly it needs to be moved by an offset which is half the length, just like in Fig. 08. This is the case we are in right now. On the right side, the sprite is moved so that the pivot point is at the top left corner of the sprite and it is aligned to the origin of the coordinate system. In this case we need fewer information in order to correctly place the tiles on a grid (only length as opposed to length and offset). By the way, the tiles don’t have to be necessarily square like in the figure or the assets.

tile placement options Fig. 08. Options for tile placement and required information for correct tile placement in the viewport.

board info Fig. 09. Board concept. The outer perimeter is where the impassable walls will be placed, the inner perimeter is where only the player and exit tiles will be placed, the player in lower left corner and the exit in the top right. The inner grid is where all of the obstacles, items and enemies will appear. The strategy for the board tile unit coordinates is also given

So before we move on let’s make our lives a little bit easier and modify the tiles so they are aligned like in the right side of Fig. 08. Select all of the Sprite and AnimatedSprite nodes (with a combination of SHIFT + click and CTRL + click, just as you would expect) and in the Inspector panel uncheck the Centered property. You’ll see the sprites immediately jump to the right and down a bit, and be placed exactly as the tile from the right side of Fig. 08.

The strategy for our grid coordinate system is shown in Fig. 09 (the player and the exit tile positions are also shown). We will have the top left inner grid tile be placed at (0, 0) and have everything placed relative to this inner grid. This is just one way of doing it (and it’s the same way the Unity3D tutorial handles it). Hopefully it will become clear how this works once we get to the code.

Next we need to think about our board (Fig. 09). The board will have an outer perimeter with impassable tiles: the walls. It will also have an inner perimeter on which we will not put any obstacles, items or enemies (an easy way to make sure the player can complete any level) and finally we’ll have the inner grid part where we will put all of the obstacles, items and enemies.

OK, let’s get to the coding already!

using our new tile set

with help from Dazz Dazz on itch

I want to start off by saying that this isn’t going to be the best algorithm implementation, but rather an inefficient way of doing it. The reason being is that I will give you the assignment of making it better at the end of the tutorial! So go over this part, try to understand it and when you’re ready go to the assignment and give it a shot! And remember, if you don’t know the meaning of any Godot function, just head over to the documentation page and search it there! I won’t be able to go over every little detail.

.

Wid. 01. Interactive widget exemplifying the process of board creation that we will use in this part of the tutorial. Courtesy of Dazz.

We are going to first use our Game scene as a test bed for the creation algorithm for the board. Later we’ll be moving the code to the proper place. Wid. 01 is a widget created with phaser.js courtesy of Dazz, who approached me on the unofficial Godot discord server and who wanted to help me out with the this tutorial series. I’m very grateful for his help! This is how awesome the Godot community is! At the moment (of this writing) there’s a small nucleus of people that are always present on IRC (FreeNode #godotengine & #godotengine-devel), on the unofficial Godot discord server and on the Godot Facebook group. Join us to build a stronger community! Godot is awesome and with the release of v3 on the horizon which is going to include proper 3D support and rewritten shader language it will be a serious player in the big league! Now is the perfect time to come on over and get used to it!

Moving on, the widget exemplifies the process we will be implementing in Godot for creating the board game table. Give it a try! Actually give it multiple tries and see if you can get the question mark to appear on top of already placed tiles! You might need a few tries for this.

Start by making a folder called Scripts in the root directory of the project. Now attach a script to our Game scene. So open up the Game scene (res://Scenes/Game.tscn) if it’s not already opened and right click on the Game node and click on Add Script. When presented with the prompt write res://Scripts/Game.gd for Path, leave everything unchanged and click Create. The main window should immediately jump to the script editor and a new (paper) symbol should have appeared in the Secene panel on the right of the Game node.

Godot scripts

Every script that starts with the extends keyword defines a class in Godot. This tells the game engine that we want to inherit from the given internal class. This way we will have access to whatever functions Godot exposes for us such as the _ready function which is called after the node and all its children have entered the scene tree. Also, it is an error to attach a script to a node that doesn’t extend the proper class.

white space

Just like in Python, Godot relies heavily on white space and consistency (compared to most of the other programming languages) instead of using curly braces. The Godot editor works with TABs, while the code listed here has SPACEs. Using TABs is the recommended way in Godot. Do not mix the two of them! (unless you’re splitting the line with \ because it’s too long). You’ll see examples of this in the listed code. Also you can have a look at res://Scripts/Game.gd script from the project files to see what I mean.

Delete everything from the script file except the top most extends Node part. We are going to start from scratch. First we define some necessary variables:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
extends Node

export var inner_grid_size = Vector2(8, 8)
export var perim_thickness = Vector2(1, 1)
export var count_obstacles = Vector2(2, 7)
export var count_items = Vector2(3, 6)
export var count_enemies = Vector2(2, 2)
var __tile_collection
var __grid

class TileCollection:
    var item = {}
    var tile_size
Godot variables and the exports keyword

Since every script attached to a node in Godot is a class, all the variable declarations that come after the extends part are member variables of this class. The exports keyword is only used when declaring these member variables and what it does is to expose those variables in the Inspector panel for us to change through the interface directly! This is especially useful if we want to create plugins or tools that other people will use since we don’t want them to be changing the code directly. Read more about it in the docs.

Let’s go over the variables and the inner class:

  • inner_grid_size: stores the size of the inner grid as a Vector2 so it can have different dimensions on x and y
  • perim_thickness: thickness of the inner perimeter. It’s stored as a Vector2 because we’ll make it so that it can be different on x and y
  • count_obstacles: the minimum and maximum allowed number of obstacles, stored as a Vector2
  • count_items & count_enemies: same as count_obstacles but for items and enemies
  • we’ll skip __tile_collection and __grid for now because they’ll be adressed later
  • TileCollection: this is an inner class. Its declaration is just like in Python. It can optionally extend another class, just like the top script/class (class A extends B:). And the variables that follow are member variables of this inner class. All we need is a Dictionary to hold our tile nodes (either the collections (TileSet/Walls, TileSet/Floors, etc.) or the Sprite/AnimatedSprite directly: TileSet/Exit and TileSet/Player). This class will be used later to populate the game board. There’s also an extra variable, tile_size which will store the size of the texture image for the tiles (as you can imagine). It is a bulk property (that is it should be the same for all tiles) and it will be used to place the tiles in the appropriate places as explained in the pre-implementation details section. Why don’t we just use some variables like we did for the other stuff?! Well it makes sense to keep things organized. What if we had used the offset method instead to place the tiles on the grid? We would have needed another bulk property of the tiles, it makes sense to keepa all of this information organized neatly in a class like this. And you get to see that classes aren’t that scary, use them whenever you feel the need!
What’s up with those names?!?!

GDScript, unlike C#, C++ and some other programming languages doesn’t have the notion of private and public class members. Now I have a significant Python background and am very influenced by their programming guidelines. In Python, member variables and methods starting with an underscore are considered to be private, even though they are not. It’s just a convention and as long as everyone follows we’re all happy. Methods exposed by the API start and end with two underscores, like so: __init__ in Python so there’s no conflict between these and the variables that we create and start with an underscore. But in Godot, internal functions start with only one underscore and they have not trailing underscores. This is why I chose to write the “private” member variables and methods with double underscores at the beginning. It is my convention in GDScripts case which you are free to use or not in your own projects. You will also notice that I prefer to use long meaningful names for my variables over small and cryptic ones. I prefer readability over conciseness, that’s why I also use the self keyword to access member variables and methods even though it’s optional.

Moving on we create a function that (randomly) fetches for us a tile from the TileSet which we will use as a source for placing tiles on the board:

1
2
3
4
5
6
7
8
9
func fetch_tiles(tile_set_filepath, size_node_name):
    self.__tile_collection = TileCollection.new()
    var tile_set = load(tile_set_filepath).instance()
    for node in tile_set.get_children():
        self.__tile_collection.item[node.get_name().to_lower()] = node
    self.__tile_collection.tile_size = (tile_set \
                                        .get_node(size_node_name) \
                                        .get_texture() \
                                        .get_size())

We first initialize the self.__tile_collection member variable with an object created from our TileCollection inner class. Then we load the TileSet located at the location given by tile_set_filepath and instantiate it so we can later duplicate the nodes (tiles) as necessary. This is Godot‘s way of programatically accessing saved scenes from the file system. You can read more about it in the docs. Next we iterate over the children from our TileSet (remember the hierarchy?): Floors, Walls, Obstacles, Items, Enemies, Exit and Player and store them for later use in the TileCollection.item inner Dictionary. Each key of the Dictionary will have the lowercase name of the Node (for example TileSet/Walls will be stored at key walls). This makes it very convenient for later use and iteration should we choose to do so. Finally we get the tile size (which should be the same for all tiles) based on a child tile which path is given by the parameter size_node_name. This way we don’t have to hard code the size inside our script, which means that if we want to play with a different set of tiles we can just swap them and even if they have different dimensions things will still work out! Pretty neat isn’t it?

Let’s now go over the __rand_tile function which we will use to grab a tile at random from one of our groups of tiles (for example TileSet/Floors):

1
2
3
4
func __rand_tile(tile_set):
    var tiles = tile_set.get_children()
    var r = int(rand_range(0, tiles.size()))
    return tiles[r]

This is fairly simple. We get the children of the given node as an Array (don’t be fulled by the tile_set name, I consider it to be a subset of the full TileSet node so it might as well be named tile_set) and generate a random number between 0 and the number of children contained in that node. We need to convert this number into an integer so that we can retrieve the child at that index.

fetch_tiles and __rand_tile

The fetch_tiles and __rand_tile functions are more like utility functions and they are best located in an external script or library so that we can reuse them if we plan to make this sort of games that rely on tiles again. We’ll come back to this at later part of the tutorial series. It’s always important to think about code organization and reusability.

Let’s look at __add_tile next:

1
2
3
4
func __add_tile(tile, xy):
    var tile_dup = tile.duplicate()
    tile_dup.set_pos((self.perim_thickness + Vector2(1, 1) + xy) * self.__tile_collection.tile_size)
    self.add_child(tile_dup)

We duplicate the given sprite first and next convert from the grid integer position values to a position in the game world in pixels. Because we chose to use the top-left corner of the inner grid as a starting point for the grid coordinate system (the 0, 0 origin), we have to first add the thickness of the inner perimeter but also Vector2(1, 1) - the outer perimeter where we have the impassable walls. Vector2(1, 1) because this outer perimeter will always have thickness of 1 (in tile units). Then this value is multiplied by the size of our tile (remember we saved this value in the fetch_tiles method).

vectors in Godot

You will have noticed by now that we used mathematical operations on the Vector2 class directly, instead of accessing the individual coordinates. That’s because Godot is smart enough to add together the appropriate coordinates, for example, Vector2(2, 3) + Vector2(1, 3) will result in Vector2(3, 6) as you might expect. But what about the multiplication (*) operation? In mathematics there are two kinds of multiplication between vectors, the scalar and cross products. The multiplication operation from Godot on the other hand, just multiplies coordinates individually, for example, Vector2(2, 3) * Vector2(1, 3) results in Vector2(2, 9), just something to keep in mind. If you want to calculate scalar product, the Vector2 class has a dot member function. 2D vectors don’t have a cross product defined.

Let’s move on to the next function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
func __add_base_tiles():
    var bounds = {
        xmin = -self.perim_thickness.x - 1, xmax = self.inner_grid_size.x + self.perim_thickness.x,
        ymin = -self.perim_thickness.y - 1, ymax = self.inner_grid_size.y + self.perim_thickness.y
    }
    for x in range(bounds.xmin, bounds.xmax + 1):
        for y in range(bounds.ymin, bounds.ymax + 1):
            var tile
            if (x == bounds.xmin or x == bounds.xmax or y == bounds.ymin or y == bounds.ymax):
                tile = self.__rand_tile(self.__tile_collection.item.walls)
            else:
                tile = self.__rand_tile(self.__tile_collection.item.floors)
            self.__add_tile(tile, Vector2(x, y))

bounds is a helper Dictionary variable that holds the extremities of the board (outer perimeter) in units of tiles relative to the inner grid used in the next iteration loops, because we need this function to go over all of the board, not just the inner grid. So then the iteration starts and inside we check if we are on the outer perimeter in the if statement, if so then we choose at random one of the wall tiles, otherwise we choose one of the floors and finally add it to the scene using the __add_tile method.

the range function

The range function simply returns an Array and it has three call variations:

  1. one argument (call it n): it then returns an Array of numbers going from 0 to n - 1 with a step of 1
  2. two arguments (call them from and to): it returns an array starting at from until to - 1, with a step of 1
  3. three arguments (call them from, to and step): it returns an array starting at from until to - 1, with the given step

This is how Godot handles for loops, it’s symilar to Python, it iterates over Arrays instead of that complicated construction from C, C++ and the like. It is very natural and once we get iterators in Godot it will be awesome!

Next we look at another similar function which has the purpose of adding tiles to the inner grid:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func __add_other_tiles(tile_set, count=Vector2(1, 1)):
    var n = int(rand_range(count.x, count.y))
    while n > 0:
        var xy = Vector2(randi() % int(self.inner_grid_size.x), \
                         randi() % int(self.inner_grid_size.y))
        if not xy in self.__grid:
            var tile = self.__rand_tile(tile_set)
            self.__add_tile(tile, xy)
            self.__grid.push_back(xy)
            n -= 1

First we get a random number between count.x and count.y, which is a parameter of the function. This will determine the number of tiles to be added to the inner grid. Next we enter a while loop that will terminate when we won’t have any more tiles to add. In the loop, we generate a random location based on the dimensions of the inner grid and we check if there is a tile already placed at this location using the self.__grid variable. If the test passes then we pick a random tile and add it to the scene. We also add the location to the self.__grid variable so we can track the places where tiles have been added. Finally we decrease n by 1 since we have one less tile to add.

generating random numbers in Godot
  • float rand_range(float from, float to): is a function that generates a random floating point number in the open interval (from, to)
  • float randf(): return a random floating number in the open interval (0, 1)
  • float randi(): return a random integer number in the closed interval [0, MAX] where MAX is the maximum value for 32-bit integers, i.e. 231 - 1 = 2147483647.
  • Nil randomize(): there’s one more function to know about and this is the randomize function. If you try using the 3 above functions you might be surprised as to why they give the exact same numbers every time you run a game. How can that be? Aren’t they supposed to be random? Well yes and no. There are no true random when dealing with computers, they’re instead pseudo-random numbers, meaning that there is a function that first sets a seed for the random number generator and based on that a sequence of random numbers is created. Each time we call randomize this seed is given a new value (internally based on the computer’s clock so it has very little chance of repeating). Also, it is a good thing that we are using pseudo-random numbers instead of real random numbers. Why? Say for example you want to debug a part of your AI that uses random numbers, it would be almost impossible should we not have pseudo-random numbers and the ability to repeat sequences as we’d like.

The final part of the algorithm is to put all of these functions together and generate the board:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func make_board():
    randomize()

    self.__grid = []

    self.__add_base_tiles()
    self.__add_other_tiles(self.__tile_collection.item.obstacles, count_obstacles)
    self.__add_other_tiles(self.__tile_collection.item.items, count_items)
    self.__add_other_tiles(self.__tile_collection.item.enemies, count_enemies)

    self.__add_tile(self.__tile_collection.item.exit, Vector2(self.inner_grid_size.x, -1))
    self.__add_tile(self.__tile_collection.item.player, Vector2(-1, self.inner_grid_size.y))

We start off with randomize by generating a random seed. Next we reset self.__grid and use the functions we defined earlier, which at this point I think are self-explanatory. The last two lines of code manually place the TileSet/Exit and TileSet/Player tiles at predefined places as in Fig. 09.

OK, so we have defined our functions to create the board, but how do we tell Godot to actually use them? Remember what I said about the _ready function? If we overwrite this function, Godot will run whatever code we have inside of it after the node and all of its children entered the scene tree. So we are going to do just that:

1
2
3
4
func _ready():
    self.fetch_tiles('res://Scenes/TileSet.tscn', 'Exit')
    self.make_board()
    self.__set_screen()

That simple! OK, now what’s up with this __set_screen function? It’s the last piece of the puzzle, let’s take a look at it:

1
2
3
4
5
6
func __set_screen():
    var board_size = (2 * (self.perim_thickness + Vector2(1, 1)) \
                     + self.inner_grid_size) * self.__tile_collection.tile_size
    self.get_tree().set_screen_stretch(SceneTree.STRETCH_MODE_2D, \
                                       SceneTree.STRETCH_ASPECT_KEEP, board_size)
    OS.set_window_size(2 * board_size)

First we calculate the board size (all of it: the outer perimeter thickness (Vector2(1, 1)), the inner perimeter thickness + the inner grid size). Next we use the set_screen_stretch method of the SceneTree object to tell Godot that we want the viewport stretch mode to be set to 2D stretch, but keep the aspect ratio (so that when we resize the window it won’t get all squished or stretched). Finally we set the window size to be 2 times the board size, to be nice and big. Comment out the set_window_size function and see what happens, experiment!

viewports and resolution

It is beyond the scope of this tutorial to go over the viewport stretch modes, there’s a good tutorial over at gamesfomscratch.com on this subject if you’re interested to know more.

assignment 01 - board building efficiency

The algorithm used here isn’t as efficient as it can be. Can you think of a better way of doing it? Hints:

  1. initialize self.__grid with available locations instead of initializing it to an empty array and populating it every loop step with locations of placed tiles
  2. the only other modification that needs to be made is in the __add_other_tiles function. Instead of adding the locations to self.__grid think of a way on how to efficiently use the array of available locations

And please try not to cheat, that is, don’t go for example to the Unity3D tutorial to see how it’s done. This is an exercies for you to get better, it won’t server you if you’re just going to copy code without thinking!

I will give out the solution in the next part of the tutorial. Meanwhile try and think of how you’d go about implementing it. What does “initialize self.__grid” with available locations mean? Where are we allowed to put the obstacles, items and enemies based on our rules? Ask questions like these and I’m sure you’ll find a solution! Good luck and have fun with it!

Godot scenes

If you come from another game engine or have read how they work, forget about everything because Godot is awesome! But because of it, more specifically, because of how Godot structures the scene tree many are left a big confused, especially the ones coming from other game engines. In Godot a scene is nothing more than a collection of nodes, for this reason I actually don’t like the terminology the devs have come up with, but that’s how it is with these things.

I find it a lot more useful to think of scenes as just regular nodes. As you know, Godot forces you to have only one root node in your scene files, every other node is a child node. That’s why we usually start a scene with a very basic or generic node, like Node or Node2D (if we need position information) or Spatial for 3D. That being said, becuase a scene is nothing more than a hierarchy of nodes, it can be anything we want it to be: a player entity, a whole level, a tool for creating levels, a TileSet, their use are virtually unlimited because of this simple tree structure Godot forces upon us. So if you have a gard time grasping the meaning of a scene, stop, take a moment and think of it instead as a collection of nodes which have a logical ierarchy. Let me go one step further and drive this idea home. Take a look at Fig. 10 below, this is what Godot created at runtime after executing the Game scene we created so far. You can see that in essence Godot doesn’t really have any notion of a scene, it only cares about nodes, it’s just terminology, it doesn’t care if the Sprite nodes we made were in the TileSet scene, it just cares about the fact that they are nodes, and nodes can be inserted anywhere! Don’t get hung up on it too much!.

Godot secene tree at runtime Fig. 10. Remote inspector showing the scene tree at run time; located in the debug panel at the bottom.

closing remarks

This has been a fairly lengthy part, but hopefully you have understood how Godot works and how to use the node hierarchy to your advantage in grouping nodes together for easy access when generating random levels for example. This is just one use case, I’m sure you can think of 10 other easily. It is up to each of us to come up with the rules for level generation (outer impassable walls, an inner perimeter that never has obstacles, items or enemies on it, etc.).

In this part I really went through almost all of the minute details, from the interface buttons and usage to the internals of Godot. Starting from the next part I’ll be a lot more concise and only explain the necessary bits. I will assume that by then you get the gist of it and how all of it is supposed to come along together.

As always, let me know what you think about it in the comments section. I will greatly appreciate your contribution! Thanks and see you in the next part when we will be adding the player controller and “collision detection”!

project files

Get the zipped finished project for this part which has annotations in the res://Scripts/Game.gd script file if you had any trouble following along (which hopefully didn’t happen!) or if you just want to compare.

As you will see, my version of res://Scripts/Game.gd has the functions in a different order. That’s because I tried to go through the functions in a logical way, but my code is structure based on my own conventions which go back to my obsession on programming guidelines that I got from Python. I follow these self-imposed guidelines:

  • constants go first (const), they don’t appear in this script but we’ll make use of them later
  • signals come right after, something we’ll also explore in later tutorials
  • exports come after signals (which are all “public” variables, i.e. they don’t start with double underscore)
  • next are regular “public” variables right after (we don’t have any of these in this script)
  • next there are the “private” variables (starting with double underscore)
  • next are defined the “public” functions
  • following are the internal Godot exposed functions (such as _ready, _init etc.)
  • finally we have all of the “private” functions
  • and if the script contains any internal classes they come at the very end and follow the same rules as above

Also the comment styles for the functions the triple """ enclosed ones were influenced by numpys commenting guidelines, a module that comes from Python as you can imagine. Since GDScript is heavily influenced by Python I find that these rules work really well. But of course it is up to you to come up with your own rules or if you want to follow any rules at all. My suggestion is for you to come up with a set of conventions and stick with them 95% of the time at least!!