Skip to main content
Coding Quests
The Scroll Library
Visual Explainers

How Procedural Dungeon Generation Works: Layout Before Decoration

October 31, 202610 min read
Part 1 of 8: Procedural dungeons, explained

How a dungeon generator places rooms, joins them, builds walls, picks the boss room and proves the result is playable. Every part uses the same seeded 22 by 22 dungeon.

  1. 1.How dungeon generation works
  2. 2.Stopping rooms overlapping
  3. 3.Making every room reachable
  4. 4.Joining premade rooms at their doors
  5. 5.Walls around the floor
  6. 6.Where the boss room goes
  7. 7.Seeds: the same dungeon on purpose
  8. 8.Testing that a level is playable

You press New Run, and a floor you have never seen appears. Rooms of different sizes, corridors between them, the boss somewhere far from the start, and crates and torches everywhere. Nobody drew it. A few hundred lines of code built it in a few milliseconds, and the next run will build another.

That is procedural generation: the game makes content from rules and random numbers while it runs, instead of you placing every wall. It looks like magic until you see the trick. The trick is that a good generator never does everything at once.

A dungeon is a grid of numbers first

Before there are tiles, sprites or 3D models, there is a grid. The example in this series is 22 cells wide and 22 cells tall, and every cell holds one number: 0 for solid rock, 1 for floor, 2 for wall.

Every pass reads that grid and writes to it. Only at the very end do you draw it, with a TileMapLayer in a 2D Godot game or a GridMap in 3D. Keeping the layout as plain data is what makes the rest of this possible. You can check it, repeat it from a seed, and test thousands of them without opening a scene.

The generator in this series follows the rules of the 3D dungeon course on CodingQuests. Rooms are 3 to 6 cells wide, with at least one cell of rock between them. Each new room gets an L-shaped corridor back to the room before it. Seed 214 is the dungeon every part of the series uses, so you can compare them.

The passes, one at a time

One dungeon, built in passes

Interactive, engine-accurate
5 / 5

Pass 4, decoration. 6 props placed, then the same check runs again: every room is still reachable.

012345678S

Rock is dark, floor is pale, and walls are the brown blocks. Rooms are numbered in the order they were placed, so room 0 is where the player starts. On the last pass, hatched floor is floor the player can no longer reach. Try Props anywhere on seed 214 to see the decoration pass break a level that was fine a moment earlier.

This runs the GDScript from this article, ported to the browser. The same seed gives the same result in Godot 4.7.2.

Pass 1: rooms

The generator rolls a random rectangle: a width and height from 3 to 6, and a position on the grid. Then it asks one question. Does this rectangle, grown by one cell on every side, touch a room that is already there? If it does, it is thrown away. If not, it is carved into floor.

It repeats that up to 66 times, or until it has 11 rooms. On seed 214 it keeps 9 rooms and throws away 57 tries. Most tries fail, and that is normal on a small map. How to stop generated rooms overlapping walks through every one of those tries.

Pass 2: corridors

Each room is joined to the room placed before it, with a corridor that runs sideways first and then up or down. Joining every room to the previous one forms a chain, and a chain cannot leave a room out. On seed 214 that is 8 corridors.

There are other ways to join rooms, and some of them strand rooms on most seeds. How to guarantee every dungeon room is reachable shows which, and how to catch it.

Pass 3: walls

Now the floor exists, the walls can be worked out from it. Any rock cell that touches a floor cell, including at a corner, becomes a wall. On seed 214 that turns 179 rock cells into walls around 190 floor cells. How to generate walls around dungeon floors covers the neighbour test and why corners matter.

Pass 4: check the level, then choose special rooms

A flood fill starts at the entrance and spreads to every floor cell it can walk to, counting steps as it goes. It answers two questions at once. Did it reach every room? And which room is the longest walk from the start? That room gets the boss.

On seed 214 every room is reachable, and room 7 is the longest walk at 43 steps. Where should the boss room go? explains why "longest walk" and "farthest away" can pick different rooms.

