Skip to main content
Dev Corner · 10 min read

Procedural Generation for Indie Games: 5 Algorithms You Can Implement This Weekend

From noise-based terrain to dungeon generation algorithms. A practical, engine-agnostic guide to the procedural techniques behind Spelunky, Dead Cells, and Caves of Qud.

Spelunky 2 gameplay showing a procedurally generated cave level

Every run feels different. The map is never the same twice. Players sink hundreds of hours into a game with a fraction of the hand-built content.

That is the promise of procedural generation. And the good news is that the core algorithms behind games like Spelunky, Dead Cells, and The Binding of Isaac are surprisingly approachable. You do not need a math degree or a 50-person team. You need a grid, a random number generator, and a few well-chosen rules.

This guide covers five techniques that power most indie procgen games. All examples use engine-agnostic pseudocode. Pick your engine, translate the logic, and start generating.

1. Noise Functions: The Foundation of Natural Worlds

Perlin noise and its successor Simplex noise generate smooth, continuous random values across a grid. Unlike pure randomness (which produces static), noise creates coherent patterns with gentle transitions. Think rolling hills, not jagged spikes.

How it works: You sample a noise function at grid coordinates and get a value between -1 and 1. The magic happens when you layer multiple samples at different scales. This is called octave layering.

// Octave-layered noise for terrain height
function terrainHeight(x, y):
    height  = noise(x * 0.01, y * 0.01) * 50   // large hills
    height += noise(x * 0.05, y * 0.05) * 10   // medium bumps
    height += noise(x * 0.1,  y * 0.1)  * 2    // fine detail
    return height

Low frequency noise gives you continents and mountain ranges. High frequency adds rocky outcroppings and small variations. Together they produce terrain that looks natural at every zoom level.

Where it shines: Terrain generation, biome distribution, resource placement, cave density maps, weather systems. Minecraft uses multi-octave Perlin noise for its entire world. Terraria layers noise functions to create distinct underground biomes. Caves of Qud combines Perlin, Simplex, and fractal noise for its sprawling overworld.

Tip: Use noise as a decision layer, not just a height map. Sample noise to decide “should this tile be forest or desert?” based on threshold values. Two noise maps (one for temperature, one for moisture) give you a full biome system with almost no code.

2. Binary Space Partitioning: Reliable Dungeon Rooms

BSP recursively splits a rectangle into smaller rectangles, places rooms inside the leaves, then connects them with corridors. It is the workhorse of traditional dungeon generation.

How it works:

function generateBSP(area, depth):
    if depth == 0 or area is too small:
        place a random room inside the area
        return

    split area into two halves (random horizontal or vertical)
    generateBSP(leftHalf, depth - 1)
    generateBSP(rightHalf, depth - 1)
    connect a room from each half with a corridor

The recursive split guarantees rooms never overlap. Walking back up the tree to connect siblings guarantees every room is reachable. You get a fully connected dungeon with zero overlap, every time.

Where it shines: Any game with distinct rooms and corridors. The algorithm is a staple of roguelikes and dungeon crawlers. NetHack and Dungeon Crawl: Stone Soup both use BSP-based generation.

