Procedural Generation

Procedural generation replaces authored content with an algorithm plus a seed. Its appeal is obvious — infinite variety, tiny download size, replayability — and its failure mode is equally well known: output that is technically varied and experientially identical. No Man's Sky at launch and a thousand roguelike prototypes since demonstrate the same lesson, which is that variety is not the same as interestingness.

The technique that consistently works in shipped games is hybrid generation: hand-authored pieces assembled procedurally. Authored rooms placed by an algorithm, authored biomes distributed by noise, authored encounter templates seeded with varying enemies. The generator provides novelty and the human provides quality, and you get both. Pure generation is best reserved for the layers where players don't expect authorship — terrain, foliage, texture detail, star fields.

TL;DR

Quick Example

A seeded terrain heightmap using fractal noise — the pattern under most procedural landscapes:

Three ideas carry most of the result: one seed drives everything, fbm turns flat noise into a landscape, and a second independent noise field (moisture) crossed with the first produces biomes that vary in two dimensions rather than in bands.

Core Concepts

Seeded randomness

A seed makes generation a pure function. That gives you shareable worlds ("try seed 8675309"), reproducible bug reports, and a save file that stores an integer instead of a terrain mesh.

Two rules matter in practice. First, derive sub-seeds rather than sharing one generator across systems, so adding a call to the terrain generator doesn't shift every enemy placement:

Second, don't rely on the language's default RNG for cross-platform determinism — implementations differ between versions and platforms. Ship your own PRNG (PCG, xoshiro, splitmix64) if seeds must produce identical worlds everywhere.

Noise

Random values per point look like static. Noise functions produce values that vary smoothly across space, which is why they resemble natural phenomena.

Fractal Brownian motion

One octave of noise gives rolling hills. Real terrain has detail at every scale, which fbm produces by summing octaves at doubling frequency and halving amplitude:

Variations shape the character: ridged noise (1 - |noise|) makes mountain ridges, turbulence (|noise|) makes billowing clouds, and domain warping (feeding noise into noise's coordinates) produces the organic, non-repetitive look that distinguishes good procedural terrain from obviously algorithmic terrain.

Redistribution

Raw noise is roughly normally distributed — mostly mid-height, which reads as endless rolling hills. Reshaping the output is what creates recognizable landforms:

Level Generation

BSP (binary space partitioning)

Recursively split a rectangle, place a room in each leaf, then connect siblings back up the tree.

Rooms never overlap and the tree structure guarantees connectivity for free. The output reads as architectural — right for buildings, dungeons, and space stations; wrong for caves.

Cellular automata

Start with random noise, then repeatedly apply a smoothing rule. Organic caves emerge in a handful of iterations.

The catch: cellular automata routinely produce disconnected caverns. Always flood-fill afterward, keep the largest region, and either discard the rest or tunnel connections to them.

Wave function collapse

WFC generates output that locally resembles a small input example, by treating generation as constraint propagation. Each cell starts as a superposition of all possible tiles; collapse the lowest-entropy cell to one option, propagate the adjacency constraints to its neighbors, repeat.

It's exceptionally good at tile-based content — towns, dungeons, level chunks — that looks hand-placed because every local neighborhood matches an authored example. The costs are real: contradictions require backtracking or restarts, global structure isn't guaranteed (WFC has no concept of "the exit must be reachable"), and performance degrades with large tile sets.

Choosing an approach

Validation

The generator's output must be playable. This is where most procedural games actually fail, and it's mostly a solved engineering problem.

Generate-and-test is unglamorous and extremely effective: generate, run a checker, retry with a nudged seed on failure. A flood fill answers reachability; a pathfinding pass answers navigability; a simulated playthrough answers "can this be completed." Keep an authored fallback level for the case where the generator can't produce a valid result — players tolerate a familiar level far better than an impossible one.

⚠️ Warning: Bugs in generated content are only reproducible if you log the seed. Always print or save the seed with every generated level, and make it settable from a debug console.

Best Practices

Combine authored and generated content

Author rooms, encounters, and set pieces; let the algorithm choose and arrange them. This is what Spelunky, Dead Cells, Hades, and most successful roguelikes do. The generator supplies novelty; the human supplies the guarantee that each piece is worth encountering.

Design for the median output, not the best one

It's easy to fixate on the spectacular seed. Players see hundreds of ordinary ones. Generate a hundred outputs and evaluate the batch — the boring middle is your actual game.

Constrain rather than filter

Generate-and-test works, but building the constraint into the generator is faster and produces better results. If enemies must not spawn within sight of the start, exclude that region from the spawn candidates rather than generating and rejecting.

Derive sub-seeds per system

One shared RNG means that adding a single call anywhere shifts every downstream result, invalidating every existing seed. Independent streams per system keep worlds stable as you develop.

Vary the meaningful axes

A hundred layouts with identical pacing feel identical. Vary the things players perceive — difficulty curve, resource scarcity, encounter composition, spatial rhythm — before varying cosmetic details.

Use noise for placement, not just terrain

Noise fields drive foliage density, enemy population, weather, and resource distribution. Sampling one field for several purposes correlates them naturally: dense forest where moisture is high, and predators where prey is.

Generate at multiple scales

Continents, then regions, then local terrain, then detail. A hierarchical generator produces coherent worlds and lets you generate only what's near the player, which is how open worlds stay in memory.

Common Mistakes

Using unseeded randomness

Not verifying connectivity

Confusing variety with interest

Raw noise with no redistribution

Generating everything up front

Because generation from a seed is a pure function, a chunk can be discarded and regenerated identically — which is why chunked worlds don't need to persist terrain, only player modifications.

Relying on a platform RNG for shared seeds

FAQ

Which noise function should I use?

OpenSimplex or simplex noise for most purposes — smoother than Perlin, fewer directional artifacts, and better performance in three and four dimensions. Value noise is fine for cheap detail layers. Worley/cellular noise is the right tool for anything cell-structured: stone, cracks, scales, water caustics. If your engine ships one, start with it and change only if you can see the artifacts.

How do I make generated worlds feel handcrafted?

Author the pieces and generate the arrangement. Beyond that: guarantee landmarks (a distinctive structure per region), vary pacing deliberately rather than uniformly, use wave function collapse or template stitching so local neighborhoods match authored examples, and place set pieces at fixed narrative beats. The generated portion should be the connective tissue, not the memorable moments.

Should I generate at runtime or offline?

Runtime for infinite or replayable worlds, and for saving download size — but you must guarantee validity and bounded generation time. Offline when you want full quality control and can afford the storage: generate a large batch, curate the good ones, ship those. Hybrid is common — offline-generated regions with runtime detail and scatter.

How do I handle multiplayer?

Share the seed and use an identical deterministic generator on every client, so nobody transmits terrain. This requires the same PRNG implementation and the same floating-point behavior on every platform — the cross-platform determinism problem again. The alternative is server-side generation with the results replicated, which is simpler and costs bandwidth. See Multiplayer Netcode.

Can I use AI models for procedural generation?

Yes, in specific places: diffusion models for textures and concept art, LLMs for item descriptions, quest text, and dialogue variation, and learned models for level layout trained on authored examples. The practical constraints are the usual ones — nondeterminism, latency, cost per generation, and the difficulty of guaranteeing that output is valid and on-brand. Most shipping uses treat model output as an authoring accelerator rather than a runtime generator.

How do I store a procedurally generated world?

Store the seed and the diffs. Because generation is a pure function of the seed, the world can always be regenerated; you only need to persist what the player changed — blocks broken, structures built, chests looted. This is exactly how voxel games keep save files small, and it's the main practical payoff of keeping generation deterministic.

Related Topics

References