Tags: unity, procedural-generation, perlin-noise, cellular-automata, l-systems, spawn-tables, seed, game-dev Last updated: 2026-06-27

Procedural Generation Cheatsheet

Quick Reference

TechniqueBest For
Perlin NoiseTerrain heightmaps, caves, clouds
Cellular AutomataCave systems, organic patterns
L-SystemsTrees, plants, branching structures
Spawn TablesLoot, enemy placement, room contents
Voronoi / Worley NoiseCell-like patterns, biomes
Wave Function CollapseTile-based level generation

Perlin Noise

Unity API

float value = Mathf.PerlinNoise(x, y);   # Returns 0.0–1.0

# 2D noise
float n = Mathf.PerlinNoise(x * scale, y * scale);

# 3D noise (third dimension = time / z-axis)
float n3d = Mathf.PerlinNoise(
    x * scale + z * scale,
    y * scale + z * scale);

Terrain Heightmap

float[,] GenerateHeightmap(int width, int height, float scale) {
    float[,] map = new float[width, height];
    for (int y = 0; y < height; y++)
    for (int x = 0; x < width; x++)
        map[x, y] = Mathf.PerlinNoise(
            x / (float)width * scale,
            y / (float)height * scale);
    return map;
}

Octave Noise (Fractal / FBM)

float OctaveNoise(float x, float y, int octaves, float persistence) {
    float value = 0f;
    float amplitude = 1f;
    float frequency = 1f;
    float maxValue = 0f;

    for (int i = 0; i < octaves; i++) {
        value += Mathf.PerlinNoise(x * frequency, y * frequency) * amplitude;
        maxValue += amplitude;
        amplitude *= persistence;
        frequency *= 2f;
    }
    return value / maxValue;
}
ParameterEffectTypical
OctavesDetail layers (3–8)4–6
PersistenceAmplitude drop per octave (0–1)0.5
LacunarityFrequency increase per octave2.0
ScaleOverall zoom0.01–0.1 for world scale

Noise Applications

UseSetup
Terrain heightnoise > threshold = land
Cave generationSeparate noise for ceiling + floor
Ore veins3D noise > 0.7 = ore block
Biome mapsTemperature + moisture noise
Cloud covernoise * alpha for transparency

Cellular Automata

Cave Generation

bool[,] GenerateCaves(int width, int height, int iterations) {
    bool[,] map = new bool[width, height];

    # Step 1: Random fill (45% wall)
    for (int y = 0; y < height; y++)
    for (int x = 0; x < width; x++)
        map[x, y] = Random.value < 0.45f;

    # Step 2: Apply rules repeatedly
    for (int i = 0; i < iterations; i++)
        map = ApplyCellularRules(map);

    return map;
}

bool[,] ApplyCellularRules(bool[,] map) {
    int w = map.GetLength(0), h = map.GetLength(1);
    bool[,] next = new bool[w, h];

    for (int y = 0; y < h; y++)
    for (int x = 0; x < w; x++) {
        int neighbours = CountWallNeighbors(map, x, y);
        if (map[x, y])
            next[x, y] = neighbours >= 4;    # Wall survives
        else
            next[x, y] = neighbours >= 5;    # Empty becomes wall
    }
    return next;
}

Cellular Automata Rules

RuleWall→Empty→Effect
4-5Survives if ≥4Born if ≥5Standard caves
B5678/S45678SurvivesBornDense caverns
B3/S12345Born if 3Survives if 1–5Maze-like

L-Systems (Trees & Plants)

String Rewriting

string GenerateLSystem(string axiom, Dictionary<char, string> rules,
                       int iterations) {
    string current = axiom;
    for (int i = 0; i < iterations; i++) {
        string next = "";
        foreach (char c in current)
            next += rules.ContainsKey(c) ? rules[c] : c.ToString();
        current = next;
    }
    return current;
}

Plant Example

Axiom:    F
Rules:    F → FF+[+F-F-F]-[-F+F+F]
Angle:    25°
# F = draw forward, +/- = rotate, [/] = push/pop state (branch)

Turtle Graphics Interpreter

