Coding the Rooms class

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.SIDEType.LRType.LRBType.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: SideLRLRBLRT, 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 SideLRLRBLRT, 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 105 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.

References

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
Community Discussion