Your generator carves a beautiful floor plan, and then you have to put walls around it. Do it by hand and you miss a corner. Do it with a quick rule and the corners of every room come out notched, or the walls overlap the floor, or one tile in a doorway is a wall when it should not be.
Walls are not something you generate separately. They follow from the floor. This is part 5 of a series that builds one dungeon generator, pass by pass.
Walls come from the floor
After the room and corridor passes, every cell is either floor or rock. A wall is just the rock that the player can see, the rock that touches floor. Rock deeper in never shows, so it never needs a tile.
That gives the whole rule: a rock cell becomes a wall when one of its neighbours is floor. The only question is which cells count as neighbours.
Four neighbours or eight?
The 4 neighbours of a cell are the ones that share a side with it: left, right, up and down. The 8 neighbours add the four diagonals, the cells that only touch at a corner.
Look at the outside corner of a rectangular room. The rock cell just beyond the corner touches the floor only diagonally. With 4 neighbours it has no floor neighbour, so it stays rock. Every room ends up with a notch at each outside corner, a gap in the wall line where you can see straight into the dark.
With 8 neighbours that corner cell sees the floor diagonally and becomes a wall. The wall line closes. Inspect the corner cells in the demo, then switch rules.
Walls from floor: the neighbour test
Interactive, engine-accurateCell (7, 6) is rock with 1 floor neighbour out of 8, so it becomes a wall.
The orange square is the cell being inspected, and the outlined cells are the floor neighbours it found. This rule turned 48 rock cells into walls. Look at the outside corners of each room. With 4 neighbours the corner cell only touches floor diagonally, so it stays rock and the wall line has a notch. Focus the map to use the arrow keys, and Space to paint.
This runs the GDScript from this article, ported to the browser. The same seed gives the same result in Godot 4.7.2.
On seed 214, the dungeon from this series, 4 neighbours makes 158 wall cells and 8 neighbours makes 179. The 21 extra cells are all corners.
When the notch matters
In a game with only straight moves, the notch is cosmetic. The player can never step diagonally, so they can never squeeze between two wall corners. But it still shows: if your rock is drawn as black void, every corner reads as a hole in the wall.
If your player or enemies can move diagonally, it is a real gap. Two walls that touch only at their corners leave a diagonal path between them, and pathfinding will happily take it. Use 8 neighbours for tile walls unless you have a reason not to.
Edge walls for 3D kits
Modular 3D kits, like KayKit's dungeon pieces, do not fill cells with walls. A wall piece is a thin slab that stands on the edge between two cells. For those, turn the rule around:
Visit every floor cell. For each of its 4 sides, if the cell on the other side is not floor, put a wall piece on that side, facing in.
Corners need no extra rule here. Two wall pieces meet at the corner of a floor cell on their own. Diagonals do not matter either, because a slab only ever sits on a side. On seed 214 this places 208 wall pieces. Pick Edge pieces in the demo to see them as thick lines on cell edges.
| Approach | Visits | Checks | Good for |
|---|---|---|---|
| Tile walls, 4 neighbours | Every rock cell | Left, right, up, down | Cheap, but notched corners |
| Tile walls, 8 neighbours | Every rock cell | All 8 around it | 2D tile maps, closed corners |
| Edge walls | Every floor cell | Its 4 sides | Modular 3D wall pieces |
Where it still goes wrong
Walls written into the floor. If your wall pass reads the grid while also writing walls into it, a new wall can make the next cell think it touches something solid. Write walls to rock cells only, and only test against floor, as below. Then the order cells are visited in does not matter.
Rooms at the edge of the map. A floor cell on the very edge has no room outside it for a wall. Keep a one-cell margin of rock around the map when you place rooms. The generator in this series rolls positions starting at 1 for that reason.
Two-tile-high walls. In top-down art with a visible wall face, a wall cell with floor directly below it needs the face tile, and every other wall cell needs the top. That is one more neighbour check on the cells your wall pass already found. Godot's terrain system can also pick the right tile for you: TileMapLayer.set_cells_terrain_connect() "might update neighboring tiles if needed to create correct terrain transitions", in the docs' words.
Doors. A doorway is floor, so nothing in this pass will put a wall there. Doors that close are a separate object placed on top, after walls. Lesson 11 of the 3D dungeon course closes the boss room's doorway with a gate piece this way.
In Godot: the wall pass
The tile version visits every cell, skips anything that is not rock, and turns rock into wall as soon as one neighbour is floor:
func build_walls(include_diagonals: bool = true) -> void: var offsets := NEIGHBORS_8 if include_diagonals else NEIGHBORS_4 for y in grid_height: for x in grid_width: var cell := Vector2i(x, y) if grid[index(cell)] != EMPTY: continue for offset in offsets: if is_floor(cell + offset): grid[index(cell)] = WALL breakNEIGHBORS_8 is a list of the 8 offsets around a cell, from Vector2i(-1, -1) to Vector2i(1, 1). is_floor() returns false for cells off the edge of the grid, so the border needs no special case. Because the test only asks "is this floor?", walls already written never affect later cells.
The edge version returns a list of wall pieces: the floor cell, and the direction the piece faces away from it:
func wall_edges() -> Array[Dictionary]: var edges: Array[Dictionary] = [] for y in grid_height: for x in grid_width: var cell := Vector2i(x, y) if not is_floor(cell): continue for dir in NEIGHBORS_4: if not is_floor(cell + dir): edges.append({"cell": cell, "facing": dir}) return edgesTo draw tile walls, give your tileset a floor tile and a wall tile and call set_cell() once per cell:
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)source_id is the id of the atlas in your TileSet, and FLOOR_TILE and WALL_TILE are the atlas coordinates of the two tiles. For edge walls in 3D, instance your wall scene once per entry in wall_edges(). Place it on the shared side of the cell, and turn it to face along facing.
The complete generator 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.
Lesson 4 of Procedural Dungeon & Run is the edge-wall pass in 3D. It checks the 4 sides of every floor cell and spawns a wall piece from the KayKit set, plus a box collider so the player cannot walk through. Lesson 5 fills the rock between rooms with solid stone, so nothing falls off the map.
Both are member lessons. The course builds edge walls only. Use the 8-neighbour tile rule above for a 2D tile map.
- Lesson 4: Walls Around the RoomsMembers
- Lesson 5: No More Falling OffMembers
Lessons 1 to 3 are free to open, no account needed. Guild Hall, the membership, opens the other 9.
FAQ
How do I add walls around a procedurally generated floor in Godot?
After the floor is carved, loop over every cell. Any empty cell with a floor neighbour becomes a wall, and you paint it with TileMapLayer.set_cell() using your wall tile. Check all 8 neighbours so room corners close.
Why do my generated walls have gaps at the corners?
Your wall check only looks at 4 neighbours. The cell outside a room's corner touches the floor only diagonally, so a 4-way check leaves it empty. Include the 4 diagonal neighbours in the check.
Should walls be tiles or separate wall pieces?
For 2D tile maps, make walls tiles in their own cells. For modular 3D kits, put wall pieces on the edges of floor cells instead. Both come from the same floor grid. Only the neighbour rule changes.
Sources and testing
- Godot docs: TileMapLayer (set_cell, set_cells_terrain_connect) and GridMap
- Wikipedia: Flood fill, on four-way and eight-way neighbours
- RogueBasin, Cellular Automata Method, which uses the same 8-neighbour count to shape caves
- Developer questions this answers, on the Godot Forum: How to autotile 2-tile high walls for a procedural/random level generator?, Procedural generation for a 2d roguelike, Help with tileset/autotile and walker generation
How this was tested. The GDScript on this page was run in Godot 4.7.2 (headless, on Windows) on 31 October 2026. Painting seed 214 into a real TileMapLayer gave 190 floor tiles and 179 wall tiles, the same counts as the layout. The demo runs a browser port of the same wall code, checked against Godot on 40 seeds. The two-tile-high face rule and set_cells_terrain_connect() are described, not tested here.

