Random Dungeon Generator

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

  1. Download the required files from the repository.
    https://github.com/ggg-shirokishi/procedural-layout-tools

  2. Place the addons and scripts directories directly under the project root.

2. Add the layout generator node to a scene

  1. Add a Node2D to any scene and attach the layout script room_rayout_generator from the scripts directory.

  2. Configure the parameters of room_rayout_generator in 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_width
      • corridor_width_min / max
      • corridor_width_randomize_each_corridor
        • When corridor_width_randomize_each_corridor is enabled, each corridor’s width is randomized between corridor_width_min / max.
    • Diagonal connectivity setting: connectivity_allow_diagonal
      • When connectivity_allow_diagonal is enabled, rooms can be connected diagonally.
    • Seed: seed
  3. 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

  1. Add a TileMapLayer and a Node2D (for running the placement script) to the scene, and attach terrain_atlas_pattern_placer from the scripts directory to the Node2D.

    • The target TileMap layer and the placement Node2D have 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_placer pairs: one for exterior (Terrain tile placement) and one for interior.
      (In the sample project these are TerrainTileLayer / TerrainTilePlacer and InteriorBaseLayer / InteriorBasePlacer.)
  2. Assign a terrain TileSet to the TileMapLayer and register the Terrains and Patterns.

  3. On the node with the TerrainAtlasPatternPlacer script attached, configure the following items.

Main settings of TerrainAtlasPatternPlacer

  • layout_node: Path to the room_rayout_generator node added in the previous section.
  • target_layer_path: Path to the TileMapLayer operated by this placer.
  • target_kind: Cell kind handled on this layer (WALLS / FLOORS)
    WALLS: Exterior / non-walkable terrain (usually Terrain tiles)
    FLOORS: Interior
  • placement_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’s used_cells_mask.
    When this option is enabled, and you later use only_place_on_unoccupied on layers/patterns with larger execution_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 multiple TerrainAtlasPatternPlacer nodes.
    Nodes with smaller values run first. Used in combination with only_place_on_unoccupied and register_used_cells_to_layout to 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 with pattern_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 as pattern_required_cells and set pattern_adjacent_dirs to “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
  • pattern_only_place_on_unoccupied_override: Override for “place only on unoccupied cells”

    • Inherit global setting (same as TerrainAtlasPatternPlacer)
    • Force On
    • Force Off

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 RuntimeLayoutKeyboardController to a Node.

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: TileMapLayer used for floor detection.
  • move_after_generation: Whether to move the target to a free cell after regeneration.
  • zoom_camera_path: Camera whose limit should be adjusted to the layout.

After the game starts, pressing the specified action key will:

  1. Change the seed.
  2. Regenerate the layout.
  3. 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_layout
  • only_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’s GameObjectLayer and InteriorBaseLayer.)

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 of int
    CELL_WALL = 0, CELL_FLOOR = 1, CELL_DOOR = 2
  • rooms: 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 emits generation_finished(success: bool) when it completes.
  • generate_async() -> Signal
    Generates the layout asynchronously. It checks _is_generating to see if it is busy, performs processing using await, and finally returns the result via the same generation_finished signal.

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 from seed and _seed_internal.
  • var _is_generating: bool
    Flag indicating whether asynchronous generation is in progress. It becomes true during generate_async and false when completed.
  • var grid: Array
    2D array grid[y][x]. Values are one of the CELL_* 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 -1 if not a room).
  • var corridor_id_grid: Array
    corridor_id_grid[y][x] stores corridor IDs (or -1 if not a corridor).
  • var _next_corridor_id: int
    Internal counter used to assign corridor IDs.
  • var used_cells_mask: Array
    2D array of bools used_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 to true.
    get_free_cells(kind: int) returns cells whose mask is false as free cells.

Map size and room parameters

  • @export var width: int = 80
    Layout width in cells. _set_width() prevents values smaller than 8, and regenerates in the editor when changed.
  • @export var height: int = 60
    Layout height in cells. _set_height() keeps it at least 8.
  • @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 maintains room_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
    When true, each corridor (segment) randomly chooses its width in the range corridor_width_min to corridor_width_max.
  • @export var corridor_use_diagonal_path: bool = false
    When true, corridor digging between room centers allows diagonal paths (the path zigzags diagonally).
  • @export var connectivity_allow_diagonal: bool = false
    When true, 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_internal and, if editor_live_update is true in 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, via await 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
    When true, _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 to true, _generate_editor_safe() is called once and the flag is immediately set back to false.

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_node references a node such as room_rayout_generator.gd that owns a grid and emits layout_updated / generation_finished.
  • target_layer_path specifies the TileMapLayer to place tiles on.

Target cell kinds:

  • When target_kind = WALLS, target CELL_WALL cells.
  • When target_kind = FLOORS, target CELL_FLOOR and CELL_DOOR cells.

Placement modes:

  • When placement_type = TERRAIN,
    set_cells_terrain_connect() is used to paint with Terrain.
  • When placement_type = PATTERN,
    TileSet TileMapPattern objects are used to stamp patterns.

