Tag Archives: Godot Engine

Randomly Generated Dungeons in Godot Part 2 – Isometric

You can click here to read part 1

Last time I touched this project (almost a year ago, ouch) we got the drunken walk algorithm working inside of the Godot game engine. Now we are going to continue with the project and spawn in walls and a player.

Because it had been so long I went and tried to follow my own tutorial, to get back up to speed, and found it hard to follow. So I have gone back and added some details that I felt that it was lacking. It should be easier to follow now.

I have been on a bit of an Isometric kick so I thought it would be cool to get this random map generation to work in an isometric perspective. Like last time, the sprites that I used can be downloaded here

Just like with the isometric dungeons in Gamemaker Studio, we don’t need walls on all 4 sides of the floor, only two of them. So at the end of our _generate_map() function we can run a nested for loop, checking for ground tiles and flag the tiles above and to the left as walls.

for x in room_size.x:
	for y in room_size.y:
		if get_cell(x, y) == GROUND:
			if get_cell(x, y-1) == VOID:
				set_cell(x, y-1, WALL);
			if get_cell(x-1, y) == VOID:
				set_cell(x-1, y, WALL);

This step alone will not flag the walls because the tilemap is full of null values and not VOID so these if statements will not resolve to true. At the beginning of _generate_map() we need another for loop that sets all of the tiles as void.

for x in room_size.x:
	for y in room_size.y:
		set_cell(x, y, VOID);

Now when we run the scene, it should flag all of the walls. What you may notice is that floor tiles right at the edge of the scene will not have any walls, and to fix that you will just need to update the clamps from 0 and room_size.x/y -1 to 1 and room_size.x/y -2. This creates a 1 tile border around the edge of the map that can be dedicated to spawning in walls.

2D Random Walk with walls spawning in

Next we need to go to the editor and change the tilemap mode from square to isometric and update the sprites that we are using. You can either add my sprites or make your own.

If you do decide to make your own sprites the floor tile needs to be wider than it is tall. A good ratio is 64 pixels wide and 32 pixels tall. If You can make your floor 34 pixels tall to give it a little bit of thickness at the edges (You will see what I mean when we are done).

Clicking on the TileMap Node will display its inspector information on the right hand side of the window. Using the Mode drop down, select isometric instead of square, update the cell size from 32 by 32 to 64 by 32 and using the Tile Set drop down select New Tile Set.

When we click on the New Tile Set, the window at the bottom of the screen will update. Drag your new tile set sprite into the filesystem window and then add your new sprite to the tile set selector. My isometric sprites have a little bit of white space in between the tiles so we need to modify the step to be 64 by 97 and add a separation of 2 between the tiles and an offset of 1 like this.

A better quality video can be located here, Set the snap offset to 1 instead of 0

We need to add collision to the void and wall tiles that matches the shape of the floor. You will probably need to play with the snap options to move the grid lines so that it lines up properly.

I set the snap step to 32 by 16 and the offset at 5 to set the collision for the wall, and 1 to set the collision for the void space. You don’t need to worry about the little bit of the wall that is below the collision, the floor is going to draw over that.

If you have done everything properly when you execute the scene, it should be drawing the map in an isometric perspective, but some of your map may be off screen.

With the map rendering in an isometric perspective we are ready to add a player character and a camera so that we can explore the map. You will need to change the game window from 1024 by 1024 to something more appropriate. I picked 960 by 540.

To make our player we need to make a new scene. In the new scene menu, select other node and add a KinematicBody2D as the root node, add the player character sprite to your project and then drag the player sprite into the scene, and add a Camera2D and CollisionPolygon2D as child nodes to the Kinematic Body. The feet of the player sprite should be lined up with the horizontal pink line in the scene, consider that the floor.

The collision box on the CollisionPolygon should be in an isometric shape just like the tiles. It only needs to have 4 points. You can set them at the values in the screenshot below, and you need to check the “Current” flag on the camera2d node so that the camera will follow the player.

