Now that we covered the _update_*() functions, we can look at the functions that place the actual rooms.
func _generate_level() -> void: _reset() _update_start_position() while _state.offset.y < grid_size.y: _update_room_type() _update_next_position() _update_down_counter() _place_walls() _place_path_rooms() _place_side_rooms()
In this part we’ll cover the _place_walls(), _place_path_rooms() and _place_side_rooms() functions.
We place the outer walls of the level with _place_walls().
func _place_walls(type: int = 0) -> void: var cell_grid_size := _grid_to_map(grid_size) for x in [-1, cell_grid_size.x]: for y in range(-1, cell_grid_size.y + 1): level.set_cell(x, y, type) for x in range(cell_grid_size.x + 1): for y in [-1, cell_grid_size.y]: level.set_cell(x, y, type)
This function takes a type parameter which has the value 0 by default. This corresponds to our wall tile. We get the grid size of the TileMap via _grid_to_map(grid_size). This function is covered at the end of this part.
We then iterate over the two x positions: -1 and cell_grid_size.x. For each of these we go through all the cells from -1 to cell_grid_size.y and set the cell with TileMap.set_cell(). This creates the left and right boundaries of our level.
We do the same for all the horizontal cells between 0 and cell_grid_size.x at the two Y boundaries: -1 and cell_grid_size.y. This creates the top and bottom boundaries of our level.
Next we have the _place_path_rooms() function.
func _place_path_rooms() -> void: for path in _state.path: yield(timer, "timeout") _copy_room(path.offset, path.type) emit_signal("path_completed")
This is a straightforward loop over _state.path. For each element we use _copy_room() to place the appropriate cells from the Rooms scene.
To delay the room placement, we wait on the Timer’s timeout signal using yield().
At the end of the process we call emit_signal("path_completed") so that the next function _place_side_rooms() will start. We’ll cover _copy_room() after we look at _place_side_rooms.
func _place_side_rooms() -> void: yield(self, "path_completed") var rooms_max_index: int = _rooms.Type.size() - 1 for key in _state.empty_cells: var type := _rng.randi_range(0, rooms_max_index) _copy_room(key, type)
When the path_completed signal is emitted, we again use _copy_room() to place cells for each position from _state.empty_cells. Remember that _state.empty_cells is a dictionary which stores the positions as keys. We pick a room type at random since it doesn’t change the valid path. These rooms can safely be Type.SIDE as well.
Let’s now take a look at the _copy_room() utility function.
func _copy_room(offset: Vector2, type: int) -> void: var map_offset := _grid_to_map(offset) var data: Array = _rooms.get_room_data(type) for d in data: level.set_cellv(map_offset + d.offset, d.cell)
Given an offset position in grid coordinates, we first convert it to TileMap coordinates with _grid_to_map(). Then we get the room data and loop over it, setting the appropriate cell with set_cellv().
The last functions that we need to cover are the _grid_*() functions.
func _grid_to_map(vector: Vector2) -> Vector2: return _rooms.room_size * vector func _grid_to_world(vector: Vector2) -> Vector2: return _rooms.cell_size * _rooms.room_size * vector
_grid_to_map() maps a vector on the level grid to a position on the level TileMap.
_grid_to_world() maps a vector from the level grid to its position in pixels. This is its position in the world.
Recall that _grid_to_world() was used in the _setup_camera() function to calculate the camera.position property.
Here is the full RandomWalker.gd for reference.
extends Node2D signal path_completed const STEP := [Vector2.LEFT, Vector2.LEFT, Vector2.RIGHT, Vector2.RIGHT, Vector2.DOWN] export (PackedScene) var Rooms := preload("Rooms.tscn") export var grid_size := Vector2(8, 6) var _rooms: Node2D = null var _rng := RandomNumberGenerator.new() var _state := {} var _horizontal_chance := 0.0 onready var camera: Camera2D = $Camera2D onready var timer: Timer = $Timer onready var level: TileMap = $Level func _ready() -> void: _rng.randomize() _rooms = Rooms.instance() _horizontal_chance = 1.0 - STEP.count(Vector2.DOWN) / float(STEP.size()) _setup_camera() _generate_level() func _setup_camera() -> void: var world_size := _grid_to_world(grid_size) camera.position = world_size / 2 var ratio := world_size / OS.window_size var zoom_max := max(ratio.x, ratio.y) + 1 camera.zoom = Vector2(zoom_max, zoom_max) func _generate_level() -> void: _reset() _update_start_position() while _state.offset.y < grid_size.y: _update_room_type() _update_next_position() _update_down_counter() _place_walls() _place_path_rooms() _place_side_rooms() func _reset() -> void: _state = { "random_index": -1, "offset": Vector2.ZERO, "delta": Vector2.ZERO, "down_counter": 0, "path": [], "empty_cells": {} } for x in range(grid_size.x): for y in range(grid_size.y): _state.empty_cells[Vector2(x, y)] = 0 func _update_start_position() -> void: var x := _rng.randi_range(0, grid_size.x - 1) _state.offset = Vector2(x, 0) func _update_room_type() -> void: if not _state.path.empty(): var last: Dictionary = _state.path.back() if last.type in _rooms.BOTTOM_CLOSED and _state.delta.is_equal_approx(Vector2.DOWN): var index := _rng.randi_range(0, _rooms.BOTTOM_OPENED.size() - 1) var type: int = ( _rooms.BOTTOM_OPENED[index] if _state.down_counter < 2 else _rooms.Type.LRTB ) _state.path[-1].type = type var type: int = ( _rooms.Type.LRT if _state.delta.is_equal_approx(Vector2.DOWN) else _rng.randi_range(1, _rooms.Type.size() - 1) ) _state.empty_cells.erase(_state.offset) _state.path.push_back({"offset": _state.offset, "type": type}) func _update_next_position() -> void: _state.random_index = ( _rng.randi_range(0, STEP.size() - 1) if _state.random_index < 0 else _state.random_index ) _state.delta = STEP[_state.random_index] var horizontal_chance := _rng.randf() if _state.delta.is_equal_approx(Vector2.LEFT): _state.random_index = 0 if _state.offset.x > 1 and horizontal_chance < _horizontal_chance else 4 elif _state.delta.is_equal_approx(Vector2.RIGHT): _state.random_index = 2 if _state.offset.x < grid_size.x - 1 and horizontal_chance < _horizontal_chance else 4 else: if _state.offset.x > 0 and _state.offset.x < grid_size.x - 1: _state.random_index = _rng.randi_range(0, 4) elif _state.offset.x == 0: _state.random_index = 2 if horizontal_chance < _horizontal_chance else 4 elif _state.offset.x == grid_size.x - 1: _state.random_index = 0 if horizontal_chance < _horizontal_chance else 4 _state.delta = STEP[_state.random_index] _state.offset += _state.delta func _update_down_counter() -> void: _state.down_counter = ( _state.down_counter + 1 if _state.delta.is_equal_approx(Vector2.DOWN) else 0 ) func _place_walls(type: int = 0) -> void: var cell_grid_size := _grid_to_map(grid_size) for x in [-1, cell_grid_size.x]: for y in range(-1, cell_grid_size.y + 1): level.set_cell(x, y, type) for x in range(cell_grid_size.x + 1): for y in [-1, cell_grid_size.y]: level.set_cell(x, y, type) func _place_path_rooms() -> void: for path in _state.path: yield(timer, "timeout") _copy_room(path.offset, path.type) emit_signal("path_completed") func _place_side_rooms() -> void: yield(self, "path_completed") var rooms_max_index: int = _rooms.Type.size() - 1 for key in _state.empty_cells: var type := _rng.randi_range(0, rooms_max_index) _copy_room(key, type) func _copy_room(offset: Vector2, type: int) -> void: var map_offset := _grid_to_map(offset) var data: Array = _rooms.get_room_data(type) for d in data: level.set_cellv(map_offset + d.offset, d.cell) func _grid_to_map(vector: Vector2) -> Vector2: return _rooms.room_size * vector func _grid_to_world(vector: Vector2) -> Vector2: return _rooms.cell_size * _rooms.room_size * vector