Our level generator needs to get random room data of a given type. So add a new script to your Rooms node and save it as Rooms.gd
.
At the top of the script, define a new enumeration named Type
. We need it to get a random room among Type.SIDE
, Type.LR
, Type.LRB
, Type.LRT
, and Type.LRTB
.
extends Node2D enum Type { SIDE, LR, LRB, LRT, LRTB }
By default, enumeration members are associated with a unique integer value starting from 0
. We can use these values as indices to get the group nodes we created in the previous lesson: Side, LR, LRB, LRT, and LRTB. That’s why the order of the nodes matters in the Rooms scene.
Next, let’s define some constants to group rooms depending on their bottom openings. Our random walker algorithm is going to create a path moving down, so this information is useful to produce the level’s main path.
const BOTTOM_OPENED := [Type.LRB, Type.LRTB] const BOTTOM_CLOSED := [Type.LR, Type.LRT]
Then, define variables to keep track of each room’s size in cells, and the size of cells in pixels. Doing so makes our system work with arbitrary chunk sizes.
var room_size := Vector2.ZERO var cell_size := Vector2.ZERO
We need to pick random rooms for the level generator to use. The built-in RandomNumberGenerator
class is the perfect tool for that. Create a new instance of it.
var _rng := RandomNumberGenerator.new()
At this point, the top of the class should look like this:
extends Node2D enum Type { SIDE, LR, LRB, LRT, LRTB } const BOTTOM_OPENED := [Type.LRB, Type.LRTB] const BOTTOM_CLOSED := [Type.LR, Type.LRT] var room_size := Vector2.ZERO var cell_size := Vector2.ZERO var _rng := RandomNumberGenerator.new()
In the next code listing, we calculate the public variables room_size
and cell_size
at run time. This way, we don’t have to keep track of them manually. We also initialize the RandomNumberGenerator
variable, _rng
. We use this to get a random room, given a room type.
Instead of using the more common _ready()
callback, we use _notification()
because _ready
runs when a node enters the scene tree and not if you instance it from code. With _notification()
, you can run instructions when you create an instance of a class even if you don’t add it to the tree. Check out the official documentation for more details on the _notification()
function.
func _notification(what: int) -> void: if what == Node.NOTIFICATION_INSTANCED: _rng.randomize() var room: TileMap = $Side.get_child(0) room_size = room.get_used_rect().size cell_size = room.cell_size
The Node.NOTIFICATION_INSTANCED
value gets emitted once upon instancing the scene. When that happens, we randomize the RandomNumberGenerator
’s seed so we get different rooms each time we run the game. Otherwise, Godot would start with the same seed value and we’d get the same result each time.
We get the first child under the Side node to calculate the room_size
and cell_size
. We do so, assuming that all the other rooms are the same size.
Finally we have the get_room_data()
. The main algorithm is going to call it to get random room data of a specific room type in the form of an array:
func get_room_data(type: int) -> Array: var group: Node2D = get_child(type) var index := _rng.randi_range(0, group.get_child_count() - 1) var room: TileMap = group.get_child(index) var data := [] for v in room.get_used_cells(): data.push_back({"offset": v, "cell": room.get_cellv(v)}) return data
First, we get the group node based on the given type. It should be one of the Side, LR, LRB, LRT, and LRTB nodes.
Next we generate an index
between 0
and r - 1
using the RandomNumberGenerator.randi_range()
function, where r
is the child count of the group node. We use r - 1
because RandomNumberGenerator.randi_range()
is an inclusive function, meaning that _rng.randi_range(5, 10)
returns values from 5
to 10
, 5
and 10
included.
We then iterate over the TileMap used cells and append a dictionary to data
in the form {"offset": Vector2, "cell": int}
. data.offset
is the location in TileMap coordinates of the tile type data.cell
.
Here is the complete Rooms.gd
code we went over:
extends Node2D enum Type { SIDE, LR, LRB, LRT, LRTB } const BOTTOM_OPENED := [Type.LRB, Type.LRTB] const BOTTOM_CLOSED := [Type.LR, Type.LRT] var room_size := Vector2.ZERO var cell_size := Vector2.ZERO var _rng := RandomNumberGenerator.new() func _notification(what: int) -> void: if what == Node.NOTIFICATION_INSTANCED: _rng.randomize() var room: TileMap = $Side.get_child(0) room_size = room.get_used_rect().size cell_size = room.cell_size func get_room_data(type: int) -> Array: var group: Node2D = get_child(type) var index := _rng.randi_range(0, group.get_child_count() - 1) var room: TileMap = group.get_child(index) var data := [] for v in room.get_used_cells(): data.push_back({"offset": v, "cell": room.get_cellv(v)}) return data