The KinematicBody2d node is going to need a script attached so we can listen for input and move the player. We assign a speed value to the player, and inside of _physics_process(delta): we listen for keystrokes and move the object based off of our speed value. The finished script looks something like this

extends KinematicBody2D

# Declare member variables here. Examples:
export (float) var spd = 120;

# Called when the node enters the scene tree for the first time.
func _ready():
	pass # Replace with function body.

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _physics_process(delta):
	var motion = Vector2.ZERO;
	
	if (Input.is_action_pressed("ui_up")):
		motion += Vector2.UP;
	if (Input.is_action_pressed("ui_down")):
		motion += Vector2.DOWN;
	if (Input.is_action_pressed("ui_right")):
		motion += Vector2.RIGHT;
	if (Input.is_action_pressed("ui_left")):
		motion += Vector2.LEFT;
		
	motion = motion.normalized();
	motion *= spd;
	move_and_slide(motion);

Finally you can add the player scene as a child of the tilemap simply by dragging the player scene on top of the tilemap in the editor.

If you were to run the game at this point, you would notice that the player does not spawn inside of the map, and you are unable to move. We need to add 1 more line of code to the _generate_map() function of the tilemap script which moves the player inside of the tilemap.

$KinematicBody2D.position = map_to_world(room_size / 2);

With that one line of code added, when you execute the game, the player should be moved onto the very first tile that was flagged as a floor, and you should be able to move around the map, and collide with the walls.

Depth doesn’t work yet, so the player will always render above the walls, but as you can see, the player is able to move around the map, collide with the walls and the void space. We have set up the beginnings of a 2d Isometric game in the GODOT engine.

You can click here to read part 3

Until Next Time
– Steven

Converting Heartbeast’s Random Walk to Godot Part 1

One of my colleagues has been requesting that I make the switch to Godot from Gamemaker Studio to develop my games. One key point to our discussions has been about the video of Heartbeast saying that his favorite engine to use is Godot (also, you can use godot in Linux).

I have been reluctant to make the switch because I find the ease of use with Gamemaker Studio very appealing and my overall comfort level with it is high; however, I relented and agreed to try out Godot for the next game that I am working on. Depending on how well this goes I may switch over to Godot permanently.

If I am going to switch over, some of my commonly used tricks would need to come over with me, and one of the first things I would need to do is figure out how to rebuild Heartbeast‘s Drunken Walk algorithm in Godot.

For those who are not familiar with how this algorithm works, Heartbeast creates a DS_Grid, fills it with a void value, and then has a “controller” (just 2 variables that hold an x and y value) move through the DS_Grid, randomly, and assign the value of floor to its coordinates. For this conversion to be considered successful I will need to match this functionality. So, lets open up a new Godot project and see what we can do.

By default the Godot editor will be in 3d, you can switch over to 2d by clicking on 2D at the top of the editor. The png file, that we want to use for our tileset, can be dragged into the asset library at the bottom left of the screen. Once your editor looks like mine, then we are able to proceed. (If you don’t want to make your own sprites, you can download the sprites I will be using here.)

Next we will need to add a node to the scene. Since our scene is a 2d scene, it makes sense to add a 2d node. After your root node has been added we can add a tilemap as a child to the root node. The tilemap is a new feature to Godot and it will provide us the same functionality that the DS_Grid does in Gamemaker Studio, and a little bit more.

Once the tilemap has been added to the scene, we can edit it with the inspector, on the right hand side of the screen, while it is selected. A new tilemap will start with the tileset field being empty, by clicking on the drop down menu we can create a new tileset, and we can edit the tileset by clicking on the drop down menu again (I found this part confusing at first).

Once you click edit tile map, a new window will be displayed at the bottom of the screen. With this window we can assign a texture (sprite) to the tileset and after our texture has been assigned we can select which part of the sprite represents what tile by clicking on “New Single Tile”.

A lot more work in the editor than I am used to in GM:S

After our tiles have been created, we can specify which tiles will be used for collision. My sprite has 3 tiles, a void, ground and wall, so I assigned collision to void and wall, you can do whatever your project requires. We also need to set the tile size in the inspector, my tiles are 32 * 32. Don’t forget to save your tilemap by clicking the save button in the inspector at the top right hand side.

