Help with dungeon generation

Godot Version

4.4

Question

` New to game development, and wanted to create a roguelike medevil dungeon crawler, the issue I am having now is the generation of dungeons.
From doing multipple hours of research and looking at godot redit posts, i have written this code that is semi-funtional, he only issue is that the dungeon only generates 1 room and i want a maximum of 25 to 30 rooms per dungeon. The scenes for each room are set up to have collisions and sprites that cover doorways into other rooms and are disabled when another room is spawned in that position. but it still wont work. Never expected game development to be this annoying but help would be rally appreciated.

CODE:
extends Node

const ROOM_SCENES = {
   "square": preload("res://Scenes/Rooms/Beginging/room_square_begining.tscn"),
   "horizontal_corridor": preload("res://Scenes/Rooms/Beginging/horizontal_corridor.tscn"),
   "vertical_corridor": preload("res://Scenes/Rooms/Beginging/vertical_corridor.tscn")
}

var used_positions = []
var placed_rooms = []

func get_entry_points(room):
   if not room.has_node("EntryPoints"):
   	push_warning("Room missing EntryPoints: " + str(room.name))
   	return []
   return room.get_node("EntryPoints").get_children()

func position_overlaps(pos: Vector2) -> bool:
   for used_pos in used_positions:
   	if used_pos.distance_to(pos) < 128:  # Adjust to your room size + margin
   		return true
   return false

func _ready():
   var lobby_scene = preload("res://Scenes/Rooms/Beginging/begining_lobby.tscn")
   var lobby = lobby_scene.instantiate()
   var map_root = get_parent().get_node("MapRoot")
   map_root.add_child(lobby)

   var spawn = lobby.get_node("SpawnPoint")
   get_parent().get_node("Player").global_position = spawn.global_position

   var exit_door = lobby.get_node("ExitDoor")
   exit_door.connect("dungeon_requested", Callable(self, "generate_dungeon"))

   used_positions.append(lobby.global_position)
   placed_rooms.append(lobby)

func disable_wall_at_door(room, door_marker: Marker2D):
   var side = door_marker.name
   var parts_to_disable = {
   	"Top": ["TopLeft", "TopRight"],
   	"Bottom": ["BottomLeft", "BottomRight"],
   	"Left": ["LeftTop", "LeftBottom"],
   	"Right": ["RightTop", "RightBottom"]
   }

   if not room.has_node("WallCollider"):
   	return

   var wall_collider = room.get_node("WallCollider")
   for part_name in parts_to_disable.get(side, []):
   	if wall_collider.has_node(part_name):
   		var part = wall_collider.get_node(part_name)
   		part.set_deferred("disabled", true)

func pick_room_type(exclude_type: String) -> String:
   var types = ROOM_SCENES.keys()
   var filtered = []
   for t in types:
   	if t != exclude_type:
   		filtered.append(t)
   return filtered.pick_random()

func generate_dungeon():
   var map_root = get_parent().get_node("MapRoot")
   var start_room = placed_rooms[0]  # lobby
   var open_doors = get_entry_points(start_room).duplicate()
   var max_rooms = 10
   var last_room_type = "lobby"

   for i in range(max_rooms):
   	if open_doors.is_empty():
   		break

   	var existing_door = open_doors.pop_front()
   	var room_type = pick_room_type(last_room_type)
   	last_room_type = room_type

   	var new_room = ROOM_SCENES[room_type].instantiate()
   	var new_entry_points = get_entry_points(new_room)
   	if new_entry_points.is_empty():
   		push_warning("New room has no entry points: " + room_type)
   		continue
   	var new_door = new_entry_points.pick_random()

   	# Position new_room so new_door aligns with existing_door
   	var offset = existing_door.global_position - new_door.global_position
   	new_room.global_position = offset

   	# Check overlap - skip if overlapping
   	if position_overlaps(new_room.global_position):
   		continue

   	map_root.add_child(new_room)
   	used_positions.append(new_room.global_position)
   	placed_rooms.append(new_room)

   	# Remove the door we connected from new room's open doors
   	new_entry_points.erase(new_door)

   	# Add remaining doors to open_doors for further expansion
   	open_doors += new_entry_points

   	# Disable walls at connecting doors on both rooms
   	disable_wall_at_door(start_room, existing_door)
   	disable_wall_at_door(new_room, new_door)

   	# Update start_room to the new_room for next iteration
   	start_room = new_room 

1 Like

It would be helpful if you could go into more detail. What exactly isn’t working? Are there any errors or warnings?

Also, please edit your post and add ```gdscript before and ``` after your code. This will property format it and make it much easier to read.

Sorry about that, the main issue I am having is that when I walking past a certain checkpoint to trigger map generation, one of two things will happen:

  1. The Map wont generate for some reason, either that or its really delayed
  2. The map generates 1 single room and will over lap the spawn room

This happens when the map generation is triggered after passing the marker 2D in the ‘lobby room’


This error appears multiple times:
"game:tscn::GDsciprt_wrm1d:95 @ generate_dungeon(): Cant change state while flushing queries. Use call_deferred() or set_deferred() to change monitoring state instead

I have no idea what this error means, what could be causing this or how this is happening.

It’s saying that on line 95 of your code, you are calling a function that it can’t do when you call it. It’s asking you to defer the call until the end of the frame when it can be handled.

What’s on line 95 of your code?

Likely this is due to when the dungeon_requested signal is being fired, and you could also solve this by moving this signal emission outside of _physics_process() or _process().

1 Like

Line 95 is

   	map_root.add_child(new_room)

I have no idea why its happening, I’ll probably have to restart from scratch thanks for your help.

Instead of starting from scratch, try this:

map_root.add_child.call_deferred(new_room)