class_name StandardWorldGenerator 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] = [] var _rooms_bottom: Array[PackedScene] = [] var _rooms_blocking: Array[PackedScene] = [] var _progress_tracker: ProgressTracker func _generate(map: TileMap) -> void: self._progress_tracker = ProgressTracker.new(1) self._sort_rooms(_progress_tracker.next_step("Sorting Rooms")) # 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")) map.update_internals() func _sort_rooms(step_tracker: ProgressStepTracker) -> void: step_tracker.complete.call_deferred() step_tracker.set_substeps(len(self.rooms)) for room_scene in rooms: step_tracker.substep(room_scene.get_name()) var room: StandardRoom = room_scene.instantiate() if room.left: self._rooms_left.append(room_scene) if room.right: self._rooms_right.append(room_scene) if room.top: self._rooms_top.append(room_scene) if room.bottom: self._rooms_bottom.append(room_scene) if room.is_block(): self._rooms_blocking.append(room_scene) room.queue_free() if len(self._rooms_left) == 0: push_error("0 left rooms!") if len(self._rooms_right) == 0: push_error("0 right rooms!") if len(self._rooms_top) == 0: push_error("0 top rooms!") if len(self._rooms_bottom) == 0: push_error("0 bottom rooms!") 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 init_room: StandardRoom = room.instantiate() # Copy spawn room into map init_room.copy_to_map(map) return init_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: for exit in exits: parent_room.seal_exit(map, parent_room.position, exit) 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) # Get the rooms that can be added in this direction var possible_rooms: Array[PackedScene] = self._get_rooms(parent_room.flip_vector(exit)).duplicate() # If there are no rooms that can be added in this direction, skip it if len(possible_rooms) == 0: continue # Get the room to add var room_instance: StandardRoom = null while len(possible_rooms) != 0 and room_instance == null: var i = randi() % len(possible_rooms) var possible_room = possible_rooms.pop_at(i).instantiate() var origin_pos: Vector2i = possible_room.get_exit_pos_offset(room_edge_pos, exit) possible_room.position = origin_pos if possible_room.is_overlapping(map): continue room_instance = possible_room if room_instance == null: parent_room.seal_exit(map, parent_room.position, exit) continue room_instance.queue_free.call_deferred() # Disable direction that the room was added from # since the parent room will already be there room_instance.disable_room_exit_opposite(exit) # Copy the room into the map room_instance.copy_to_map(map) # Create rooms from the exits of the room that was just added step_tracker.substep("Creating Rooms") self._create_rooms(map, room_instance, max_path_length - 1, step_tracker) # 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