Pass 5: decoration, then check again

Last come the props. In the demo, every room except the entrance gets up to two crates on random cells, and crates block movement. Then the same check from pass 4 runs again on the finished level.

Why layout comes before decoration

You could place crates while you carve rooms. Three things go wrong when you do.

Every pass is easier to get right when it has one job. The room pass only has to keep rooms apart. The corridor pass only has to join them. When a level comes out wrong, you know which pass to look at.

Decoration is where good levels quietly break. On seed 214 the demo drops 7 crates. One of them lands on the doorway of room 6, the only way into rooms 7 and 8. The layout was fine. The decorated level is not. Across seeds 1 to 500, crates placed anywhere break 70 levels, about 1 in 7. Keeping crates off doorway cells brings that to zero, apart from 11 seeds that failed for a different reason: too few rooms. Try Props anywhere in the demo to see it happen.

Decoration should not move the walls. Decoration is the pass you will change most often. If it draws its random numbers from the same stream as the layout, adding one torch reshuffles every room after it. Give decoration its own random stream, and you can restyle a seed without changing its map. Seeds explained shows the difference on seed 214.

Other ways to build the layout

Rooms and corridors are one family of generator. The others change pass 1 and 2, and keep the rest.

  • Binary space partitioning, or BSP. Split the map in two, then split each half, until the pieces are room-sized. Put one room in each piece and join sister pieces. Rooms can never overlap, because each one has its own slice of the map. RogueBasin has a step-by-step BSP walkthrough.
  • Cellular automata caves. Fill the map with random rock, then smooth it a few times so lone cells disappear. You get organic caves, plus pockets that a flood fill has to find and remove. See RogueBasin's cellular automata method.
  • Premade rooms joined at doors. You design each room by hand, and the generator only decides which rooms go where. That swaps "rectangles that do not overlap" for "doors that line up". How to connect doors between premade rooms covers it.
  • A grid of room slots. Spelunky lays its levels out as a 4 by 4 grid of rooms, then walks a guaranteed path through them first. Darius Kazemi's Spelunky Generator Lessons lets you step through it.

Bob Nystrom's Rooms and Mazes is a longer tour of the rooms-and-corridors family, with mazes filling the space between rooms.

Where generators usually go wrong

  • Rooms overlap or merge. A missing gap check lets rooms share walls and fuse into blobs.
  • A room cannot be reached. The corridor pass joined rooms in a way that left an island.
  • The boss is next door. "Farthest room" was measured in a straight line through rock.
  • Decoration blocks the way. A prop in a one-cell doorway cuts off everything behind it.
  • A seed does not repeat. Something in the generator used a random number that was not from the seeded stream.
  • The generator stalls. A retry loop with no limit, on a map too small for the rooms it wants.

Each of those has its own part in this series, and the last part, how to test whether a generated level is playable, turns the list into checks that run on every seed.

In Godot: data first, nodes last

The whole generator is a plain script that extends RefCounted, so it needs no scene and no nodes. RefCounted is Godot's base class for objects that free themselves when nothing uses them any more. generate() runs the layout passes in order:

GDScript
func generate(seed_value: int) -> void:
set_seed(seed_value)
clear()
place_rooms()
connect_chain()

The room pass is the clearest example of a pass with one job. Rect2i is Godot's rectangle on a grid of whole numbers. grow(1) makes a copy one cell bigger on every side, and intersects() reports a real overlap:

GDScript
func place_rooms() -> void:
for _attempt in max_rooms * 6:
if rooms.size() >= max_rooms:
break
var w := rng.randi_range(room_min, room_max)
var h := rng.randi_range(room_min, room_max)
var x := rng.randi_range(1, grid_width - w - 1)
var y := rng.randi_range(1, grid_height - h - 1)
var rect := Rect2i(x, y, w, h)
if overlaps_any(rect):
continue # rejected, roll a new rectangle
rooms.append(rect)
carve_room(rect)
​
​
func overlaps_any(rect: Rect2i) -> bool:
for other in rooms:
if rect.grow(padding).intersects(other):
return true
return false