Tip: Raw BSP dungeons feel boxy. Fix this by randomizing room sizes within each partition, adding irregularly shaped rooms as post-processing, or combining BSP with cellular automata (technique #3) for organic cave sections between the structured rooms.

3. Cellular Automata: Organic Caves in 20 Lines

This is the simplest algorithm on the list and one of the most visually impressive. Based on the same principle as Conway’s Game of Life, cellular automata transform random noise into smooth, organic cave systems.

How it works:

function generateCave(width, height):
    // Step 1: Random fill (45% walls)
    for each cell in grid:
        cell = WALL if random() < 0.45 else FLOOR

    // Step 2: Smooth with the 4-5 rule (repeat 4-6 times)
    repeat 5 times:
        for each cell in grid:
            wallNeighbors = count neighbors that are WALL (8 directions)
            if cell is WALL and wallNeighbors >= 4:
                cell stays WALL
            else if cell is FLOOR and wallNeighbors >= 5:
                cell becomes WALL
            else:
                cell becomes FLOOR

After five iterations, the random static resolves into smooth, natural cave shapes. Wide caverns, narrow passages, and organic walls emerge from nothing but a fill ratio and a neighbor-counting rule.

The catch: Cellular automata do not guarantee connectivity. You may end up with isolated cave pockets. The fix is a flood fill pass after generation. Find the largest connected region and either discard the rest or tunnel between disconnected areas.

Where it shines: Natural caves, underground environments, alien landscapes, eroded ruins. Any terrain that should look formed by nature rather than designed by an architect.

Tip: Tweak the initial fill ratio to control cave density. 40% walls produces wide open caverns. 50% creates tight, claustrophobic tunnels. Run more smoothing iterations for rounder shapes, fewer for rougher edges.

4. Wave Function Collapse: Coherent Patterns from Tiles

Wave Function Collapse (WFC) is the newest technique on this list and the one generating the most excitement. It produces complex, locally coherent tilemaps from a small set of tiles and adjacency rules. Think of it like solving a Sudoku.

How it works:

function generateWFC(grid, tileSet, rules):
    // Initialize: every cell can be any tile
    for each cell in grid:
        cell.possibilities = all tiles in tileSet

    while any cell is uncollapsed:
        // Find the cell with fewest remaining options
        cell = cell with lowest entropy (fewest possibilities)

        // Collapse it to one random valid tile
        cell.tile = random choice from cell.possibilities

        // Propagate constraints to neighbors
        propagate(cell, rules)
        // Remove tiles from neighbors that violate adjacency rules

The key insight: WFC works backward from constraints. You define which tiles can sit next to each other, and the algorithm fills in a valid arrangement. A small tileset with well-defined rules can produce enormous, varied output that always looks “right.”

Townscaper by Oskar Stalberg brought WFC into the spotlight by using it to generate charming little towns from mouse clicks. Caves of Qud uses WFC specifically for building interiors (huts, crypts, lairs) while using other techniques for the overworld.

Where it shines: Tilemap decoration, building generation, town layouts, any situation where local coherence matters more than global structure. WFC excels at making things look hand-placed when they are not.

Limitation: WFC does not guarantee global properties like “there is a path from A to B.” It ensures every tile fits its neighbors, but not that the overall layout makes sense for gameplay. Use it for decoration and aesthetics, and pair it with BSP or graph-based methods for structural layout.

5. The Drunkard’s Walk: Quick and Connected

The simplest corridor generation algorithm. A virtual agent walks randomly on a grid, carving floor tiles as it goes. When it has carved enough space, it stops.

function drunkardWalk(grid, targetFloorPercent):
    position = random starting point
    floorCount = 0
    totalCells = grid.width * grid.height

    while floorCount / totalCells < targetFloorPercent:
        grid[position] = FLOOR
        floorCount += 1
        direction = random choice of [UP, DOWN, LEFT, RIGHT]
        position = position + direction
        clamp position to grid bounds

The output is always fully connected because the walker never teleports. It produces winding, organic paths that work well as cave systems or corridor networks.

Tip: Use multiple walkers starting from different points for faster coverage and more interesting shapes. Or use a biased walk (weighted toward a target direction) to create elongated tunnels between known room positions. The drunkard’s walk pairs well with BSP: use BSP for room placement, then send walkers between room centers to carve connecting corridors.

How the Pros Do It: Three Case Studies

Spelunky: The Template Guarantee

Spelunky divides each level into a 4x4 grid. First, it traces a guaranteed critical path from entrance to exit using a random walk through the grid cells. Then each cell is filled with a hand-designed room template selected based on which sides need openings. Templates are “mutated” with random block placements and enemy spawns.

The brilliance: every level is completable by construction. The critical path is guaranteed before any content is placed. Hand-crafted templates ensure quality. Randomization provides variety.

Dead Cells: The Hybrid Assembler

Dead Cells designers create individual room templates and define a graph structure for each biome describing how rooms connect. The algorithm selects and assembles templates according to the graph. Motion Twin’s philosophy: “involve the algorithm in the most restrained way possible.” The generator handles room selection and assembly. Humans handle the design of every individual room and every connection rule. If you want to understand what makes games like Dead Cells feel so tight, this hybrid approach is the answer.

Hades: The Anti-Procgen Roguelike

Supergiant Games deliberately rejected procedural room generation for Hades. Every room is hand-designed and hand-painted. The randomization happens at the selection level: which rooms appear, which enemies spawn, which rewards are offered.

This proves something important. You do not need algorithmic room generation to make a roguelike. Handcrafted rooms with randomized selection, encounters, and rewards can create the same “every run feels different” experience without sacrificing art direction or level design quality.

The Pitfalls That Kill Procgen Projects

The “1000 Bowls of Oatmeal” problem. Your system generates a thousand unique levels that are technically different but feel identical. The fix: ensure your generation creates recognizably different situations, not just different tile arrangements. If the player’s strategy never changes between levels, your variety is cosmetic.

No seed logging. Bugs in procedural systems are nearly impossible to reproduce if you cannot recreate the exact output. Always use a seed-based random number generator and log the seed. Players can share interesting worlds. You can reproduce and fix bugs.

Skipping validation. A generated level that is unbeatable is worse than no generation at all. After every generation pass, run automated checks. Is there a path from start to exit? Are resources reachable? Is the difficulty within acceptable bounds? Reject and regenerate anything that fails.

Over-engineering before playtesting. No amount of parameter tweaking replaces the insight from playing 200 generated levels. Build the simplest version first. Play it. Then decide what needs more sophistication. If you are just starting out with game development, our beginner’s guide covers the fundamentals of scoping and shipping.

Where to Start

Pick the algorithm that matches your game:

  • Top-down dungeon crawler? Start with BSP + drunkard’s walk corridors
  • Cave explorer? Cellular automata with flood fill connectivity
  • Open world / survival? Noise-based terrain with biome thresholds
  • Tile-based town or decoration? Wave Function Collapse
  • Roguelike with tight design? Hand-crafted rooms with randomized assembly (the Hades/Dead Cells approach)

Start simple. A BSP dungeon generator with 20 hand-designed room templates will be more fun than a sophisticated algorithm with no authored content. The goal is not for the player to marvel at your algorithm. The goal is for generated levels to feel like someone designed them on purpose.

Resources for Going Deeper

  • Red Blob Games (redblobgames.com) has the best visual, interactive tutorials for noise, pathfinding, and grid-based algorithms
  • RogueBasin (roguebasin.com) is a wiki packed with roguelike development techniques, including BSP and cellular automata walkthroughs
  • Boris the Brave (boristhebrave.com) publishes detailed technical breakdowns of how Binding of Isaac and Enter the Gungeon generate levels
  • “End-to-End Procedural Generation in Caves of Qud” by Jason Grinblat (GDC 2019) is the best talk on layering multiple procgen systems into a coherent game

Your first procedural level will not be pretty. Your hundredth will surprise you. Start with one algorithm, get it generating playable output, and iterate from there.

#procedural-generation #roguelike #level-design #game-dev #tutorial #algorithms
Florian Huet

Written by

Florian Huet

iOS dev by day, indie game dev by night. Trying to give life to GameDō Studio.

Building games and talking about the ones I can't stop playing.

Related Articles