From cd80ab3357c1e9ec40c5630f3b434c47b367754b Mon Sep 17 00:00:00 2001 From: Layla Manley Date: Tue, 16 Jan 2024 21:34:08 +0100 Subject: [PATCH] Implement simple worldgen --- scripts/v2/worldgen/standard/room.gd | 65 +++++++++++++- scripts/v2/worldgen/standard_generator.gd | 103 +++++++++++++++++++++- 2 files changed, 166 insertions(+), 2 deletions(-) diff --git a/scripts/v2/worldgen/standard/room.gd b/scripts/v2/worldgen/standard/room.gd index ddb1740..11ed025 100644 --- a/scripts/v2/worldgen/standard/room.gd +++ b/scripts/v2/worldgen/standard/room.gd @@ -1,19 +1,82 @@ class_name StandardRoom extends TileMap +const TILEMAP_LAYER = 0 + @export_category("Room Openings") @export var left: bool = false @export var right: bool = false @export var top: bool = false @export var bottom: bool = false - @onready var room_size: Vector2i = self.get_used_rect().size @onready var cell_size: Vector2i = self.tile_set.tile_size + func is_block() -> bool: if self.left or self.right or self.top or self.bottom: return false return true + +func get_exits() -> Array[Vector2i]: + var exits: Array[Vector2i] = [] + if self.left: + exits.append(Vector2i.LEFT) + if self.right: + exits.append(Vector2i.RIGHT) + if self.top: + exits.append(Vector2i.UP) + if self.bottom: + exits.append(Vector2i.DOWN) + return exits + +func get_exit_pos(exit: Vector2i) -> Vector2i: + var pos: Vector2i = Vector2i.ZERO + if exit == Vector2i.LEFT: + pos = Vector2i(0, self.room_size.y / 2) + elif exit == Vector2i.RIGHT: + pos = Vector2i(self.room_size.x - 1, self.room_size.y / 2) + elif exit == Vector2i.UP: + pos = Vector2i(self.room_size.x / 2, 0) + elif exit == Vector2i.DOWN: + pos = Vector2i(self.room_size.x / 2, self.room_size.y - 1) + return pos + +func copy_to_map(map: TileMap, pos: Vector2i) -> void: + for x in range(self.room_size.x): + for y in range(self.room_size.y): + var tile = self.get_cell_source_id(TILEMAP_LAYER, Vector2i(x, y)) + if tile.tile_set: + map.set_cell(TILEMAP_LAYER, Vector2i(pos.x + x, pos.y + y), tile.tile_id) + else: + map.set_cell(TILEMAP_LAYER, Vector2i(pos.x + x, pos.y + y), -1) + +# Validate room by checking that the top left tile is at 0, 0 and all exits are valid +func _validate() -> bool: + + # Check of tiles where x is negative + pass + + # Check for tiles where y is negative + pass + + # If exit on left, check that the middle tile on the left is empty + if self.left and self.get_cell_source_id(TILEMAP_LAYER, Vector2i(0, self.room_size.y / 2)) != -1: + push_error("Room with exit on left must have empty middle tile on left") + return false + # If exit on right, check that the middle tile on the right is empty + if self.right and self.get_cell_source_id(TILEMAP_LAYER, Vector2i(self.room_size.x - 1, self.room_size.y / 2)) != -1: + push_error("Room with exit on right must have empty middle tile on right") + return false + # If exit on top, check that the middle tile on the top is empty + if self.top and self.get_cell_source_id(TILEMAP_LAYER, Vector2i(self.room_size.x / 2, 0)) != -1: + push_error("Room with exit on top must have empty middle tile on top") + return false + # If exit on bottom, check that the middle tile on the bottom is empty + if self.bottom and self.get_cell_source_id(TILEMAP_LAYER, Vector2i(self.room_size.x / 2, self.room_size.y - 1)) != -1: + push_error("Room with exit on bottom must have empty middle tile on bottom") + return false + + return true diff --git a/scripts/v2/worldgen/standard_generator.gd b/scripts/v2/worldgen/standard_generator.gd index 8cec37d..6bb79fb 100644 --- a/scripts/v2/worldgen/standard_generator.gd +++ b/scripts/v2/worldgen/standard_generator.gd @@ -4,6 +4,14 @@ extends WorldGenerator @export var rooms: Array[PackedScene] = [] +@export +var max_room_path_length: int = 10 + +@export +var spawn_room: PackedScene = null + +# Rooms are sorted into these arrays based on their exits +# Each PackedScene is a StandardRoom var _rooms_left: Array[PackedScene] = [] var _rooms_right: Array[PackedScene] = [] var _rooms_top: Array[PackedScene] = [] @@ -13,9 +21,17 @@ var _rooms_blocking: Array[PackedScene] = [] var _progress_tracker: ProgressTracker = ProgressTracker.new(1) func _generate(map: TileMap) -> void: + self._sort_rooms(_progress_tracker.next_step("Sorting Rooms")) -func _sort_rooms(step_tracker: ProgressStepTracker) -> void: + # Create initial room + var init_room = self._create_initial_room(map, self.spawn_room, _progress_tracker.next_step("Creating Initial Room")) + init_room.queue_free.call_deferred() + + # Create rooms + self._create_rooms(map, init_room, self.max_room_path_length, _progress_tracker.next_step("Creating Rooms")) + +func _sort_rooms(step_tracker: ProgressStepTracker, validate: bool = false) -> void: step_tracker.complete.call_deferred() step_tracker.set_substeps(len(self.rooms)) @@ -46,5 +62,90 @@ func _sort_rooms(step_tracker: ProgressStepTracker) -> void: if len(self._rooms_blocking) == 0: push_warning("0 blocking rooms!") +# Create initial room +func _create_initial_room(map: TileMap, room: PackedScene, step_tracker: ProgressStepTracker) -> StandardRoom: + step_tracker.complete.call_deferred() + + # Instantiate spawn room + var spawn_room: StandardRoom = room.instance() + + # Copy spawn room into map + spawn_room.copy_to_map(map, Vector2i(0, 0)) + + return spawn_room + + +# Create rooms by randomly selecting from the available rooms in every direction with openings from the current room +# where the maximum path length has not been exceeded and the room does not overlap with any other room. +# +# Each room is marked with boolean flags for each direction that it has an opening to another room. +# +# This is done recursively until the maximum path length is exceeded or there are no more rooms to add +func _create_rooms(map: TileMap, parent_room: StandardRoom, max_path_length: int, step_tracker: ProgressStepTracker) -> void: + step_tracker.complete.call_deferred() + + var exits: Array[Vector2i] = parent_room.get_exits() + + # If the maximum path length has been exceeded, stop + if max_path_length <= 0: + return + + # If there are no more exits, stop + if len(exits) == 0: + return + + # If there are no more rooms to add, stop + if len(self._rooms_left) == 0 and len(self._rooms_right) == 0 and len(self._rooms_top) == 0 and len(self._rooms_bottom) == 0: + push_warning("No more rooms to add!") + return + + # For every exit, try to add a room + for exit in exits: + # Find exit pos in current room + var exit_pos: Vector2i = parent_room.get_exit_pos(exit) + + var room_edge_pos: Vector2i = exit_pos + Vector2i(parent_room.position) + + # If there is already a room at this position, skip it + if map.get_cellv(room_edge_pos) != -1: + continue + + # Get the rooms that can be added in this direction + var rooms: Array[PackedScene] = self._get_rooms(exit) + + # If there are no rooms that can be added in this direction, skip it + if len(rooms) == 0: + continue + + # Get the room to add + var room: PackedScene = rooms[randi() % len(rooms)] + + # Instantiate the room + var room_instance: StandardRoom = room.instance() + room_instance.position = room_edge_pos + (room_instance.size / 2) + room_instance.queue_free.call_deferred() + + # Copy the room into the map + room_instance.copy_to_map(map, Vector2i(room_instance.position)) + + # Create rooms from the exits of the room that was just added + self._create_rooms(map, room_instance, max_path_length - 1, step_tracker.next_step("Creating Rooms")) + + # If there are no more exits, stop + return + +func _get_rooms(exit: Vector2i) -> Array[PackedScene]: + if exit == Vector2i.LEFT: + return self._rooms_left + elif exit == Vector2i.RIGHT: + return self._rooms_right + elif exit == Vector2i.UP: + return self._rooms_top + elif exit == Vector2i.DOWN: + return self._rooms_bottom + else: + push_error("Invalid exit: " + str(exit)) + return [] + func _get_progress_tracker() -> ProgressTracker: return self._progress_tracker