Other behavior:

  • If auto_update_on_layout_signal is true, the node automatically re-places tiles when it receives layout_updated / generation_finished from layout_node.
  • register_used_cells_to_layout controls whether to register used cells to layout_node.register_used_cells(cells) and reflect them in used_cells_mask.
  • only_place_on_unoccupied restricts placement to cells that are unused both on the layout and on the TileMapLayer.
    Additionally, pattern_*_override allows this behavior to be overridden per pattern.
  • When multiple placers share the same layout, execution_order controls 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 to RoomLayoutGenerator.
  • 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_node caches the reference in _layout_ref.
  • var _layout_ref: Node
    Actual layout node reference.
  • var _grid: Array
    Internal cache of the current layout grid.
  • @export var target_layer_path: NodePath
    Path to the target TileMapLayer.
  • var _target_layer: TileMapLayer
    Reference to the actual TileMap layer.

Execution control and linkage

  • @export var clear_before_place: bool = true
    Whether to call TileMapLayer.clear() before placement.
  • @export var register_used_cells_to_layout: bool = true
    Whether to pass the list of cells used for placement to layout_node.register_used_cells(cells).
  • @export var only_place_on_unoccupied: bool = false
    When true, only cells that are free both in the layout and on the TileMapLayer are targeted.
    This combines layout.get_free_cells(kind) and TileMap cell checks.
  • @export var run_on_editor_button: bool = false
    Editor-side button trigger flag. When set to true, _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 to false.
  • @export var auto_update_on_layout_signal: bool = true
    When true, automatically reacts to layout_updated / generation_finished signals from layout_node.
  • @export var auto_update_in_editor_only: bool = true
    When true, 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 to 0, 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 same layout_node.
    Lower values run first; if the values are equal, the order falls back to ascending instance_id.
  • var _pending_auto_update_local: bool
  • var _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 of TileMapPattern indices to use. If empty, all available patterns are targeted.
  • @export var pattern_avoid_overlap: bool = true
    When true, 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: float weight
  • @export var pattern_adjacent_dirs: Dictionary = {}
    Direction bitmask per pattern.
    • Key: pattern_index
    • Value: int bitmask
      • Bits: 1 = U (up), 2 = R, 4 = D, 8 = L
  • @export var pattern_required_cells: Dictionary = {}
    Definition of required contact cells per pattern.
    • Key: pattern_index
    • Value: Array[Vector2i] of internal pattern cell coordinates
  • @export var pattern_min_counts: Dictionary = {}
    Minimum placement count per pattern.
  • @export var pattern_max_counts: Dictionary = {}
    Maximum placement count per pattern. -1 or missing means “no upper limit”.
  • @export var pattern_register_used_cells_override: Dictionary = {}
    Per-pattern override for register_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)
  • @export var pattern_only_place_on_unoccupied_override: Dictionary = {}
    Per-pattern override for only_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)

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.gd via Script.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/L check boxes).
  • Required contact cells (buttons for internal pattern cells, toggled by clicking).
  • Minimum count (Min).
  • Maximum count (Max, -1 for 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_weights
  • pattern_adjacent_dirs
  • pattern_required_cells
  • pattern_min_counts
  • pattern_max_counts
  • pattern_register_used_cells_override
  • pattern_only_place_on_unoccupied_override

class PatternPreviewControl extends Control

Control subclass dedicated to pattern preview rendering.

  • var tileset: TileSet
    TileSet used 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 each Dictionary, 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 seed of the layout node.
    • Optionally randomizes layout parameters (width, height, room count, room size, corridor width).
    • Triggers layout_node.generate_now() to regenerate the layout.
  • After regeneration, it can:
    • Move the node specified by move_target_node_path to one of the free cells.
    • Adjust the limit_* properties of the camera specified by zoom_camera_path to match the layout’s outer bounds.

Assumptions

  • layout_node_path should point to a Node2D with room_rayout_generator.gd attached.

Input monitoring

  • In _process(), it checks Input.is_action_just_pressed(input_action_name) and calls _regenerate_with_random_seed() when the action is pressed.

Movement and camera

  • If move_after_generation is true, it calls _move_target_to_free_cell() after the layout update.
  • If zoom_camera_path is 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 as RoomLayoutGenerator).
  • 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"
    InputMap action name used as a trigger for layout regeneration.
  • @export var log_enabled: bool = true
    When true, outputs internal logs via print() / push_warning(), etc.
  • var layout_node: Node = null
    Actual layout node reference. Retrieved from layout_node_path in _ready().
  • var _rng: RandomNumberGenerator = RandomNumberGenerator.new()
    RNG used for randomization on each regeneration.

Randomizing layout parameters

  • @export var randomize_layout_size: bool = false
    When true, randomizes layout width and height on each regeneration.
  • @export var layout_width_range: Vector2i = Vector2i(80, 80)
    Random range for width (x = min, y = max).
  • @export var layout_height_range: Vector2i = Vector2i(60, 60)
    Random range for height.
  • @export var randomize_room_count: bool = false
    When true, randomizes room_count on each regeneration.
  • @export var room_count_range: Vector2i = Vector2i(18, 18)
    Random range for room_count.
  • @export var randomize_room_size: bool = false
    When true, 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 decide min and max.
  • @export var room_height_range: Vector2i = Vector2i(4, 12)
    Base random range for room height.
  • @export var randomize_corridor_width: bool = false
    When true, 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 decide min/max or 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 the TileMapLayer corresponding 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
    When true, 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 a Camera2D / ZoomCamera2D whose limit_left / right / top / bottom are adjusted after regeneration.

2 Likes