void DrawLSystem(string instructions, float length, float angle) {
    Stack<TransformState> stack = new();
    foreach (char c in instructions) {
        switch (c) {
            case 'F': MoveForward(length); break;
            case '+': Rotate(angle); break;
            case '-': Rotate(-angle); break;
            case '[': stack.Push(SaveState()); break;
            case ']': RestoreState(stack.Pop()); break;
        }
    }
}

Spawn Tables

Weighted Random

public GameObject PickWeighted(List<SpawnEntry> entries) {
    float total = entries.Sum(e => e.weight);
    float roll = Random.Range(0f, total);
    float running = 0f;

    foreach (var entry in entries) {
        running += entry.weight;
        if (roll <= running) return entry.prefab;
    }
    return entries[0].prefab;
}

Tiered Tables

public GameObject RollTiered() {
    float roll = Random.value;
    if (roll < 0.01f)  return PickWeighted(legendaryTable);   # 1%
    if (roll < 0.06f)  return PickWeighted(rareTable);        # 5%
    if (roll < 0.26f)  return PickWeighted(uncommonTable);    # 20%
    return PickWeighted(commonTable);                         # 74%
}

Seed Management

Setting and Using Seeds

public int seed;

void Start() {
    if (seed == 0) seed = Random.Range(1, int.MaxValue);
    Random.InitState(seed);
    GenerateWorld();
}

Deterministic Random

# System.Random for isolated RNG
System.Random rng = new System.Random(seed);
float randomFloat = (float)rng.NextDouble();
int randomInt = rng.Next(min, max);

# Hash-based pseudo-random for coordinate lookups
float HashNoise(int x, int y, int seed) {
    int hash = seed;
    hash = hash * 1664525 + x * 1013904223 + y;
    hash = (hash >> 13) ^ hash;
    return (float)(hash & 0x7fffffff) / 0x7fffffff;
}

Seed Debugging

[ContextMenu("Regenerate")]
void Regenerate() {
    Random.InitState(seed);
    GenerateWorld();
}

PlayerPrefs.SetInt("WorldSeed", seed);
Debug.Log($"World seed: {seed}");

Practical Recipes

Dungeon Room Placement

  1. Generate rooms with random size/position.
  2. Separate overlapping rooms (push apart).
  3. Connect rooms with corridors (A* or L-shaped).
  4. Place doors at corridor-room intersections.
  5. Populate rooms from spawn tables.

Island Generation

float[,] heightmap = GenerateHeightmap(256, 256, 0.01f);

# Apply falloff (higher edges → island shape)
for (int y = 0; y < 256; y++)
for (int x = 0; x < 256; x++) {
    float dx = (x / 128f) - 1f;
    float dy = (y / 128f) - 1f;
    float distance = Mathf.Sqrt(dx*dx + dy*dy);
    float falloff = 1f - Mathf.Clamp01(distance / 1.2f);
    heightmap[x, y] *= falloff;
}

Biome Assignment

Height →
Moisture ↓
LowMediumHigh
HighTundraForest
MidDesertGrasslandForest
LowDesertBeach
Biome GetBiome(float height, float moisture) {
    if (height > 0.7f) return moisture > 0.5f ? Biome.Rainforest : Biome.Tundra;
    if (height > 0.4f) return moisture > 0.5f ? Biome.Forest : Biome.Grassland;
    return moisture > 0.3f ? Biome.Beach : Biome.Desert;
}

Wave Function Collapse (Overview)

  1. Define tileset with adjacency rules (which tiles can touch).
  2. Start with all tiles possible in every cell (superposition).
  3. Collapse cell with lowest entropy to one tile.
  4. Propagate constraints to neighbours.
  5. Repeat until all cells collapsed or contradiction.

Tile Adjacency Rules

TileNorthEastSouthWest
GrassGrass, DirtGrass, DirtGrassGrass, Dirt
DirtGrass, DirtDirt, StoneDirt, StoneDirt
StoneDirt, StoneStoneStoneDirt, Stone
WaterWaterWater, SandWater, SandWater
SandWater, Sand, GrassSandSandWater, Sand