For line 136 (and 135) you’ve got the door index, but you need to get the room index for that door. A quick fix would be to divide c
and p
by 2
when indexing into door_tiles
, but that would only work because each room has exactly 2 doors.
I would suggest you start from the top.
I did some refactoring of the code out of necessity while testing your project. Allow me to share some changes I made.
Making the scene testable
You shouldn’t be trying to debug stuff like this via a tool script in-editor. As mentioned earlier, you can easily crash the editor itself, and some errors are suppressed.
The first thing I did was add the default Sun and Environment to the scene (you can do this by clicking on the triple-dot icon in the 3D view’s toolbar).
Next, I found a nice viewpoint above the dungeon, added a Camera3D, and used Ctrl+Alt+M
to align the camera to my current viewpoint.
My scene looks like this:
Next, I added an input function to the generator script to regenerate the dungeon when I hit Space:
func _unhandled_key_input(event: InputEvent) -> void:
if Engine.is_editor_hint():
return
if event.pressed:
match event.physical_keycode:
KEY_SPACE:
generate()
At this point I was able to run the scene with F5 and debug as normal.
Of course, it still works in-editor with the “Start” button.
Data Structure
The first thing I would do is reevaluate how you’re storing the room and door data. The current data seems a little clunky to use. Here is how I would have structured it:
var room_tiles: Array[PackedVector3Array] # room_tiles[room_id] -> list of covered tiles
var room_doors: Array[PackedInt32Array] # room_doors[room_id] -> door indices
var door_positions: PackedVector3Array # door_positions[door_id] -> door's position
var door_rooms: PackedInt32Array # door_rooms[door_id] -> door's room_id
(You weren’t using room_positions
anywhere so I removed it, feel free to keep it of course.)
But either way, the very next thing you should do is write some helper methods:
func add_room(tiles: PackedVector3Array) -> int:
var room_id := room_tiles.size()
room_tiles.append(tiles)
room_doors.append(PackedInt32Array())
return room_id
func add_door(room_id: int, door_position: Vector3) -> int:
assert(room_id >= 0 and room_id < room_tiles.size()) # validate room_id
var door_id := door_positions.size()
door_positions.append(door_position)
door_rooms.append(room_id)
room_doors[room_id].append(door_id)
return door_id
And update the place_predefined_room()
function to use them:
var room : PackedVector3Array = []
var dt: PackedVector3Array = []
for r in height:
for door_id_to in width:
var pos : Vector3i = start_pos + Vector3i(door_id_to,0,r)
grid_map.set_cell_item(pos, 0)
room.append(pos)
var room_id := add_room(room)
for door_position in new_room["doors"]:
var door_pos : Vector3i = start_pos + Vector3i(door_position.x, 0, door_position.z)
grid_map.set_cell_item(door_pos, 2)
add_door(room_id, door_pos)
# Not needed anymore:
#room_positions.append(room_pos)
#room_tiles.append(room)
#room_doors.append(dt)
Cleanup
Next we need to look at each piece of code accessing these arrays and see if anything needs to be updated.
connection_calculations()
The first thing I see is in connection_calculations()
. You don’t need to use AStar2D.get_available_point_id()
to create ids. You already have an id: the door’s index. Let’s refactor that code to just use door indices:
var door_positions_2d : PackedVector2Array = []
var del_graph : AStar2D = AStar2D.new()
var mst_graph : AStar2D = AStar2D.new()
for door_id: int in door_positions.size():
var p := door_positions[door_id]
door_positions_2d.append(Vector2(p.x,p.z))
del_graph.add_point(door_id, Vector2(p.x,p.z))
mst_graph.add_point(door_id, Vector2(p.x,p.z))
(I renamed rvp2
to door_positions_2d
as well.)
create_hallways()
Now let’s jump down to create_hallways()
, since that’s where the real trouble is.
Lines 135-146 are a real mess. There’s a lot of extra work being done to calculate the closest tile inside the room to the door. But… why not just use the door position?
Here’s how I rewrote that section:
var hallways : Array[PackedVector3Array] = []
for door_id_from in hallway_graph.get_point_ids():
for door_id_to in hallway_graph.get_point_connections(door_id_from):
if door_id_to <= door_id_from:
continue
var tile_from : Vector3 = door_positions[door_id_from]
var tile_to : Vector3 = door_positions[door_id_to]
var hallway : PackedVector3Array = [tile_from,tile_to]
hallways.append(hallway)
Fixing the same-room problem
Right now, since each door is a node in the graph, sometimes doors belonging to the same room will be connected.
In fact, because of the size of the rooms and the nature of Delaunay triangulation, this will always happen.
It looks like this:
We can fix this very easily by adding the following check to create_hallways()
:
var hallways : Array[PackedVector3Array] = []
for door_id_from in hallway_graph.get_point_ids():
for door_id_to in hallway_graph.get_point_connections(door_id_from):
if door_id_to <= door_id_from:
continue
+ # Prevent connecting a room to itself
+ if door_rooms[door_id_from] == door_rooms[door_id_to]:
+ continue
var tile_from : Vector3 = door_positions[door_id_from]
var tile_to : Vector3 = door_positions[door_id_to]
var hallway : PackedVector3Array = [tile_from,tile_to]
hallways.append(hallway)
Final thoughts
With these fixes you should have a working dungeon generator.
Here is what it looks like in action:
You should be able to get everything else working from this point.
Good luck!
P.S. I made a pull request on your repo with the changes I made, since I’m sure you’ll want to see the full diff.