Deterministic, grid-based level generators behind dungeon crawlers, roguelike floor layouts, and any other game that wants a fresh map every run without designers placing tiles by hand.
Summary
The Scylla procedural map generation utilities provide nine algorithms: Drunkard’s Walk, Cellular Automata, BSP (Binary Space Partition), Room-and-Corridor, Maze, Maze with Rooms, Noise Threshold, Voronoi Regions, and Floorplan. The result of any algorithm is a MapGenResult<TCoord> containing detected rooms, corridors, connected-component count, and optional spawn points; the Scylla{Square|Hex|Tri}Grid<int> used in combination with this carries the cell values in place. Every algorithm is deterministic from an IRandomSource: the same source, the same seed, and the same settings always produce the same map on every platform.
Three grid shapes are supported:
- Square grids work with every algorithm.
- Hex grids support Drunkard’s Walk, Cellular Automata, Room-and-Corridor, Noise Threshold, and Voronoi Regions.
- Triangle grids support Noise Threshold and Voronoi Regions.
BSP, Maze, Maze-with-Rooms, and Floorplan are square-only because their geometry assumes orthogonal partitioning. All algorithms write the standard MapCellType integer encoding into the grid by default (Wall = 1, Floor = 2, Corridor = 3, Door = 4, Spawn = 5); MapGenSettings lets each project pick its own integer encoding if your tile set uses different indices.
Map post-processing is automatic when MapGenSettings flags are set. EnsureConnectivity runs a flood-fill from the largest floor component and carves tunnels to any disconnected islands so every floor cell is reachable. RemoveDeadEnds erodes one-neighbor floor cells until none remain, useful for tightening maze and drunkard-walk output. DetectRooms flood-fills floor cells into MapRoom records that the result returns. FindSpawns BFS-selects up to SpawnCount well-distributed floor cells. Every post-processing step is also exposed as a standalone method on ProceduralMapPostProcessor for custom pipelines that need finer control over which step runs in what order. Doors are placed via ProceduralMapPostProcessor.PlaceDoors with a DoorPlacementSettings controlling probability, per-room caps, and minimum spacing.
Procedural level generation can be used to create a near infinite amount of maps. A roguelike, a survival sandbox, a tactics game with random missions, a dungeon-crawler with daily challenges – all of them might need the same features: carve a layout, ensure connectivity, identify rooms, place spawns. The Lerp static method on every settings struct supports level-scaled difficulty (sparse rooms at level 1, dense rooms at level 20) by linearly interpolating between two presets.
Use the procedural map generation utilities for:
- Roguelike and dungeon-crawl level generation: rooms-and-corridors, BSP, or maze-with-rooms.
- Cave systems and organic underground areas: Drunkard’s Walk or Cellular Automata.
- Open-world chunks, biome maps, and continent shapes: Noise Threshold over Perlin/OpenSimplex2 fractal noise.
- Region partitioning for tactical maps and territorial AI: Voronoi Regions.
- Building interiors and modern floor plans: Floorplan (Squarified Treemap) packs mixed-size rooms wall-to-wall with thin shared walls and doorways, with no wasted void space.
- Perfect-maze puzzles, infiltration levels, and labyrinth challenges: Maze (with optional loops and dead-end removal).
- Tutorial pads and small fixed layouts seeded against a known integer for reproducible “Day 1” content.
- Procedural content sharing via seed strings: one
ulongis enough to reproduce an entire dungeon on another player’s machine.

