Overview
These are a set of GDScripts that procedurally generate dungeons in the Godot editor and at runtime.
Features
-
You can configure the overall size of the dungeon, number of rooms, and the minimum/maximum width and height of both rooms and corridors.
-
Layout generation based on a seed.
-
The system first creates layout data for exterior and interior, then uses basic tiling patterns of the dungeon to place decorations and objects.
-
For each pattern you can visually configure minimum/maximum number of occurrences, spawn weights, and adjacency directions.
-
For each layer and pattern you can configure whether a cell is considered occupied, pattern coverage, and whether it is allowed to overwrite existing tiles.
By setting up multiple layers you can build multi-layered decoration.
Usage
There are many configuration options, but downloading the sample project once will help you understand the workflow smoothly.
1. Download the sample project
-
Download the required files from the repository.
https://github.com/ggg-shirokishi/procedural-layout-tools -
Place the
addonsandscriptsdirectories directly under the project root.
2. Add the layout generator node to a scene
-
Add a
Node2Dto any scene and attach the layout scriptroom_rayout_generatorfrom thescriptsdirectory. -
Configure the parameters of
room_rayout_generatorin the inspector.- Map size:
width,height - Number of rooms:
room_count - Room size range:
room_w_min / room_w_max,room_h_min / room_h_max - Corridor width and related parameters:
corridor_widthcorridor_width_min / maxcorridor_width_randomize_each_corridor- When
corridor_width_randomize_each_corridoris enabled, each corridor’s width is randomized betweencorridor_width_min / max.
- When
- Diagonal connectivity setting:
connectivity_allow_diagonal- When
connectivity_allow_diagonalis enabled, rooms can be connected diagonally.
- When
- Seed:
seed
- Map size:
-
If you enable
editor_auto_generate/editor_live_update, any parameter changes will be immediately reflected in the editor.
3. Setting up TileMapLayer and TerrainAtlasPatternPlacer
-
Add a
TileMapLayerand aNode2D(for running the placement script) to the scene, and attachterrain_atlas_pattern_placerfrom thescriptsdirectory to theNode2D.- The target TileMap layer and the placement
Node2Dhave a 1:1 relationship, and you can add as many pairs as needed. - There is no lower or upper limit on the number of layers, but in most cases you will want at least two
TileMapLayer+terrain_atlas_pattern_placerpairs: one for exterior (Terrain tile placement) and one for interior.
(In the sample project these areTerrainTileLayer / TerrainTilePlacerandInteriorBaseLayer / InteriorBasePlacer.)
- The target TileMap layer and the placement
-
Assign a terrain
TileSetto theTileMapLayerand register the Terrains and Patterns.- This system reads Terrains and patterns from the TileSet assigned to each TileMapLayer.
Please set up the TileSet on the TileMapLayer side. - For an explanation of Terrains, see:
https://docs.godotengine.org/ja/4.x/tutorials/2d/using_tilesets.html#creating-terrain-sets-autotiling
- This system reads Terrains and patterns from the TileSet assigned to each TileMapLayer.
-
On the node with the
TerrainAtlasPatternPlacerscript attached, configure the following items.
Main settings of TerrainAtlasPatternPlacer
layout_node: Path to theroom_rayout_generatornode added in the previous section.target_layer_path: Path to theTileMapLayeroperated by this placer.target_kind: Cell kind handled on this layer (WALLS/FLOORS)
WALLS: Exterior / non-walkable terrain (usually Terrain tiles)
FLOORS: Interiorplacement_type: Whether to paint with Terrain or stamp Patterns (TERRAIN/PATTERN).clear_before_place: Whether to clear the layer each time before placement.register_used_cells_to_layout: Whether to register used cells into the layout’sused_cells_mask.
When this option is enabled, and you later useonly_place_on_unoccupiedon layers/patterns with largerexecution_order, you can prevent them from placing over already used cells.only_place_on_unoccupied: Restrict placement to unoccupied cells only.auto_update_on_layout_signal: Whether to automatically re-place when layout update signals are received.pattern_coverage_ratio: Overall density of pattern placement.execution_order: Processing order when there are multipleTerrainAtlasPatternPlacernodes.
Nodes with smaller values run first. Used in combination withonly_place_on_unoccupiedandregister_used_cells_to_layoutto control overlapping.
4. Detailed pattern settings (editor-extended inspector)
When you link a TileMapLayer to a TerrainAtlasPatternPlacer, an additional panel listing all patterns in the TileSet is added to the inspector.
You can configure the following items for each pattern.
-
pattern_required_cells: Required contact cells
(Cells inside the pattern that must touch an edge)
Used in combination withpattern_adjacent_dirs.
You can configure this by checking cells under the thumbnail. -
pattern_weights: Spawn weights. -
pattern_min_counts,pattern_max_counts: Minimum/maximum number of placements (unlimited by default). -
pattern_adjacent_dirs: Adjacency directions
(Which directions, among up/down/left/right, the required cells must be adjacent to.)Example:
If you set the top two cells aspattern_required_cellsand setpattern_adjacent_dirsto “Up”, the pattern will only be placed on terrain where those top two cells touch from above. -
pattern_register_used_cells_override: Override for registering cells as occupied
Allows you to control whether this pattern registers its cells as occupied.- Inherit global setting (same as
TerrainAtlasPatternPlacer) - Force On
- Force Off
- Inherit global setting (same as
-
pattern_only_place_on_unoccupied_override: Override for “place only on unoccupied cells”- Inherit global setting (same as
TerrainAtlasPatternPlacer) - Force On
- Force Off
- Inherit global setting (same as
After configuring these roughly, press run_on_editor_button to generate the layout.
Runtime regeneration settings (optional)
- If you want to regenerate the dungeon during gameplay using a key input, attach a script equivalent to
RuntimeLayoutKeyboardControllerto aNode.
Main settings
layout_node_path: Layout generator node.input_action_name: InputMap action name used for regeneration (for example,regen_dungeon).randomize_layout_size/randomize_room_count/randomize_room_size/randomize_corridor_width: Whether to randomize each parameter on every regeneration.move_target_node_path: Node such as the player to move after regeneration.move_target_tilemap_layer_path:TileMapLayerused for floor detection.move_after_generation: Whether to move the target to a free cell after regeneration.zoom_camera_path: Camera whoselimitshould be adjusted to the layout.
After the game starts, pressing the specified action key will:
- Change the seed.
- Regenerate the layout.
- Optionally move the player and adjust the camera.
Multi-layer decoration using multiple layers
By letting multiple TerrainAtlasPatternPlacer nodes share the same layout node and controlling their execution order with execution_order, you can create multi-layer decoration such as the following:
- First layer: Exterior (base wall/floor tiles)
- Second layer: Interior (patterns such as pillars, windows, beams)
- Third layer: Objects (furniture, rubble, decorations)
For each layer you can toggle:
register_used_cells_to_layoutonly_place_on_unoccupied
and use the per-pattern overrides to:
- “Overlay on top without destroying the base”
- “Overwrite only part of the base”
Tips and notes
-
TileMap layer sizes and TileSet sizes must be unified.
-
If you want to place game objects randomly:
You can add a scene collection to the TileSet, place objects on the TileMap, then bake them back into patterns for placement.
(You can see these settings in the sample project’sGameObjectLayerandInteriorBaseLayer.)
Details of each script
room_rayout_generator.gd
Usage overview
A layout-only generator attached to Node2D.
It never writes to TileMap.
The generation result is stored in the following properties:
grid: 2D array ofint
CELL_WALL = 0,CELL_FLOOR = 1,CELL_DOOR = 2rooms:Array[Rect2i](room rectangles)centers:Array[Vector2i](room centers)room_id_grid: Room ID for each cell (identifies rooms).corridor_id_grid: Corridor ID for each cell.used_cells_mask: Mask of “used cells” registered externally (from placers).
Public API for layout generation
generate_now()
Generates the layout synchronously and emitsgeneration_finished(success: bool)when it completes.generate_async() -> Signal
Generates the layout asynchronously. It checks_is_generatingto see if it is busy, performs processing usingawait, and finally returns the result via the samegeneration_finishedsignal.
Signals
layout_updated(grid, rooms, centers)
Emitted when the grid and room information are updated.generation_finished(success)
Notifies whether the whole generation succeeded.
Nodes such as TerrainAtlasPatternPlacer refer to the generated grid and use
register_used_cells() / get_free_cells()
to share “free cells / used cells” over the layout.
In the editor, editor_auto_generate / editor_live_update / editor_generate_button control:
- Automatic generation when the scene is loaded.
- Automatic regeneration when parameters are changed in the inspector.
Main variables and properties
Output and state
signal layout_updated(grid: Array, rooms: Array, centers: Array)
Signal for notifying the current layout information.signal generation_finished(success: bool)
Signal for notifying whether generation finished successfully.const CELL_WALL: int = 0
Value representing a “wall” in the grid.const CELL_FLOOR: int = 1
Value representing a “floor” in the grid.const CELL_DOOR: int = 2
Value representing a “planned door cell” in the grid.var rng: RandomNumberGenerator
RNG instance used for random numbers, seeded fromseedand_seed_internal.var _is_generating: bool
Flag indicating whether asynchronous generation is in progress. It becomestrueduringgenerate_asyncandfalsewhen completed.var grid: Array
2D arraygrid[y][x]. Values are one of theCELL_*constants above.var rooms: Array[Rect2i]
Array of successfully generated room rectangles.var centers: Array[Vector2i]
Array of center cell coordinates for each room.var room_id_grid: Array
room_id_grid[y][x]stores which room ID the cell belongs to (or-1if not a room).var corridor_id_grid: Array
corridor_id_grid[y][x]stores corridor IDs (or-1if not a corridor).var _next_corridor_id: int
Internal counter used to assign corridor IDs.var used_cells_mask: Array
2D array of boolsused_cells_mask[y][x]indicating “whether this cell has been registered as used from the TilePlacer side”.
register_used_cells(cells: Array[Vector2i])sets these totrue.
get_free_cells(kind: int)returns cells whose mask isfalseas free cells.
Map size and room parameters
@export var width: int = 80
Layout width in cells._set_width()prevents values smaller than8, and regenerates in the editor when changed.@export var height: int = 60
Layout height in cells._set_height()keeps it at least8.@export var cell_padding: int = 1
Margin from the outer border. Rooms are not placed within this many cells from the outer frame.@export var room_count: int = 18
Target number of rooms to try to generate. The actual number may be smaller due to collisions.@export var room_w_min: int = 5/room_w_max: int = 14
Minimum/maximum room width in cells. Setter maintainsroom_w_min <= room_w_max.@export var room_h_min: int = 4/room_h_max: int = 12
Minimum/maximum room height in cells. Also kept consistent by the setter.
Corridor parameters
@export var corridor_width: int = 1
Base corridor width. Used as a fixed width when random width is disabled.@export var corridor_width_min: int = 0
Minimum corridor width when randomizing.@export var corridor_width_max: int = 1
Maximum corridor width when randomizing.@export var corridor_width_randomize_each_corridor: bool = true
Whentrue, each corridor (segment) randomly chooses its width in the rangecorridor_width_mintocorridor_width_max.@export var corridor_use_diagonal_path: bool = false
Whentrue, corridor digging between room centers allows diagonal paths (the path zigzags diagonally).@export var connectivity_allow_diagonal: bool = false
Whentrue, the connectivity check (whether all rooms are connected) treats diagonal (8-direction) adjacency as connected.
Randomness, retries, and async
var _seed_internal: int = 123456
Internal seed actually passed to the RNG.@export var seed: int = 123456
Public seed for layout generation. The setter updates_seed_internaland, ifeditor_live_updateistruein the editor, regenerates immediately.@export var max_retry: int = 25
Maximum number of retries when room or corridor generation fails, changing the seed each time.@export var async_yield_rows: int = 6
In asynchronous generation, this controls how many rows are processed before yielding (for example, viaawait process_frame).
Larger values dig more per batch but block the main thread longer.
Logging and editor-specific settings
@export var log_enabled: bool = true
Whentrue,_log()outputs logs.@export var log_verbosity: int = 1
Log verbosity level. Larger values output more detailed logs.@export var editor_auto_generate: bool = true
Flag for automatically generating the layout when the scene is loaded.@export var editor_live_update: bool = true
Whether to automatically regenerate the layout in the editor when parameters are changed.@export var editor_generate_button: bool = false
Inspector-side one-shot “generate now” trigger flag.
When set totrue,_generate_editor_safe()is called once and the flag is immediately set back tofalse.
terrain_atlas_pattern_placer.gd (TerrainAtlasPatternPlacer)
Usage overview
A Node2D script with class_name TerrainAtlasPatternPlacer.
Its role is:
“Refer to the grid of RoomLayoutGenerator and paint Terrain or stamp Patterns onto a specified TileMapLayer.”
Main assumptions:
layout_nodereferences a node such asroom_rayout_generator.gdthat owns agridand emitslayout_updated/generation_finished.target_layer_pathspecifies theTileMapLayerto place tiles on.
Target cell kinds:
- When
target_kind = WALLS, targetCELL_WALLcells. - When
target_kind = FLOORS, targetCELL_FLOORandCELL_DOORcells.
Placement modes:
- When
placement_type = TERRAIN,
set_cells_terrain_connect()is used to paint with Terrain. - When
placement_type = PATTERN,
TileSetTileMapPatternobjects are used to stamp patterns.
Other behavior:
- If
auto_update_on_layout_signalistrue, the node automatically re-places tiles when it receiveslayout_updated/generation_finishedfromlayout_node. register_used_cells_to_layoutcontrols whether to register used cells tolayout_node.register_used_cells(cells)and reflect them inused_cells_mask.only_place_on_unoccupiedrestricts placement to cells that are unused both on the layout and on theTileMapLayer.
Additionally,pattern_*_overrideallows this behavior to be overridden per pattern.- When multiple placers share the same layout,
execution_ordercontrols the order in which they are run (smaller values run first).
Signals
signal placement_finished(success: bool)
Emitted when the placement process has completed.
Main variables and properties
Basics and enums
signal placement_finished(success: bool)
Signal notifying whether placement has finished successfully.const CELL_WALL: int = 0/CELL_FLOOR: int = 1/CELL_DOOR: int = 2
Layout grid value definitions, corresponding toRoomLayoutGenerator.enum TargetKind { WALLS, FLOORS }
Which cell kind to target.enum PlacementType { TERRAIN, PATTERN }
Mode for Terrain painting or Pattern stamping.
Layout reference and target layer
@export var layout_node: NodePath
Path to the layout generation node (RoomLayoutGenerator, etc.).
The setter_set_layout_nodecaches the reference in_layout_ref.var _layout_ref: Node
Actual layout node reference.var _grid: Array
Internal cache of the current layoutgrid.@export var target_layer_path: NodePath
Path to the targetTileMapLayer.var _target_layer: TileMapLayer
Reference to the actual TileMap layer.
Execution control and linkage
@export var clear_before_place: bool = true
Whether to callTileMapLayer.clear()before placement.@export var register_used_cells_to_layout: bool = true
Whether to pass the list of cells used for placement tolayout_node.register_used_cells(cells).@export var only_place_on_unoccupied: bool = false
Whentrue, only cells that are free both in the layout and on theTileMapLayerare targeted.
This combineslayout.get_free_cells(kind)and TileMap cell checks.@export var run_on_editor_button: bool = false
Editor-side button trigger flag. When set totrue,_set_run_on_editor_button()is called, which:- Executes
layout.generate_now(). - Then runs placement equivalent to
place_now().
After completion, the flag is reset tofalse.
- Executes
@export var auto_update_on_layout_signal: bool = true
Whentrue, automatically reacts tolayout_updated/generation_finishedsignals fromlayout_node.@export var auto_update_in_editor_only: bool = true
Whentrue, auto-update works only in the editor and not during gameplay.@export var auto_update_debounce_frames: int = 1
Number of frames to debounce before batch updating on auto-update.
Even when set to0, at least 1 frame is waited.@export var log_enabled: bool = true
Whether to output logs.@export var log_verbosity: int = 1
Log verbosity level.@export var execution_order: int = 0
Execution order among multiple placers that share the samelayout_node.
Lower values run first; if the values are equal, the order falls back to ascendinginstance_id.var _pending_auto_update_local: boolvar _last_used_cells: Array[Vector2i]var _last_pattern_force_register: bool
Internal variables holding pending state and the latest placement information.
Placement mode and Terrain settings
@export var target_kind: TargetKind = TargetKind.WALLS
Specifies whether to target walls or floors in the layout.@export var placement_type: PlacementType = PlacementType.TERRAIN
Chooses between Terrain mode and Pattern mode.@export var terrain_set_index: int = 0
Index of the Terrain set to use.@export var terrain_index: int = 0
Index of the Terrain within the set.
Pattern-related settings
@export var pattern_indices: PackedInt32Array = PackedInt32Array()
List ofTileMapPatternindices to use. If empty, all available patterns are targeted.@export var pattern_avoid_overlap: bool = true
Whentrue, patterns are placed so that they do not overlap each other.@export var pattern_coverage_ratio: float = 0.1
Target ratio (0.0–1.0) for how much of the target cells should be covered by patterns.@export var pattern_weights: Dictionary = {}
Spawn weight per pattern.- Key:
pattern_index - Value:
floatweight
- Key:
@export var pattern_adjacent_dirs: Dictionary = {}
Direction bitmask per pattern.- Key:
pattern_index - Value:
intbitmask- Bits:
1 = U(up),2 = R,4 = D,8 = L
- Bits:
- Key:
@export var pattern_required_cells: Dictionary = {}
Definition of required contact cells per pattern.- Key:
pattern_index - Value:
Array[Vector2i]of internal pattern cell coordinates
- Key:
@export var pattern_min_counts: Dictionary = {}
Minimum placement count per pattern.@export var pattern_max_counts: Dictionary = {}
Maximum placement count per pattern.-1or missing means “no upper limit”.@export var pattern_register_used_cells_override: Dictionary = {}
Per-pattern override forregister_used_cells_to_layout.- Value:
0 = Inherit(inherit global setting) - Value:
1 = Force On(always register as used) - Value:
2 = Force Off(never register as used)
- Value:
@export var pattern_only_place_on_unoccupied_override: Dictionary = {}
Per-pattern override foronly_place_on_unoccupied.- Value:
0 = Inherit(inherit global setting) - Value:
1 = Force On(only place on unoccupied cells for this pattern) - Value:
2 = Force Off(allow this pattern on already used cells)
- Value:
terrain_pattern_placer_inspector.gd + plugin.gd / plugin.cfg
Usage overview
plugin.cfg and plugin.gd register this as a
“Terrain Pattern Tools” plugin in the Godot editor.
Contents of plugin.cfg:
name="Terrain Pattern Tools"description="Custom inspector for TerrainAtlasPatternPlacer (pattern weights / adjacency)."script="plugin.gd"
plugin.gd extends EditorPlugin and in _enter_tree():
- Creates
terrain_pattern_placer_inspector.gdviaScript.new(). - Registers the inspector extension via
add_inspector_plugin(_insp).
In _exit_tree(), it removes the inspector extension using remove_inspector_plugin(_insp).
When you enable this plugin in the Project Settings and select a node with TerrainAtlasPatternPlacer attached,
- The standard inspector gains an additional UI for per-pattern settings.
Contents editable via the added UI
For each TileMapPattern:
- Thumbnail showing the pattern’s tile appearance.
- Grid thumbnail showing the pattern’s internal cell layout.
- Spawn weight (
SpinBox). - Adjacency directions (
U/R/D/Lcheck boxes). - Required contact cells (buttons for internal pattern cells, toggled by clicking).
- Minimum count (
Min). - Maximum count (
Max,-1for unlimited). - Per-pattern override of
register_used_cells_to_layout(Reg). - Per-pattern override of
only_place_on_unoccupied(Unocc).
These directly modify the following properties on TerrainAtlasPatternPlacer:
pattern_weightspattern_adjacent_dirspattern_required_cellspattern_min_countspattern_max_countspattern_register_used_cells_overridepattern_only_place_on_unoccupied_override
class PatternPreviewControl extends Control
Control subclass dedicated to pattern preview rendering.
var tileset: TileSet
TileSetused for preview.var pattern: TileMapPattern
Pattern to preview.var preview_size: Vector2
Display size inside the scroll view.
Internally, it creates a TileMapLayer, stamps the pattern using set_cell(), and displays the appearance as-is in _draw().
Dictionary access helper functions
- Functions such as
_get_dict_safe(placer, "pattern_weights")retrieve eachDictionary, update it based on UI operations, and write it back via
placer.set("pattern_weights", d).
runtime_rayout_controller.gd (RuntimeLayoutKeyboardController)
Usage overview
Runtime controller attached to a Node.
Its role:
- When the specified input action is pressed:
- Randomly updates the
seedof the layout node. - Optionally randomizes layout parameters (width, height, room count, room size, corridor width).
- Triggers
layout_node.generate_now()to regenerate the layout.
- Randomly updates the
- After regeneration, it can:
- Move the node specified by
move_target_node_pathto one of the free cells. - Adjust the
limit_*properties of the camera specified byzoom_camera_pathto match the layout’s outer bounds.
- Move the node specified by
Assumptions
layout_node_pathshould point to aNode2Dwithroom_rayout_generator.gdattached.
Input monitoring
- In
_process(), it checksInput.is_action_just_pressed(input_action_name)and calls_regenerate_with_random_seed()when the action is pressed.
Movement and camera
- If
move_after_generationistrue, it calls_move_target_to_free_cell()after the layout update. - If
zoom_camera_pathis set,_update_camera_limits()recalculates
limit_left/right/top/bottom.
Main variables and properties
Basic settings
const CELL_WALL: int = 0/const CELL_FLOOR: int = 1
Grid value definitions (same asRoomLayoutGenerator).enum MoveTargetKind { MOVE_ON_FLOORS, MOVE_NEAR_WALLS }
How to choose the destination cell.MOVE_ON_FLOORS: Move onto a floor cell.MOVE_NEAR_WALLS: Prefer candidates such as floor cells near walls.
@export var layout_node_path: NodePath
Path to the layout node (RoomLayoutGenerator).@export var input_action_name: String = "dungeon_regen"
InputMapaction name used as a trigger for layout regeneration.@export var log_enabled: bool = true
Whentrue, outputs internal logs viaprint()/push_warning(), etc.var layout_node: Node = null
Actual layout node reference. Retrieved fromlayout_node_pathin_ready().var _rng: RandomNumberGenerator = RandomNumberGenerator.new()
RNG used for randomization on each regeneration.
Randomizing layout parameters
@export var randomize_layout_size: bool = false
Whentrue, randomizes layout width and height on each regeneration.@export var layout_width_range: Vector2i = Vector2i(80, 80)
Random range forwidth(x = min,y = max).@export var layout_height_range: Vector2i = Vector2i(60, 60)
Random range forheight.@export var randomize_room_count: bool = false
Whentrue, randomizesroom_counton each regeneration.@export var room_count_range: Vector2i = Vector2i(18, 18)
Random range forroom_count.@export var randomize_room_size: bool = false
Whentrue, randomizes room size ranges on each regeneration.@export var room_width_range: Vector2i = Vector2i(5, 14)
Base random range for room width. Internally, two values are drawn to decideminandmax.@export var room_height_range: Vector2i = Vector2i(4, 12)
Base random range for room height.@export var randomize_corridor_width: bool = false
Whentrue, randomizes corridor width on each regeneration.@export var corridor_width_range: Vector2i = Vector2i(0, 3)
Base random range for corridor width. Two values are drawn and used to decidemin/maxor a fixed width.
Movement target and camera
@export var move_target_node_path: NodePath
Path to the node to move after layout update (such as the player character).@export var move_target_tilemap_layer_path: NodePath
Path to theTileMapLayercorresponding to the layout.
Used to determine which cells are floor or wall and to obtain candidate destinations.@export var move_target_kind: MoveTargetKind = MoveTargetKind.MOVE_ON_FLOORS
Specifies how to choose the destination cell as described above.@export var move_after_generation: bool = true
Whentrue, calls_move_target_to_free_cell()after each layout regeneration.@export var cell_size: Vector2 = Vector2(16.0, 16.0)
Pixel size of one cell in the layout grid.
Used to convert grid coordinates to world coordinates.@export var zoom_camera_path: NodePath
Path to aCamera2D/ZoomCamera2Dwhoselimit_left/right/top/bottomare adjusted after regeneration.