Here is the full process of building the tileset.

This is most of the work that we need to finish in the editor, the rest of the work is going to be coding in Gdscript. To attach a script to the tilemap we can right click the tilemap and select “Attach Script”. After the script has been attached, the editor will open and we will be ready to do some coding.

Finally some coding

The script that was created comes with 2 pre-built functions, the _ready() function and the _process(delta) function. These are comparable to the create and step events from Gamemaker Studio. Most of our work is going to be inside of the _ready() function, but before we do that we need to write some code at the very top of the script.

Like in Heartbeast’s script we first create some constants to reference the tiles in our tile map. Your values may be different than mine depending on the order that you created them. I made mine in order from left to right on the sprite so 0, 1, 2 will be void, ground and wall respectively. We can also use a line of code to add a field to the editor that allows us to set how many tiles will be in the room. Instead of having a separate x and y values, like in Gamemaker, we can just use a Vector2.

export (Vector2) var room_size;
const VOID = 0;
const GROUND = 1;
const WALL = 2;

In the _ready() function we can create our controller and have it start in the middle of the tilemap. We also need to get our first random direction, and set how frequently it will change direction. Like the room size, our controller can also be a Vector2, the direction can still be an int from 0-3 and the odds can still be an int from 0-1 giving us a 50% chance of changing directions. You will also need to call the randomize function so that your map gets a new seed each time it executes.

randomize();
var controller = room_size / 2;
var direction = randi() % 4;
var odds = randi() % 2;

The final part of this script is to use a loop to move the controller in a random direction. This looks pretty similar to how it is done with Gamemaker Studio. We create an if statement to check if we need to switch direction, we multiply the direction by 90 to give us a value in degrees, from 0-360, and then move the cursor in our random direction. We are also going to clamp the controller so that it doesn’t go outside of the tilemap.

for i in 400:
     set_cellv(controller, GROUND);
     var vdir = Vector2.RIGHT.rotated(deg2rad(direction * 90));
     if odds == 1:
          direction = randi() % 4;
     controller += vdir;
     controller.x = clamp(controller.x, 0, room_size.x - 1);
     controller.y = clamp(controller.y, 0, room_size.y - 1);
     odds = randi() % 2;

With our coding done we can go back to the editor and set how large the room will be in tiles with the room_size variable that will now display. The sprites are 32 by 32 so we can make the cell size 32 by 32 and we can make the room size 32 by 32. This gives our game a width and height of 32 cells and each cell is 32 by 32 pixels.

The finished inspector of the tilemap

Before you run the game you need to make sure that your camera is big enough to see the whole map (this tutorial doesn’t cover making a player or a moving camera). To adjust the map size you can click on Project -> Project Settings -> Display -> Window and adjust the resolution so that it will fit your whole map (1024 * 1024). Once that is finished you can run the scene and you should hopefully see something like this.

I have a Drunken Walk map

Because this is being done in Godot, and GDScript isn’t as broken as GML, we can copy the contents of the _ready() function and make our own function called _generate_map() and inside of _process(delta) we can clear the tilemap and generate a new map with a keypress and you can see the different maps you can get.

if (Input.is_action_pressed("ui_select")):
     clear();
     _generate_map();

Changing the odds variable will give us different kinds of maps. Having the odds variable as odds = randi() % 2; gives us a value between 0 and 1, giving us a 50/50 chance of changing direction. Which results in maps that have larger rooms. Lowering your odds will result in maps with longer and longer hallways and less rooms. Here is an example with odds = randi() % 5 (you need to change your if statement to if odds == 4: for this to work)

The script isn’t 100% finished yet. Walls are not yet being spawned in, and there are some other missing features, but this blog post / tutorial is already over 1000 words, so I suppose I will have to come back and write another post to finish the script.

I would like to thank Rkoopah for helping me out while I am learning how to use Godot.

You can click here to read part 2
You can click here to read part 3

Until Next Time
– Steven