Guide
Procedural generation in games explained: noise, dungeons, seeds, and replayability
Procedural generation is how games build levels, terrain, loot, and entire worlds from algorithms instead of hand-placing every tile. Minecraft mountains, roguelike dungeon layouts, and randomized enemy spawns all share the same core idea: a compact set of rules plus a seed produces vast variety at runtime. Done well, players get fresh experiences every run without an art team painting millions of unique maps. Done poorly, you get impossible dead ends, unfair loot spikes, or visually repetitive noise blobs. This guide walks through the main techniques — gradient noise for landscapes, graph and cellular methods for dungeons, weighted tables for drops — and how they connect to your game loop, ECS architecture, and pathfinding systems.
What procedural generation actually means
At its simplest, procedural generation (often shortened to procgen) is any technique where content is created by code at load time or during play rather than stored fully authored on disk. The spectrum runs from parameterized prefabs — a designer builds ten room templates and the generator stitches them — to fully algorithmic output where no human ever saw that exact cave layout before the player entered it.
Three properties matter in production:
- Determinism — given the same seed and version of your generator, you reproduce identical output. Essential for debugging, replays, and multiplayer sync.
- Constraints — pure randomness creates garbage; good procgen encodes design rules (minimum path length, no unwinnable states, biome adjacency limits).
- Cost — generation may run once at world creation, incrementally per chunk as the player moves, or lazily when a door opens. Each choice affects frame budgets discussed in the frame timing guide.
Procgen is not the opposite of hand-crafted design. Most shipped games blend both: artists sculpt tile sets and encounter templates; algorithms arrange them. Even “infinite” worlds usually sample from a finite palette of authored assets.
Seeds and random number generators
A seed is an integer (or string hashed to an integer) that initializes a
pseudo-random number generator (PRNG). Languages expose helpers like
Math.random() in JavaScript or rand::rng() in Rust, but
Math.random() alone is a poor procgen foundation: you cannot easily replay a
bug report from seed 42,842,901 if other systems also consumed random draws in
non-deterministic order.
Best practice: use a seeded PRNG per subsystem or per chunk. Split seeds with a hash function so terrain noise and enemy placement do not share one stream — a terrain tweak should not reshuffle loot three rooms away. Popular choices include PCG, xoshiro, and Mulberry32 for speed; avoid slow cryptographic RNGs unless security matters.
Expose seeds to players when replayability is a feature (roguelikes, speedrun communities). Hide them when you need to patch generator bugs without invalidating old saves — version your generator and migrate or invalidate per policy.
Noise functions: Perlin, Simplex, and layered terrain
Heightmaps and cave systems often start with gradient noise. Ken Perlin’s classic noise returns smooth pseudo-random values in [-1, 1] that change gradually across space — unlike white noise, neighboring samples correlate, producing hills instead of static. Simplex noise is a faster variant with fewer directional artifacts, common in modern engines.
Single-octave noise looks blobby. Real terrain stacks octaves (fractal Brownian motion): sample noise at 1x, 2x, 4x frequency with decreasing amplitude. Low frequencies shape continents; high frequencies add rocky detail. Parameters to tune:
- Frequency — how quickly values change across world units.
- Amplitude — vertical scale of each octave.
- Lacunarity — frequency multiplier per octave (often 2.0).
- Persistence — amplitude multiplier per octave (often 0.5).
Threshold the combined noise to classify biomes: elevation above 0.6 becomes snow, moisture noise below 0.3 becomes desert. Blend rules prevent absurd transitions (snow directly adjacent to lava) with constraint passes after the noise pass.
For 3D voxel caves, sample 3D noise and carve where density falls below a cutoff. Add tunnel bias vectors or worm algorithms on top so players get navigable passages, not Swiss cheese. Generated geometry must still satisfy collision and navigation mesh requirements before AI can pathfind.
Dungeon and level layout algorithms
Indoor procgen usually operates on discrete grids or graphs. Common patterns:
Binary space partitioning (BSP)
Recursively split a rectangle into smaller rooms until cells hit a minimum size, then connect sibling rooms with corridors. BSP guarantees non-overlapping rectangular rooms and predictable room counts — ideal for top-down shooters and classic roguelikes. Post-process to add irregular walls or prop scatter.
Cellular automata caves
Fill a grid randomly, then iterate rules: if a wall cell has fewer than four wall neighbors, flip to floor (or the inverse, depending on variant). After several passes you get organic cave shapes. Flood-fill to remove disconnected regions; carve entrance/exit paths; verify reachability with BFS before committing — the same graph search ideas as A* pathfinding, but run once at generation time.
Prefab graphs and grammars
Designer-authored room templates carry sockets (door north, door south). The generator maintains a frontier of open sockets, picks a compatible room, and rejects placements that overlap or block critical paths. Games like Spelunky and many action roguelikes use constrained prefab stitching for authored feel with combinatorial variety.
Wave Function Collapse (WFC)
WFC treats each tile as a superposition of allowed neighbors from a ruleset, collapsing cells propagating constraints until the grid is resolved or a contradiction triggers backtracking. Excellent for tile-based maps that must respect adjacency (roads, cliffs, building facades). Contradictions are common — wrap generation in retry loops with new seeds and cap attempts for production stability.
Loot tables, encounters, and weighted randomness
Not all procgen is spatial. Loot tables map weighted entries to drops: 60% common arrow, 25% uncommon potion, 10% rare sword, 5% legendary relic. Implementation is a cumulative distribution — roll one float in [0, 1), walk the table — or alias method sampling for O(1) picks on large tables.
Pity timers and bad-luck protection adjust weights after dry streaks so players do not rage-quit; disclose rates if your game touches regulated loot-box territory. Separate presentation randomness (VFX shuffle) from outcome randomness (stats) so UI polish does not desync authoritative state in multiplayer.
Encounter tables pair enemy compositions with biome tags and difficulty curves. Scale spawn budgets by player power or depth level so procedural runs trend harder without manual per-floor tuning.
Streaming, chunks, and performance
Open-world procgen rarely materializes the entire planet at once. The world divides into chunks keyed by coordinates; only chunks near the player generate or load. Each chunk derives a sub-seed from the world seed and chunk index so neighbors align seamlessly at boundaries — sample noise in continuous world space, not per-chunk local space, or seams appear.
Heavy generation belongs off the hot path: background threads, Web Workers in browsers, or amortized across frames with budgets (“spend 2 ms on cave fill this tick”). Cache generated chunks to disk with a format version byte so patches can invalidate stale data. For browser games, consider compiling hot noise loops to WebAssembly when JavaScript profiling shows generation dominating the update phase.
In an ECS architecture, spawn entities after chunk commit: terrain colliders, prop transforms, enemy spawners as components. Keep generation systems separate from per-frame simulation systems so you can disable or throttle procgen without touching gameplay code.
Design pitfalls: when procgen feels unfair or boring
Players forgive repetition more than unwinnable states. Automated validation should assert:
- Start-to-goal path exists (graph reachability).
- Critical items are not behind impossible locks.
- Soft-lock checks — can the player always progress given current inventory?
- Density caps — not every tile spawns an enemy; use Poisson disk sampling for natural spacing.
Recognizability fights variety. If every cave uses the same three room shapes, procgen adds little. Inject authored landmarks every N floors so players orient. Macro variation (biome rotation, set-piece rooms) layered on micro variation (prop jitter, lighting hue) reads as high quality without exponential art cost.
Log generator metrics in development: rejection rates, average corridor length, loot distribution histograms. A spike in WFC contradictions or BSP failures often signals a rule change that needs tuning before players hit it in production.
Practical checklist for shipping procgen
- Version your generator — bump when algorithms change; handle old saves.
- Isolate RNG streams — terrain, loot, and VFX do not share one sequence.
- Validate outputs — automated reachability and constraint checks before spawn.
- Profile generation cost — budget ms per frame; move heavy work async.
- Blend authored and generated — prefabs and landmarks anchor quality.
- Expose debug seeds — reproduce bug reports from player-submitted codes.
- Test edge seeds — fuzz thousands of seeds in CI; catch rare contradictions.
Procedural generation is a force multiplier for small teams and infinite-replay genres. The algorithms are approachable; the craft is in constraints, validation, and knowing when to stop randomizing and start hand-placing the moment that makes a level memorable.
Related reading
- Pathfinding in games — A*, navmeshes, and validating generated level connectivity
- Entity component systems — spawning and simulating procgen content at scale
- Game loop and frame timing — budgeting generation work across frames
- Collision detection — colliders for generated terrain and dynamic props