Features
- Nine shipped algorithms. Drunkard’s Walk, Cellular Automata, BSP (Binary Space Partition), Room-and-Corridor, Maze, Maze with Rooms, Noise Threshold, Voronoi Regions, and Floorplan (Squarified Treemap). Each covers a different output shape, from organic caves to perfect mazes to authored-looking dungeons to building floor plans.
- Three grid topologies. Every algorithm supports square; five of the nine also support hex; two also support triangle.
ProceduralMapGeneratoris a single static class; the right overload is selected by the grid argument’s type. - Named settings presets. Every algorithm ships a
.Defaultplus one or more named variations (e.g. cellularSmooth/Rough, mazeNoLoops/SparseLoops, VoronoiSparse/Dense). Use a preset directly or build a custom settings struct for one-off shapes. - Deterministic from an
IRandomSource. The same source, the same seed, and the same settings always produce the same map on every platform. Share procedural content with a singleulongseed; replay or co-op desync becomes impossible. Lerp(a, b, t)on every settings struct. Linearly interpolates between two presets, the right primitive for level-scaled difficulty (sparse rooms at level 1, dense rooms at level 20) without hand-authoring intermediate presets.- Automatic post-processing pipeline.
MapGenSettingsflags driveEnsureConnectivity(carve tunnels to disconnected islands),RemoveDeadEnds(erode one-neighbor floor cells),DetectRooms(flood-fill intoMapRoomrecords), andFindSpawns(BFS-select well-distributed spawn points). Each step is also exposed standalone onProceduralMapPostProcessorfor custom pipelines. MapGenResult<TCoord>carries the structured output. Detected rooms, corridors, connected-component count, and spawn points returned alongside the modified grid. Your calling code reasons about rooms-and-corridors directly rather than re-deriving them from cell types.- Door placement.
ProceduralMapPostProcessor.PlaceDoorsusesDoorPlacementSettingsto control probability, per-room caps, and minimum spacing. Doors mark the natural choke points between rooms and corridors. - Configurable cell encoding.
MapGenSettingslets each project pick its own integer encoding forWall,Floor,Corridor,Door, andSpawncell types. Defaults are sensible; the override matters when integrating with tile sets that have their own indices. - Overlay system.
ProceduralMapOverlaypaints a second pass onto an existing map (loot positions, hazards, decorations, biome variation) throughMapOverlaySettings. The base layout stays untouched; overlays are additive. - Corridor style switch for BSP and Room-and-Corridor.
CorridorStyle.LShaped,Straight, orWinding. The winding variant usesWindingCorridorSettingsto jog corridors for a more organic feel without writing a separate algorithm. - Building floor plans via Squarified Treemap. The Floorplan algorithm packs a deliberate mix of large, medium, and small rooms wall-to-wall across the whole interior with no wasted voids, separates them with 1-2 cell shared walls, and connects them through doorways (with corridors only where a room is not wall-adjacent).
FloorplanSettingscontrols the size mix, wall thickness and thin/thick wall variety, door-loop density, and room count, so the same algorithm produces tight apartments or open-plan layouts. - Composable post-processing. Every post-processing step (
EnsureConnectivity,RemoveDeadEnds,DetectRooms,FindSpawns,PlaceDoors) is a standalone method, so custom pipelines can reorder, skip, or repeat steps without re-running the generator.
Algorithms
A “procedural” generator is a function that turns a small seed plus a settings record into a complete grid layout. Each algorithm in the table below makes a different trade-off between structure (rooms-and-corridors vs. organic caves vs. mazes) and grid-shape support. Pick the algorithm that matches the desired shape of the result; tune the inner character (room size, corridor jitter, cellular smoothness) through the algorithm’s settings struct.
A few terms worth defining for readers new to the area. BSP (Binary Space Partition) recursively splits a rectangle into two smaller rectangles until each is small enough to host one room. Cellular automata treat each cell as alive or dead and iteratively apply birth and death rules based on neighbor counts; the result over a few iterations looks like a cave system. Drunkard’s Walk spawns one or more random-walking “walkers” that carve floor cells as they wander, optionally biased toward a forward direction. Voronoi regions scatter seed points across the grid and assign each cell to the nearest seed, producing irregular polygon regions; raise FloorRatio to keep more regions traversable.
| Algorithm | Grid shape | Output character | Notes |
|---|---|---|---|
DrunkardWalk | Square, Hex | Organic caves and meandering tunnels | Multiple walkers, configurable forward bias, optional chunk-carving for thicker corridors. Spawn-and-destroy lifecycle gradually changes the walker population to vary cave texture across the map. |
CellularAutomata | Square, Hex | Cellular caves with rounded walls | Random fill followed by iterative birth/death rules. Smooth preset produces large clean rooms; Rough keeps more crevices and isolated chambers. Square supports Moore and Von Neumann neighborhoods. |
BSP | Square only | Rectangular rooms with straight corridors | Recursive binary partition until rooms fit, then connect siblings via L-shaped, straight, or winding corridors (CorridorStyle). Best for classic dungeons. |
RoomAndCorridor | Square, Hex | Scattered placed rooms connected by a minimum spanning tree | Random placement with collision testing and configurable spacing; MST connectivity plus optional extra corridors via ExtraCorridorRatio. Hex variant uses hex-axial geometry. |
Maze | Square only | Perfect or near-perfect mazes | Recursive backtracker (randomized DFS) produces a perfect maze; LoopCreationRatio adds connections to make it imperfect; DeadEndRemovalRatio prunes after generation. |
MazeWithRooms | Square only | Maze with rectangular rooms stamped in | Place rooms first (configured by RoomGroup arrays for size groups), then carve maze through the remaining space, then connect rooms to the maze. The classic NetHack-style layout. |
NoiseThreshold | Square, Hex, Triangle | Terrain-like masks | Sample NoiseSettings at each cell, threshold to floor or wall. Pair with FBM (Fractional Brownian Motion), Turbulence, or Ridged fractals from Noise Utils for islands, caves, or continents. |
VoronoiRegions | Square, Hex, Triangle | Cell-based regions with polygon boundaries | Scatter region seeds; assign every cell to its nearest seed; flood-floor a fraction (FloorRatio) of regions, mark the rest as wall. Optional MST connects floor regions through walls. |
Floorplan | Square only | Building floor plans: mixed-size rooms wall-to-wall | Squarified-treemap packing fills the whole interior with no voids; rooms come in a configurable large/medium/small mix separated by 1-2 cell shared walls; an MST of doorways connects every room (extra loop doors via DoorLoopRatio). Reads like a built structure, not a carved cave or BSP partition tree. |
One more shape family – winding corridors – is not a top-level algorithm but a CorridorStyle value used by BSP and Room-and-Corridor when carving between rooms. Pick CorridorStyle.LShaped for one-bend corridors, CorridorStyle.Straight for direct lines (which may overlap rooms), or CorridorStyle.Winding to jog the corridor through WindingCorridorSettings for a more organic feel.
Every algorithm-specific settings struct exposes .Default (a balanced preset) plus one or more named presets covering common variations, and a static Lerp(a, b, t) method for level-scaled interpolation between two presets. MapGenSettings itself has Default, Dungeon, and Cave presets.
Recipes
These recipes cover every public method on ProceduralMapGenerator, ProceduralMapPostProcessor, and ProceduralMapOverlay. Each one is a self-contained answer to a single problem, so jump to the one that matches what you are building. The first recipe establishes the grid, IRandomSource, and MapGenSettings boilerplate that the rest of them reuse – start there if you are new to the API.
Set up a grid for level generation
Problem. You want to start generating maps, but every generation call needs the same three ingredients: a grid to write into, a seeded IRandomSource for determinism, and a MapGenSettings that picks the cell-value encoding and post-processing flags. Getting these right up front means every algorithm you try from here works the same way.
Solution. Construct the grid once at the size the level needs, seed an IRandomSource (see Random Utils), and pick a MapGenSettings preset (Default, Dungeon, or Cave) or build a custom instance.
using Scylla.Core.Structures.Grid;
using Scylla.Core.Util.ProceduralMapGen;
using Scylla.Core.Util.Random;
/* The grid: each cell stores an int that encodes a MapCellType value. Square,
* hex, and triangle grids are all supported by various algorithms. */
var grid = new ScyllaSquareGrid<int>(width: 64, height: 64);
/* The deterministic random source. Same seed -> same map every time. */
IRandomSource rng = RandomUtil.CreateXoshiro256(seed: 12345UL);
/* The shared MapGenSettings. Three presets cover the common shapes:
* Default - balanced, connectivity + room detection on
* Dungeon - adds dead-end removal and one spawn point (room-and-corridor levels)
* Cave - connectivity + room detection, no dead-end removal (cave levels) */
MapGenSettings mapSettings = MapGenSettings.Dungeon;
/* Or build a custom instance with the full constructor. The DoorValue/SpawnCount
* arguments are documented inline; see the Settings reference table below. */
var custom = new MapGenSettings(
wallValue: (int)MapCellType.Wall,
floorValue: (int)MapCellType.Floor,
corridorValue: (int)MapCellType.Corridor,
doorValue: (int)MapCellType.Door,
ensureConnectivity: true,
removeDeadEnds: true,
detectRooms: true,
findSpawns: true,
spawnCount: 5
);
/* Level-scaled settings: interpolate between two presets by a 0..1 ratio. The
* boolean flags pick the value of `b` when t > 0.5; the integer cell values
* snap to the nearest preset's value. Numeric fields lerp linearly. */
MapGenSettings level10 = MapGenSettings.Lerp(MapGenSettings.Cave, MapGenSettings.Dungeon, t: 0.5f);
Generate a roguelike cave with Drunkard’s Walk
Problem. You want the feeling of a Brogue-like cave – a space that meanders for a while before opening into a chamber, then narrows back into a tunnel. That organic, unpredictable quality is hard to get from a partition-based algorithm, and you want the system to produce something different every run without hand-authoring each layout.
Solution. Drunkard’s Walk simulates random walkers that wander the grid and convert wall cells to floor as they go. The walker population can grow and shrink during a run (SpawnChance / DestroyChance), each walker can prefer continuing in its current direction (ForwardBias), and each step can carve a single cell or a square chunk (ChunkSize). Three presets cover the common shapes; build a custom instance for anything else.
/* Square grid drunkard walk with the Default preset. */
MapGenResult<SquareCoord> squareResult = ProceduralMapGenerator.DrunkardWalk(
grid: grid,
settings: DrunkardWalkSettings.Default,
genSettings: MapGenSettings.Cave,
rng: rng
);
/* Open caves: higher fill ratio, larger chunks, more walkers. */
ProceduralMapGenerator.DrunkardWalk(grid, DrunkardWalkSettings.OpenCaves, MapGenSettings.Cave, rng);
/* Tight tunnels: lower fill, single-cell carves, strong forward bias. */
ProceduralMapGenerator.DrunkardWalk(grid, DrunkardWalkSettings.TightTunnels, MapGenSettings.Cave, rng);
/* Hex grid variant. Same algorithm tuned for hex axial geometry. */
var hexGrid = new ScyllaHexGrid<int>(width: 48, height: 48);
MapGenResult<HexCoord> hexResult = ProceduralMapGenerator.DrunkardWalk(
hexGrid, DrunkardWalkSettings.Default, MapGenSettings.Cave, rng
);
/* Level-scaled drunkard settings: interpolate between two presets. The result
* is a fresh DrunkardWalkSettings struct that lerps numeric fields linearly. */
DrunkardWalkSettings tuned = DrunkardWalkSettings.Lerp(
a: DrunkardWalkSettings.TightTunnels,
b: DrunkardWalkSettings.OpenCaves,
t: 0.4f
);
/* Custom settings for a one-off shape. */
var customWalk = new DrunkardWalkSettings(
targetFillRatio: 0.40f, /* carve until ~40 % of the grid is floor */
maxSteps: 5000, /* hard step cap */
maxWalkers: 6,
chunkSize: 3, /* 3x3 chunk per step */
forwardBias: 0.65f, /* mostly continue forward */
spawnChance: 0.05f,
destroyChance: 0.02f
);
ProceduralMapGenerator.DrunkardWalk(grid, customWalk, MapGenSettings.Cave, rng);
Generate smooth cave systems with Cellular Automata
Problem. You want the organic, smooth-walled cave aesthetic that Brogue and Cogmind built much of their visual identity on – the kind where the walls have a natural feel rather than rectangular edges, and chambers form from the simulation rules rather than from a partition tree.
Solution. Cellular automata randomly fill the grid at a configurable ratio of walls to floors, then over a few iterations each cell decides whether to live or die based on how many of its neighbors are walls. Pick Smooth for large rounded chambers or Rough for more broken, crevice-heavy output.
/* Default cellular caves. Square grid. */
ProceduralMapGenerator.CellularAutomata(grid, CellularAutomataSettings.Default, MapGenSettings.Cave, rng);
/* Smooth: more iterations, gentler rules; large rounded chambers. */
ProceduralMapGenerator.CellularAutomata(grid, CellularAutomataSettings.Smooth, MapGenSettings.Cave, rng);
/* Rough: fewer iterations, harsher rules; broken walls and isolated pockets. */
ProceduralMapGenerator.CellularAutomata(grid, CellularAutomataSettings.Rough, MapGenSettings.Cave, rng);
/* Hex grid variant. */
ProceduralMapGenerator.CellularAutomata(hexGrid, CellularAutomataSettings.Default, MapGenSettings.Cave, rng);
/* Custom settings. UseVonNeumann switches to 4-neighbor adjacency (Square only;
* hex always uses its own 6-neighbor adjacency). */
var customCA = new CellularAutomataSettings(
initialFillRatio: 0.45f, /* 45 % initial walls */
iterations: 4,
birthThreshold: 5, /* dead cell becomes alive if 5+ alive neighbors */
deathThreshold: 4, /* alive cell dies if 4 or fewer alive neighbors */
useVonNeumann: false /* false = Moore (8-neighbor), true = Von Neumann (4-neighbor) */
);
ProceduralMapGenerator.CellularAutomata(grid, customCA, MapGenSettings.Cave, rng);
Generate a classic BSP dungeon
Problem. You are building a dungeon-crawl where each floor should have a clear set of rectangular rooms and connecting corridors – the kind of layout that Rogue and its descendants established. You want the rooms to feel planned rather than organically carved, and you want the generator to guarantee that every room is connected.
Solution. BSP (Binary Space Partition) recursively subdivides the grid into smaller rectangles until each is at least MinPartitionSize and at most MaxDepth deep, places one room inside each leaf rectangle, and connects sibling leaves via corridors. The corridor style is configurable (L-shaped, Straight, or Winding). Three presets cover the common variations.
/* Default BSP dungeon. */
ProceduralMapGenerator.BSP(grid, BSPSettings.Default, MapGenSettings.Dungeon, rng);
/* Small rooms preset: tighter min/max room sizes for dense dungeons. */
ProceduralMapGenerator.BSP(grid, BSPSettings.SmallRooms, MapGenSettings.Dungeon, rng);
/* Large rooms preset: bigger rooms, fewer of them. */
ProceduralMapGenerator.BSP(grid, BSPSettings.LargeRooms, MapGenSettings.Dungeon, rng);
/* Custom BSP with winding corridors. The WindingCorridorSettings parameter is
* ignored when CorridorStyle is LShaped or Straight, but must be provided. */
var customBSP = new BSPSettings(
minRoomWidth: 4,
minRoomHeight: 4,
maxRoomWidth: 10,
maxRoomHeight: 10,
minPartitionSize: 12,
maxDepth: 5,
roomPadding: 1,
corridorWidth: 1,
splitRatioMin: 0.4f,
splitRatioMax: 0.6f,
corridorStyle: CorridorStyle.Winding,
windingSettings: WindingCorridorSettings.Default
);
ProceduralMapGenerator.BSP(grid, customBSP, MapGenSettings.Dungeon, rng);
/* WindingCorridorSettings presets: Default (2 jogs, offsets -3..4),
* Subtle (1 jog), Extreme (3 jogs, wider offsets). */
var subtle = WindingCorridorSettings.Subtle;
var extreme = WindingCorridorSettings.Extreme;
Generate a dungeon with scattered rooms and MST corridors
Problem. You want rooms that feel hand-placed rather than carved out of a partition tree – rooms in positions that are a bit less predictable than BSP produces, connected by corridors that wander. This is the feel that a lot of ADOM-style dungeon layouts go for, and you also want optional extra loops above the bare minimum connectivity.
Solution. Room-and-Corridor scatters rectangular rooms onto the grid with collision tests against MinRoomSpacing and connects them via a minimum spanning tree (MST), optionally adding extra corridors above the MST for richer connectivity (ExtraCorridorRatio). Square and Hex variants exist; corridor style is configurable per call.
/* Default scattered rooms with L-shaped corridors. */
ProceduralMapGenerator.RoomAndCorridor(grid, RoomCorridorSettings.Default, MapGenSettings.Dungeon, rng);
/* Dense: more rooms, smaller spacing, more extra corridors. */
ProceduralMapGenerator.RoomAndCorridor(grid, RoomCorridorSettings.Dense, MapGenSettings.Dungeon, rng);
/* Sparse: fewer rooms, larger spacing, no extra corridors beyond the MST. */
ProceduralMapGenerator.RoomAndCorridor(grid, RoomCorridorSettings.Sparse, MapGenSettings.Dungeon, rng);
/* Hex variant. Same algorithm shape but rooms are placed in hex-axial coords. */
ProceduralMapGenerator.RoomAndCorridor(hexGrid, RoomCorridorSettings.Default, MapGenSettings.Dungeon, rng);
/* Custom RoomCorridorSettings with winding corridors. */
var customRC = new RoomCorridorSettings(
roomCount: 12,
minRoomWidth: 4,
minRoomHeight: 4,
maxRoomWidth: 8,
maxRoomHeight: 8,
minRoomSpacing: 2,
extraCorridorRatio: 0.25f, /* 25 % of MST edge count is added as extras */
corridorWidth: 1,
maxPlacementAttempts: 100,
corridorStyle: CorridorStyle.Winding,
windingSettings: WindingCorridorSettings.Default
);
ProceduralMapGenerator.RoomAndCorridor(grid, customRC, MapGenSettings.Dungeon, rng);
Generate a maze or maze-with-rooms level
Problem. You want a maze level – either a pure puzzle where every path eventually leads somewhere (no dead cells, no unreachable areas), or the NetHack-style arrangement where rooms float inside a maze and the player navigates the corridors between them. These are a roguelike staple, and “perfect maze” (every cell reachable, no cycles) has a precise mathematical definition.
Solution. Maze produces a perfect maze via randomized depth-first search. LoopCreationRatio adds occasional cycles to convert the perfect maze into an imperfect one; DeadEndRemovalRatio prunes dead-end fingers after generation. MazeWithRooms stamps rectangular rooms onto the grid first, then carves a maze through the remaining space, then connects every room to the maze. RoomGroup arrays let one settings instance describe multiple size classes (for example, several small rooms plus one large boss room).
/* Pure perfect maze. */
ProceduralMapGenerator.Maze(grid, MazeSettings.Default, MapGenSettings.Dungeon, rng);
/* Imperfect maze: 5 % loop creation, 30 % dead-end pruning. */
ProceduralMapGenerator.Maze(grid, MazeSettings.Imperfect, MapGenSettings.Dungeon, rng);
/* Open maze: heavy dead-end pruning + loops for more navigable layouts. */
ProceduralMapGenerator.Maze(grid, MazeSettings.Open, MapGenSettings.Dungeon, rng);
/* Custom maze settings. */
var customMaze = new MazeSettings(
deadEndRemovalRatio: 0.2f, /* prune 20 % of dead ends after carving */
loopCreationRatio: 0.05f /* introduce ~5 % cycles to break perfect-maze symmetry */
);
ProceduralMapGenerator.Maze(grid, customMaze, MapGenSettings.Dungeon, rng);
/* Maze with rooms. The settings carry the inner MazeSettings plus an array
* of RoomGroup specifying room size classes. */
ProceduralMapGenerator.MazeWithRooms(grid, MazeRoomSettings.Default, MapGenSettings.Dungeon, rng);
ProceduralMapGenerator.MazeWithRooms(grid, MazeRoomSettings.ManySmall, MapGenSettings.Dungeon, rng);
ProceduralMapGenerator.MazeWithRooms(grid, MazeRoomSettings.FewLarge, MapGenSettings.Dungeon, rng);
ProceduralMapGenerator.MazeWithRooms(grid, MazeRoomSettings.Mixed, MapGenSettings.Dungeon, rng);
/* Custom MazeRoomSettings with multiple RoomGroup size classes. */
var customMazeRooms = new MazeRoomSettings(
mazeSettings: MazeSettings.Imperfect,
roomGroups: new[]
{
new RoomGroup(count: 6, minWidth: 3, minHeight: 3, maxWidth: 5, maxHeight: 5), /* six small rooms */
new RoomGroup(count: 1, minWidth: 9, minHeight: 9, maxWidth: 12, maxHeight: 12) /* one big boss room */
},
minRoomSpacing: 1,
maxPlacementAttempts: 100
);
ProceduralMapGenerator.MazeWithRooms(grid, customMazeRooms, MapGenSettings.Dungeon, rng);
Generate terrain-like islands and continent shapes
Problem. You are building a procedural open-world, a hex-tile strategy game, or anything else that needs terrain with a natural, island-or-continent shape. The room-based algorithms produce structures that read as buildings; you want something that looks like land and sea or mountain and valley.
Solution. Noise Threshold turns a coherent noise field into a binary mask: cells where the noise value exceeds Threshold become floor (or wall, with Invert); the rest become the opposite. Pair with FBM (Fractional Brownian Motion) or Turbulence noise from Noise Utils for natural-looking shapes. All three grid shapes are supported (square, hex, triangle).
/* Default: OpenSimplex2 FBM at 0.05 frequency, threshold 0.0, no invert. */
ProceduralMapGenerator.NoiseThreshold(grid, NoiseThresholdSettings.Default, MapGenSettings.Cave, rng);
/* Islands: high threshold so only the peaks become floor, producing scattered islands. */
ProceduralMapGenerator.NoiseThreshold(grid, NoiseThresholdSettings.Islands, MapGenSettings.Cave, rng);
/* Dense: low threshold so most cells become floor; useful for high-coverage caves. */
ProceduralMapGenerator.NoiseThreshold(grid, NoiseThresholdSettings.Dense, MapGenSettings.Cave, rng);
/* Hex and triangle grids. */
ProceduralMapGenerator.NoiseThreshold(hexGrid, NoiseThresholdSettings.Default, MapGenSettings.Cave, rng);
var triGrid = new ScyllaTriGrid<int>(width: 32, height: 32);
ProceduralMapGenerator.NoiseThreshold(triGrid, NoiseThresholdSettings.Default, MapGenSettings.Cave, rng);
/* Custom: 3-octave Turbulence noise with a positive threshold for biome masks. */
var customNoise = new NoiseThresholdSettings(
noise: new NoiseSettings(
algorithm: NoiseAlgorithm.OpenSimplex2,
fractal: FractalType.Turbulence,
frequency: 0.04f,
octaves: 3,
lacunarity: 2.0f,
persistence: 0.5f
),
threshold: 0.2f,
invert: false
);
ProceduralMapGenerator.NoiseThreshold(grid, customNoise, MapGenSettings.Cave, rng);
Partition a tactical map into irregular regions
Problem. You are building a hex-tile RTS or a political map for a 4X game and you want the map partitioned into irregular polygon territories – regions that look like they emerged from geography rather than from a grid. BSP gives you rectangles; you want something that looks Voronoi-tiled.
Solution. Voronoi Regions scatter RegionCount seed points across the grid, assign each cell to its nearest seed, then mark a fraction (FloorRatio) of the regions as floor and the rest as wall. Optionally connect floor regions through walls with ConnectRegions = true. All three grid shapes are supported.
/* Default Voronoi regions on a square grid. */
ProceduralMapGenerator.VoronoiRegions(grid, VoronoiRegionSettings.Default, MapGenSettings.Default, rng);
/* Clustered: more regions in less space; produces small interlocking polygons. */
ProceduralMapGenerator.VoronoiRegions(grid, VoronoiRegionSettings.Clustered, MapGenSettings.Default, rng);
/* Sparse: fewer regions over a larger area; produces large open zones. */
ProceduralMapGenerator.VoronoiRegions(grid, VoronoiRegionSettings.Sparse, MapGenSettings.Default, rng);
/* Hex and triangle variants. */
ProceduralMapGenerator.VoronoiRegions(hexGrid, VoronoiRegionSettings.Default, MapGenSettings.Default, rng);
ProceduralMapGenerator.VoronoiRegions(triGrid, VoronoiRegionSettings.Default, MapGenSettings.Default, rng);
/* Custom: 16 regions, 70 % floor coverage, connected through walls. */
var customVoronoi = new VoronoiRegionSettings(
regionCount: 16,
minSpacing: 4f, /* min distance between seeds (in cells) */
floorRatio: 0.7f, /* 70 % of regions are floor */
connectRegions: true /* MST-connect floor regions if any are isolated */
);
ProceduralMapGenerator.VoronoiRegions(grid, customVoronoi, MapGenSettings.Default, rng);
Generate a building interior or space station deck
Problem. You are building a level that should read like the inside of a structure – a space station deck, an apartment block, an office building, a prison. Cave algorithms give you rock and tunnels; BSP gives you a partition tree. You want something where rooms tile the space wall-to-wall with minimal wasted void, the way a real building works.
Solution. Floorplan uses the Squarified Treemap technique to pack a deliberate mix of room sizes wall-to-wall across the entire interior, so there is no wasted void. Rooms are separated by thin (1-2 cell) shared walls and connect through doorways. Square grids only. Three presets cover the common shapes; build a custom instance for any other.
/* Default building floorplan: balanced large/medium/small room mix. */
ProceduralMapGenerator.Floorplan(grid, FloorplanSettings.Default, MapGenSettings.Default, rng);
/* Apartments: tighter, more uniform rooms with mostly thin walls. */
ProceduralMapGenerator.Floorplan(grid, FloorplanSettings.Apartments, MapGenSettings.Default, rng);
/* Open plan: fewer, more spacious rooms with more thick-wall variety. */
ProceduralMapGenerator.Floorplan(grid, FloorplanSettings.OpenPlan, MapGenSettings.Default, rng);
/* Custom settings for a one-off layout. */
var customFloorplan = new FloorplanSettings(
cellsPerRoom: 55, /* fewer cells per room -> more, smaller rooms */
minRoomArea: 4,
wallThickness: 1, /* base shared-wall thickness (1-2) */
largeRoomWeight: 1f, /* size-mix hierarchy: a few large ... */
mediumRoomWeight: 2.5f, /* ... several medium ... */
smallRoomWeight: 2f, /* ... and plenty of small rooms */
doorLoopRatio: 0.1f, /* 0 = spanning tree (fewest doors); higher adds loop openings */
aspectRatioTarget: 1.6f, /* toward 1.0 = squarer rooms */
maxDoorAttempts: 10,
thickWallChance: 0.35f /* ~35 % of shared walls become 2 cells thick */
);
ProceduralMapGenerator.Floorplan(grid, customFloorplan, MapGenSettings.Default, rng);
Read back rooms, corridors, and spawn points from the result
Problem. You have generated the grid. Now you need to place the boss in the largest room, put keys or treasures in smaller rooms, set up ambushes along corridors, and position player spawn points – all without re-scanning the grid by hand.
Solution. Every generator returns a MapGenResult<TCoord> carrying the rooms, corridors, connected-component count, and optional spawn points detected by post-processing. Walk those structures directly rather than re-deriving them from cell types; the post-processor already paid the cost.
MapGenResult<SquareCoord> result = ProceduralMapGenerator.BSP(
grid, BSPSettings.Default, MapGenSettings.Dungeon, rng
);
/* Inspect detected rooms. Each MapRoom carries id, min/max bounding box,
* center coord, and the full Cells list. */
Log.Info($"Rooms: {result.Rooms.Count}", LogCategory.Core);
foreach (MapRoom<SquareCoord> room in result.Rooms)
{
Log.Info(
$"Room {room.ID}: center ({room.Center.Col}, {room.Center.Row}), " +
$"bounds {room.Min}..{room.Max}, {room.Cells.Count} cells",
LogCategory.Core
);
/* Drive placement: put a key in the center of room 0, enemies in 1..N-1. */
if (room.ID == 0)
{
SpawnKey(room.Center);
}
else
{
SpawnEnemies(room);
}
}
/* Corridors carry the two room ids they connect plus the full Cells list. */
foreach (MapCorridor<SquareCoord> corridor in result.Corridors)
{
Log.Info(
$"Corridor: room {corridor.RoomIDA} <-> room {corridor.RoomIDB}, {corridor.Cells.Count} cells",
LogCategory.Core
);
/* Place an ambush point at the midpoint of every corridor. */
int mid = corridor.Cells.Count / 2;
PlaceAmbush(corridor.Cells[mid]);
}
/* Spawn points (when FindSpawns = true). The list contains up to SpawnCount coords. */
foreach (SquareCoord spawn in result.SpawnPoints)
{
SpawnPlayer(spawn);
}
/* Connected-component count: should be 1 when EnsureConnectivity = true.
* Use this as a sanity check after disabling connectivity for testing. */
if (result.ConnectedComponentCount > 1)
{
Log.Warning(
$"Map has {result.ConnectedComponentCount} disconnected components; " +
"regions other than the largest may be unreachable.",
LogCategory.Core
);
}
/* Result also has a ToString() that summarizes the counts. */
Log.Info(result.ToString(), LogCategory.Core);
Run post-processing steps individually
Problem. You want finer control than the MapGenSettings flags allow – running only the steps that matter, running them in a different order, or applying them to a grid that was authored by hand rather than generated. You might also want to place doors explicitly after inspecting the rooms rather than letting the automatic pipeline handle it.
Solution. ProceduralMapPostProcessor exposes every post-processing step as a standalone static method. Each method has Square and Hex overloads, and you can call them in any order.
using Scylla.Core.Util.ProceduralMapGen;
/* Connectivity repair. Returns the number of cells converted from wall to
* floor in the process. */
int patched = ProceduralMapPostProcessor.EnsureConnectivity(
grid,
floorValue: (int)MapCellType.Floor,
wallValue: (int)MapCellType.Wall,
corridorValue: (int)MapCellType.Corridor,
rng: rng
);
/* Dead-end removal. Iteratively converts floor cells with exactly one floor
* neighbor back to walls. Returns the count of removed cells. */
int pruned = ProceduralMapPostProcessor.RemoveDeadEnds(
grid,
floorValue: (int)MapCellType.Floor,
wallValue: (int)MapCellType.Wall
);
/* Room detection via flood-fill. Returns a fresh list of MapRoom structs. */
List<MapRoom<SquareCoord>> rooms = ProceduralMapPostProcessor.DetectRooms(
grid,
floorValue: (int)MapCellType.Floor
);
/* Spawn-point selection. Uses BFS from edges to pick well-distributed floor
* cells, returning at most `count` of them. */
List<SquareCoord> spawns = ProceduralMapPostProcessor.FindSpawnPoints(
grid,
floorValue: (int)MapCellType.Floor,
count: 5,
rng: rng
);
/* Connected-component count. Useful as a standalone sanity check. */
int components = ProceduralMapPostProcessor.CountConnectedComponents(
grid,
floorValue: (int)MapCellType.Floor
);
/* Region grid: an int[] where each cell carries the id of its connected
* component (0 for walls). Useful for region-aware AI or tagging. */
ScyllaSquareGrid<int> regionGrid = ProceduralMapPostProcessor.BuildRegionGrid(
grid,
floorValue: (int)MapCellType.Floor
);
/* Door placement. Walks every room boundary, drops doors with the configured
* probability, respecting the per-room cap and minimum spacing. Returns the
* number of doors placed. */
int doorCount = ProceduralMapPostProcessor.PlaceDoors(
grid,
rooms: rooms,
floorValue: (int)MapCellType.Floor,
corridorValue: (int)MapCellType.Corridor,
wallValue: (int)MapCellType.Wall,
doorValue: (int)MapCellType.Door,
settings: DoorPlacementSettings.Default,
rng: rng
);
/* Hex overloads exist for every method; same signatures with HexCoord. */
int hexPatched = ProceduralMapPostProcessor.EnsureConnectivity(
hexGrid,
floorValue: (int)MapCellType.Floor,
wallValue: (int)MapCellType.Wall,
corridorValue: (int)MapCellType.Corridor,
rng: rng
);
Paint water, lava, and decoration passes onto a finished map
Problem. You have generated the dungeon layout. Now you want a second pass that adds visual richness and environmental variety: water in the low-noise areas of the cave floor, mushrooms and vegetation scattered across open floors, a checkerboard pattern on the boss room tiles, or a decorative border around the playable area. You want these to sit on a separate layer so the core gameplay grid stays clean.
Solution. ProceduralMapOverlay applies decorative or gameplay-affecting overlays to a finished grid. Noise-driven masks place water, lava, and vegetation; geometric patterns apply checkerboards, stripes, and borders; random scatter places individual decoration tiles. Every overlay respects an OnlyAffectFloors flag so generated walls remain intact unless you explicitly want them modified. You can write overlays into the same grid or into a separate overlay grid.
using Scylla.Core.Util.ProceduralMapGen;
/* Noise overlay: write `targetValue` at every floor cell where the noise
* field exceeds the threshold. Useful for placing water, lava, grass, or
* any "natural variation" tile. */
var waterOverlay = new MapOverlaySettings(
noise: NoiseSettings.Caves,
threshold: 0.4f,
pattern: MapPatternType.Checker, /* not used in noise overlay */
onlyAffectFloors: true,
scatterDensity: 0.0f /* not used in noise overlay */
);
ProceduralMapOverlay.ApplyNoiseOverlay(
grid: grid,
targetValue: (int)MapCellType.Wall + 10, /* a project-specific water tile id */
floorValue: (int)MapCellType.Floor,
settings: waterOverlay,
rng: rng
);
/* Noise overlay onto a separate overlay grid: read floor eligibility from
* the source grid, write into a different grid. Useful for keeping decoration
* layers independent from the gameplay grid. */
var overlayGrid = new ScyllaSquareGrid<int>(grid.Width, grid.Height);
ProceduralMapOverlay.ApplyNoiseOverlay(
sourceGrid: grid,
overlayGrid: overlayGrid,
targetValue: 99, /* decoration tile id */
floorValue: (int)MapCellType.Floor,
settings: waterOverlay,
rng: rng
);
/* Hex variant. */
ProceduralMapOverlay.ApplyNoiseOverlay(
grid: hexGrid,
targetValue: 12,
floorValue: (int)MapCellType.Floor,
settings: waterOverlay,
rng: rng
);
/* Pattern overlay: applies a geometric pattern. Five MapPatternType values
* are available: Checker, DiagonalStripes, HorizontalStripes, VerticalStripes, Border. */
var checkerSettings = new MapOverlaySettings(
noise: NoiseSettings.Default,
threshold: 0f,
pattern: MapPatternType.Checker,
onlyAffectFloors: true,
scatterDensity: 0f
);
ProceduralMapOverlay.ApplyPatternOverlay(
grid: grid,
targetValue: 50,
floorValue: (int)MapCellType.Floor,
settings: checkerSettings
);
/* Random scatter: place `targetValue` at floor cells with probability
* `ScatterDensity`. Useful for sparse decoration: pebbles, mushrooms, debris. */
var scatterSettings = new MapOverlaySettings(
noise: NoiseSettings.Default,
threshold: 0f,
pattern: MapPatternType.Checker,
onlyAffectFloors: true,
scatterDensity: 0.05f /* 5 % of floor cells get the target value */
);
ProceduralMapOverlay.ApplyRandomScatter(
grid: grid,
targetValue: 60,
floorValue: (int)MapCellType.Floor,
settings: scatterSettings,
rng: rng
);
/* Hex overloads exist for every overlay method. */
ProceduralMapOverlay.ApplyRandomScatter(hexGrid, 60, (int)MapCellType.Floor, scatterSettings, rng);
API Sketch
The public surface is split across three static classes (ProceduralMapGenerator, ProceduralMapPostProcessor, ProceduralMapOverlay), one common settings struct (MapGenSettings), nine algorithm-specific settings structs, three post-processing settings structs, three result structs, and three enums. All algorithm methods follow the same signature shape: grid, algorithm settings, common settings, random source, returning MapGenResult<TCoord>.
namespace Scylla.Core.Util.ProceduralMapGen
{
public static class ProceduralMapGenerator
{
/* Drunkard's Walk: organic caves and tunnels (Square + Hex). */
public static MapGenResult<SquareCoord> DrunkardWalk(ScyllaSquareGrid<int> grid,
DrunkardWalkSettings settings, MapGenSettings genSettings, IRandomSource rng);
public static MapGenResult<HexCoord> DrunkardWalk(ScyllaHexGrid<int> grid,
DrunkardWalkSettings settings, MapGenSettings genSettings, IRandomSource rng);
/* Cellular Automata: cave systems (Square + Hex). */
public static MapGenResult<SquareCoord> CellularAutomata(ScyllaSquareGrid<int> grid,
CellularAutomataSettings settings, MapGenSettings genSettings, IRandomSource rng);
public static MapGenResult<HexCoord> CellularAutomata(ScyllaHexGrid<int> grid,
CellularAutomataSettings settings, MapGenSettings genSettings, IRandomSource rng);
/* BSP: recursive room subdivision (Square only). */
public static MapGenResult<SquareCoord> BSP(ScyllaSquareGrid<int> grid,
BSPSettings settings, MapGenSettings genSettings, IRandomSource rng);
/* Room and Corridor: scattered rooms with MST corridors (Square + Hex). */
public static MapGenResult<SquareCoord> RoomAndCorridor(ScyllaSquareGrid<int> grid,
RoomCorridorSettings settings, MapGenSettings genSettings, IRandomSource rng);
public static MapGenResult<HexCoord> RoomAndCorridor(ScyllaHexGrid<int> grid,
RoomCorridorSettings settings, MapGenSettings genSettings, IRandomSource rng);
/* Noise Threshold: terrain-like masks (Square + Hex + Triangle). */
public static MapGenResult<SquareCoord> NoiseThreshold(ScyllaSquareGrid<int> grid,
NoiseThresholdSettings settings, MapGenSettings genSettings, IRandomSource rng);
public static MapGenResult<HexCoord> NoiseThreshold(ScyllaHexGrid<int> grid,
NoiseThresholdSettings settings, MapGenSettings genSettings, IRandomSource rng);
public static MapGenResult<TriCoord> NoiseThreshold(ScyllaTriGrid<int> grid,
NoiseThresholdSettings settings, MapGenSettings genSettings, IRandomSource rng);
/* Maze: perfect or imperfect maze (Square only). */
public static MapGenResult<SquareCoord> Maze(ScyllaSquareGrid<int> grid,
MazeSettings settings, MapGenSettings genSettings, IRandomSource rng);
/* Maze with rooms: rooms stamped on top of a maze (Square only). */
public static MapGenResult<SquareCoord> MazeWithRooms(ScyllaSquareGrid<int> grid,
MazeRoomSettings settings, MapGenSettings genSettings, IRandomSource rng);
/* Voronoi Regions: cell-based region partitioning (Square + Hex + Triangle). */
public static MapGenResult<SquareCoord> VoronoiRegions(ScyllaSquareGrid<int> grid,
VoronoiRegionSettings settings, MapGenSettings genSettings, IRandomSource rng);
public static MapGenResult<HexCoord> VoronoiRegions(ScyllaHexGrid<int> grid,
VoronoiRegionSettings settings, MapGenSettings genSettings, IRandomSource rng);
public static MapGenResult<TriCoord> VoronoiRegions(ScyllaTriGrid<int> grid,
VoronoiRegionSettings settings, MapGenSettings genSettings, IRandomSource rng);
/* Floorplan: squarified-treemap building floor plans (Square only). */
public static MapGenResult<SquareCoord> Floorplan(ScyllaSquareGrid<int> grid,
FloorplanSettings settings, MapGenSettings genSettings, IRandomSource rng);
}
/* Each step also callable standalone (Square + Hex overloads for each). */
public static class ProceduralMapPostProcessor
{
public static int EnsureConnectivity(ScyllaSquareGrid<int> grid, int floorValue, int wallValue, int corridorValue, IRandomSource rng);
public static int RemoveDeadEnds (ScyllaSquareGrid<int> grid, int floorValue, int wallValue);
public static List<MapRoom<SquareCoord>> DetectRooms(ScyllaSquareGrid<int> grid, int floorValue);
public static List<SquareCoord> FindSpawnPoints (ScyllaSquareGrid<int> grid, int floorValue, int count, IRandomSource rng);
public static int CountConnectedComponents(ScyllaSquareGrid<int> grid, int floorValue);
public static ScyllaSquareGrid<int> BuildRegionGrid(ScyllaSquareGrid<int> grid, int floorValue);
public static int PlaceDoors(ScyllaSquareGrid<int> grid, List<MapRoom<SquareCoord>> rooms,
int floorValue, int corridorValue, int wallValue, int doorValue,
DoorPlacementSettings settings, IRandomSource rng);
/* + identical Hex overloads */
}
/* Overlays applied to a finished grid. Each has Square in-place, Square overlay-grid,
* and Hex variants where applicable. */
public static class ProceduralMapOverlay
{
public static void ApplyNoiseOverlay (ScyllaSquareGrid<int> grid, int targetValue, int floorValue, MapOverlaySettings settings, IRandomSource rng);
public static void ApplyNoiseOverlay (ScyllaSquareGrid<int> sourceGrid, ScyllaSquareGrid<int> overlayGrid, int targetValue, int floorValue, MapOverlaySettings settings, IRandomSource rng);
public static void ApplyNoiseOverlay (ScyllaHexGrid<int> grid, int targetValue, int floorValue, MapOverlaySettings settings, IRandomSource rng);
public static void ApplyPatternOverlay(ScyllaSquareGrid<int> grid, int targetValue, int floorValue, MapOverlaySettings settings);
public static void ApplyPatternOverlay(ScyllaSquareGrid<int> sourceGrid, ScyllaSquareGrid<int> overlayGrid, int targetValue, int floorValue, MapOverlaySettings settings);
public static void ApplyPatternOverlay(ScyllaHexGrid<int> grid, int targetValue, int floorValue, MapOverlaySettings settings);
public static void ApplyRandomScatter (ScyllaSquareGrid<int> grid, int targetValue, int floorValue, MapOverlaySettings settings, IRandomSource rng);
public static void ApplyRandomScatter (ScyllaSquareGrid<int> sourceGrid, ScyllaSquareGrid<int> overlayGrid, int targetValue, int floorValue, MapOverlaySettings settings, IRandomSource rng);
public static void ApplyRandomScatter (ScyllaHexGrid<int> grid, int targetValue, int floorValue, MapOverlaySettings settings, IRandomSource rng);
}
/* Shared generation settings. */
public readonly struct MapGenSettings
{
public static readonly MapGenSettings Default; /* connectivity + room detection */
public static readonly MapGenSettings Dungeon; /* +dead-end removal +1 spawn */
public static readonly MapGenSettings Cave; /* connectivity + room detection */
public readonly int WallValue;
public readonly int FloorValue;
public readonly int CorridorValue;
public readonly int DoorValue;
public readonly bool EnsureConnectivity;
public readonly bool RemoveDeadEnds;
public readonly bool DetectRooms;
public readonly bool FindSpawns;
public readonly int SpawnCount;
public MapGenSettings(int wallValue, int floorValue, int corridorValue, int doorValue,
bool ensureConnectivity, bool removeDeadEnds, bool detectRooms, bool findSpawns, int spawnCount);
public static MapGenSettings Lerp(MapGenSettings a, MapGenSettings b, float t);
}
/* Algorithm settings (readonly structs; each with Default + named presets + Lerp). */
public readonly struct DrunkardWalkSettings /* presets: Default, OpenCaves, TightTunnels */
public readonly struct CellularAutomataSettings /* presets: Default, Smooth, Rough */
public readonly struct BSPSettings /* presets: Default, SmallRooms, LargeRooms */
public readonly struct RoomCorridorSettings /* presets: Default, Dense, Sparse */
public readonly struct MazeSettings /* presets: Default, Imperfect, Open */
public readonly struct MazeRoomSettings /* presets: Default, ManySmall, FewLarge, Mixed */
public readonly struct NoiseThresholdSettings /* presets: Default, Islands, Dense */
public readonly struct VoronoiRegionSettings /* presets: Default, Clustered, Sparse */
public readonly struct FloorplanSettings /* presets: Default, Apartments, OpenPlan */
public readonly struct WindingCorridorSettings /* presets: Default, Subtle, Extreme */
public readonly struct DoorPlacementSettings /* presets: Default, Sparse, Dense */
public readonly struct MapOverlaySettings /* preset: Default */
public readonly struct RoomGroup /* element of MazeRoomSettings.RoomGroups */
/* Result types. */
public readonly struct MapGenResult<TCoord> where TCoord : struct, IEquatable<TCoord>
{
public readonly List<MapRoom<TCoord>> Rooms;
public readonly List<MapCorridor<TCoord>> Corridors;
public readonly int ConnectedComponentCount;
public readonly List<TCoord> SpawnPoints;
}
public readonly struct MapRoom<TCoord> where TCoord : struct, IEquatable<TCoord>
{
public readonly int ID;
public readonly TCoord Min;
public readonly TCoord Max;
public readonly TCoord Center;
public readonly List<TCoord> Cells;
}
public readonly struct MapCorridor<TCoord> where TCoord : struct, IEquatable<TCoord>
{
public readonly int RoomIDA;
public readonly int RoomIDB;
public readonly List<TCoord> Cells;
}
/* Enums. */
public enum MapCellType { Empty = 0, Wall = 1, Floor = 2, Corridor = 3, Door = 4, Spawn = 5 }
public enum MapPatternType { Checker, DiagonalStripes, HorizontalStripes, VerticalStripes, Border }
public enum CorridorStyle { LShaped, Straight, Winding }
}
See the API reference for full signatures, remarks, and exception contracts on every member.
Settings reference
The twelve settings structs cover every dial each algorithm exposes. Build a custom instance with the constructor when none of the presets fit; the framework lerps numeric fields linearly for level-scaled generation.
MapGenSettings (shared)
| Property | Type | Default | Effect |
|---|---|---|---|
WallValue | int | 1 (MapCellType.Wall) | Cell value written for walls. |
FloorValue | int | 2 (MapCellType.Floor) | Cell value written for room floors. |
CorridorValue | int | 3 (MapCellType.Corridor) | Cell value written for corridor floors. Some algorithms (Drunkard’s Walk, Cellular Automata) write FloorValue instead. |
DoorValue | int | 4 (MapCellType.Door) | Cell value written for door positions (only by PlaceDoors). |
EnsureConnectivity | bool | true | Runs the connectivity-repair pass after generation. |
RemoveDeadEnds | bool | varies by preset | Runs the dead-end pruning pass. |
DetectRooms | bool | true | Runs the room-detection flood fill. |
FindSpawns | bool | varies by preset | Runs the spawn-point selection (SpawnCount cells). |
SpawnCount | int | 1 | Number of spawn points to find when FindSpawns = true. |
Algorithm presets
| Settings struct | Default | Other presets | Lerp support |
|---|---|---|---|
DrunkardWalkSettings | yes | OpenCaves, TightTunnels | yes |
CellularAutomataSettings | yes | Smooth, Rough | yes |
BSPSettings | yes | SmallRooms, LargeRooms | yes |
RoomCorridorSettings | yes | Dense, Sparse | yes |
MazeSettings | yes | Imperfect, Open | yes |
MazeRoomSettings | yes | ManySmall, FewLarge, Mixed | yes |
NoiseThresholdSettings | yes | Islands, Dense | yes |
VoronoiRegionSettings | yes | Clustered, Sparse | yes |
FloorplanSettings | yes | Apartments, OpenPlan | yes |
WindingCorridorSettings | yes | Subtle, Extreme | yes |
DoorPlacementSettings | yes | Sparse, Dense | yes |
MapOverlaySettings | yes | none | yes |
Each algorithm struct exposes a static Lerp(a, b, t) method that linearly interpolates numeric fields and snaps boolean / enum fields to the value of b when t > 0.5. Use it for level-scaled difficulty (sparse rooms at level 1, dense rooms at level 20) or for animated transitions between preset shapes.
Best Practices
- Seed every generation. Reproducible levels are essential for testing, debugging, replay sharing, and any “daily challenge” feature. Pass an
IRandomSourceseeded from a known integer or a player-shared seed string. See Random Utils for seeding options. - Pick the algorithm by output shape, not by name. Drunkard’s Walk and Cellular Automata both produce caves, but with different visual characters. BSP and Room-and-Corridor both produce dungeons, but BSP’s recursive partition gives more uniform results while Room-and-Corridor’s MST gives more clustered layouts.
- Always run connectivity repair on cave-like generators. Cellular Automata, Drunkard’s Walk, and Noise Threshold can produce disconnected pockets that are unreachable from the spawn point.
EnsureConnectivity = true(the default inMapGenSettings.Cave) carves tunnels to merge them into one component. - Use dead-end removal for mazes and tight corridors, not for caves.
RemoveDeadEndserodes one-neighbor floor cells; in a maze this prunes finger-corridors that lead nowhere, in a cave it would erode legitimate features.MapGenSettings.Dungeonenables it;MapGenSettings.Cavedisables it. - Layer generators for richer output. A Drunkard’s Walk pass for the base cave, a Noise Threshold pass for water, and a Random Scatter pass for vegetation together produce more visual richness than any single algorithm. Each pass operates on the same grid; order them so destructive passes (carving) run first and decorative passes run last.
- Use the
MapGenResultmetadata to drive content placement. WalkRoomsfor room-bound encounters (boss in the largest room, key in the smallest), walkCorridorsfor traps and patrol routes. Re-scanning the grid is wasted work; the metadata is already computed. - Pre-bake static dungeons. A roguelike that re-generates the dungeon every play needs the fast path; a tutorial level that should always be the same can save the generated grid to disk and load it directly via Serialization. The seed alone is not enough across builds if the generator changes; persist the grid contents.
- Tune room counts for the grid size. BSP and Room-and-Corridor cap room placement at
MaxPlacementAttempts; if the room count consistently undershoots, raise the attempt limit or relaxMinRoomSpacing. A grid that is too small for the room count will produce sparse layouts. - Use
MapGenSettings.Lerpplus an algorithm-settingsLerpfor level scaling. Pass the level index normalized to[0, 1]astto interpolate between a “low level” preset and a “high level” preset for both the cell-value flags and the algorithm-specific tuning. - Reset the grid between generations. The generators fill the grid with walls before carving, but if a previous generation left exotic cell values in unrelated cells (for example, spawned by an overlay), they will remain. Construct a fresh grid or call
grid.Fill(MapGenSettings.WallValue)before re-running. - Profile in IL2CPP release. Larger grids (256×256 and up) and high octave counts (Cellular Automata
Iterations = 8+, BSPMaxDepth = 7+) can be slow. The Editor’s debug mode is significantly slower than IL2CPP release; profile in the target build before optimizing. - Combine overlays for biome variety. A Noise Overlay places lakes; a Random Scatter places mushrooms; a Pattern Overlay places a border around the playable area. The three together turn a flat cave into a layered scene without modifying the core generation algorithm.
Pitfalls
Related
- Noise Utils
- Random Utils
- Grid
- API reference:
Scylla.Core.Util.ProceduralMapGen.ProceduralMapGenerator - API reference:
Scylla.Core.Util.ProceduralMapGen.ProceduralMapPostProcessor - API reference:
Scylla.Core.Util.ProceduralMapGen.ProceduralMapOverlay - API reference:
Scylla.Core.Util.ProceduralMapGen.MapGenSettings - API reference:
Scylla.Core.Util.ProceduralMapGen.DrunkardWalkSettings - API reference:
Scylla.Core.Util.ProceduralMapGen.CellularAutomataSettings - API reference:
Scylla.Core.Util.ProceduralMapGen.BSPSettings - API reference:
Scylla.Core.Util.ProceduralMapGen.RoomCorridorSettings - API reference:
Scylla.Core.Util.ProceduralMapGen.MazeSettings - API reference:
Scylla.Core.Util.ProceduralMapGen.MazeRoomSettings - API reference:
Scylla.Core.Util.ProceduralMapGen.NoiseThresholdSettings - API reference:
Scylla.Core.Util.ProceduralMapGen.VoronoiRegionSettings - API reference:
Scylla.Core.Util.ProceduralMapGen.FloorplanSettings - API reference:
Scylla.Core.Util.ProceduralMapGen.WindingCorridorSettings - API reference:
Scylla.Core.Util.ProceduralMapGen.DoorPlacementSettings - API reference:
Scylla.Core.Util.ProceduralMapGen.MapOverlaySettings - API reference:
Scylla.Core.Util.ProceduralMapGen.MapGenResult`1 - API reference:
Scylla.Core.Util.ProceduralMapGen.MapRoom`1 - API reference:
Scylla.Core.Util.ProceduralMapGen.MapCorridor`1 - API reference:
Scylla.Core.Util.ProceduralMapGen.MapCellType - API reference:
Scylla.Core.Util.ProceduralMapGen.MapPatternType - API reference:
Scylla.Core.Util.ProceduralMapGen.CorridorStyle