Drawing happens once, at the end. This script goes on a Node2D with a TileMapLayer child called Tiles. A TileMapLayer is the node that paints a grid of tiles from a tileset:

GDScript
func _ready() -> void:
layout = DungeonLayout.new()
layout.generate(dungeon_seed)
layout.build_walls()
DrawTiles.new().paint_tiles(layout, $Tiles, tile_source_id)

paint_tiles() is a double loop that calls set_cell() with a floor tile for every 1 and a wall tile for every 2:

GDScript
func paint_tiles(layout: Object, layer: TileMapLayer, source_id: int) -> void:
layer.clear()
for y in layout.grid_height:
for x in layout.grid_width:
var cell := Vector2i(x, y)
var kind: int = layout.grid[layout.index(cell)]
if kind == layout.FLOOR:
layer.set_cell(cell, source_id, FLOOR_TILE)
elif kind == layout.WALL:
layer.set_cell(cell, source_id, WALL_TILE)

For a 3D game, swap the TileMapLayer for a GridMap and call set_cell_item(Vector3i(x, 0, y), item) instead. The layout code does not change at all.

The complete script, with every pass in this series, is dungeon_layout.gd.

Build it next

Procedural Dungeon & Run

A quest on CodingQuests: 12 interactive lessons that build one part of a game in Godot, in your browser, checked as you go.

Procedural Dungeon & Run builds this same layout in 3D, on a GridMap. Lesson 2 places rooms with the one-cell gap and chains the corridors. Lesson 4 puts wall pieces on every edge that faces rock. Lesson 9 picks the boss room. Between them it adds a navigation mesh for enemies, themed decoration and the run loop.

It does not teach seeds, a reachability check or a playability test. Those are in this series.

  • Lesson 2: Rooms & CorridorsFree
  • Lesson 4: Walls Around the RoomsMembers
  • Lesson 9: Placing the CastMembers

Lessons 1 to 3 are free to open, no account needed. Guild Hall, the membership, opens the other 9.

FAQ

What is procedural dungeon generation?

It means the game builds its dungeon levels from rules and random numbers while it runs, instead of a designer placing every room by hand. Most generators work in passes: place rooms, connect them, add walls, check the level is playable, then decorate.

Which dungeon generation algorithm should I start with?

Start with random rooms and L-shaped corridors. Reject any room that comes within one cell of another, and join each new room to the previous one so nothing is cut off. It fits in about a hundred lines and teaches every pass the fancier algorithms also need.

Does Godot have a built-in dungeon generator?

No. Godot gives you the pieces: RandomNumberGenerator for seeded random numbers, Rect2i for room rectangles, and TileMapLayer or GridMap to draw the result. The generator itself is your own script.

Sources and testing

How this was tested. Every GDScript block on this page was run in Godot 4.7.2 (headless, on Windows) on 31 October 2026, including the TileMapLayer and GridMap drawing. The demo runs a browser port of the same script. An automated check compares the two on 40 seeds and fails on any difference, so seed 214 in the demo is seed 214 in Godot. The numbers quoted for seeds 1 to 500 come from that port. Only Godot 4.7.2 has been checked. Another version could, in principle, change how its random numbers are made, and with them the layouts.

procedural generationdungeonroguelikealgorithmsgodot

Stop reading. Start building.

Build a roguelike in Godot from the first lesson. The first quest, the player controller, is free from start to finish. Later quests in the same game add combat, enemies, rooms, loot and upgrades.

Free, and no card needed. Built by a real person, with new quests every month.

Get the next Godot build in your inbox

New quests, project breakdowns, and game-dev tips.

You'll get emails from CodingQuests with Godot tips, new tutorials, course updates, and occasional offers. Unsubscribe anytime. Privacy Policy

Written by Omar

We teach Godot 4 by making you build complete systems: inventories, save systems, action roguelike controllers, enemy AI. The scrolls are free. The quests are where it sticks.