Intro

Demonstration of Procedural Content Generation in Unity to create terrain with distinct biomes (e.g., hills + mountains) using Perlin noise, hierarchical nodes, and mapping functions for controlled feature placement.

Setup

  • Create a Terrain object in Unity.
  • Add a C# script (e.g., BiomeTerrainGenerator).
  • Include using UnityEngine;.
  • Start with a flat base terrain to allow additive/subtractive shaping.
  • Use seeded randomness for reproducibility.

Saving and Loading Configurations

  • Create a ScriptableObject to store terrain configuration (noise settings, max/base values, node hierarchy).
  • To save a configuration, click the save button

  • To load a configuration:

    • Drag the ScriptableObject into the config field in your terrain generator script.
    • Click a Load button (or call a LoadConfig() function) to synchronize all nodes with the selected configuration.
  • Always load after switching configs to ensure all nodes reference the current ScriptableObject.

  • Use duplicate or versioning (Ctrl+D / Cmd+D) to experiment with multiple terrain setups.

Trapezoid & Mapping Curves

Trapezoid functions are used to control where features appear on your terrain. In PCG, they often serve as fall-off maps or distance-based masks, defining boundaries for landmasses like islands, plateaus, or biome regions.

Mathematical Logic

  • A 1D trapezoid is defined by four points: (start rise), (start plateau), (end plateau), (end fall).

  • Terrain logic:

    • Peak Area: Heights remain at 100% of the noise value.
    • Sloped Area: Heights interpolate linearly from the peak to the base.
    • Base Area: Heights are clamped to 0 (water level).
  • Often simplified to a radial or square gradient for 2D terrain.

Trapezoid Example Table

| Element | x   | y |
| ------- | --- | - |
| 0       | 0.5 | 0 |
| 1       | 0.6 | 1 |
| 2       | 1   | 1 |
| 3       | 1   | 0 |

PCG Nodes Overview

Abstract

Terrain
 └── Biomes
      β”œβ”€β”€ MountainBiome
      β”‚     β”œβ”€β”€ MountainBase
      β”‚       └── MountainDetail
      β”‚
      └── HillsBiome
            β”œβ”€β”€ HillsBase
            β”œβ”€β”€ HillDetail
            └── Stones

Terrain (Root) Node

  • Turn off Perlin Scalar and set Max Value to ~0.2 to provide room for carving away (subtracting) from terrain

Terrain Node

  • Max Value: 0.212
  • Perlin Scalar: 0
  • Gen Noise Type: 2
  • Process Parent Type: 0
  • Combine Type: 0
description

Biomes

  • The Biomes node will tell us the probability we’re in a particular node
  • Perlin Scalar = 4.1
  • Max Value: 0.825

Biomes Node

  • Max Value: 0.79
  • Perlin Scalar: 4.1
  • Gen Noise Type: 0
  • Process Parent Type: 0
  • Combine Type: 0
description

Biome: Mountains

Goal: Large peaks with height variation and detail.

  • Child nodes
    • MountainBase β†’ low-frequency noise for broad mountain shapes.
    • MountainDetail β†’ high-frequency noise for peaks and subtle features.

Create MountainBiome node

description
  • Add a Mountain biome node under the root probability map.
  • Assign low-frequency Perlin noise for base shape.
  • Use trap or mapping curves to select mountain regions.
    • Example: values 0.5–0.6 β†’ plateau β†’ mountains appear only in this range.
  • Define trapezoid (shown at Max Value = 1), then set to 0

MountainBiome Node

  • Max Value: 0
  • Perlin Scalar: 0
  • Gen Noise Type: None
  • Process Parent Type: Trapezoid [0.45, 0.6, 1, 1]
  • Combine Type: Multiply
description

MountainBase

MountainBase Node

  • Max Value: 0.361
  • Perlin Scalar: 7.4
  • Gen Noise Type: 0
  • Process Parent Type: 0
  • Combine Type: 0
description

MountainDetail

MountainDetail Node

  • Max Value: 0.24
  • Perlin Scalar: 28.4
  • Gen Noise Type: 0
  • Process Parent Type: 0
  • Combine Type: 0
description

Refine Features

  • Smooth curves to remove artifacts from trapezoid function.
  • Adjust max/base values to control intensity.
  • Lower trapezoid slope

    description
  • lower mountain base (max = 0.361)

description

Tips:

  • Only mountain child nodes should affect mountain areas.
  • Use mapping curves for smoother transitions.

Biome: Hills

Goal: Rolling terrain with smaller hills and scattered features.

  • Use trap or mapping curves to define hill regions.
  • HillBase β†’ basic hill shapes.
  • HillDetail β†’ curl/noise for surface texture.
  • Stones β†’ isolate high noise values for small rocks.

Create HillsBiome Node

  • Add a Hills biome node under the root probability map.
  • Assign mid-frequency Perlin noise for hills.
  • raised to 1 to show shape

HillsBiome Node

  • Max Value: 0.241
  • Perlin Scalar: 0
  • Gen Noise Type: None
  • Process Parent Type: Trapezoid [0, 0, 0.25, 0.5]
  • Combine Type: Multiply
description

HillsBase

HillsBase Node

  • Max Value: 0.263
  • Perlin Scalar: 8.4
  • Gen Noise Type: 0
  • Process Parent Type: 0
  • Combine Type: Add
description

Refine Features

  • Blend transitions between hills and mountains using mapping curves.
  • Avoid doubling contributions by carefully managing child hierarchy.
  • Adjust HillsBiome to make hills smoother
    • Trapezoid: [0, 0, 0.25, 0.5]
      • The hill is fully engaged from 0 to 0.25 and then ramps down to 0.5
img
  • Adjust hillsBase down a bit, max = 0.263

HillDetail

HillDetail Node

  • Max Value: 0.023
  • Perlin Scalar: 28.9
  • Gen Noise Type: 0
  • Process Parent Type: 0
  • Combine Type: 0
description

Stones

Stones Node

  • Max Value: 0.223
  • Perlin Scalar: 18.2
  • Gen Noise Type: Perlin Noise with mapping curve
  • Process Parent Type: *Zero Out
  • Combine Type: 0
  • Use Perlin Noise with Mapping Curve to isolate the highest values (stal)
description
  • Create sunken/erosion areas around the stones
description
  • Can switch to subtract to create pockmark areas
description

Tips:

  • Curves control placement frequency and intensity of stones or small features.

Adjust

  • In HillsBiome, the linear interpolation from the trapezoid function caused an artifact
  • Switch to Mapping Curve β†’ Process Parent Curve to use smoother, non-linear curves
description

Visualization & Testing

  • Visualize probability maps for biome coverage.
  • Adjust terrain max/base values to see feature placement.
  • Test different config versions and tweak curves for smoother transitions.
  • Keep code modular: separate noise generation, mapping, and object placement.

Final Config

Abstract

Terrain (Perlin: 0, Max: 0.212)
 └── Biomes (Perlin: 4.1, Max: 0.79)
      β”œβ”€β”€ MountainBiome (Perlin: 0, Max: 0, Mask: Trapezoid [0.45, 0.6, 1, 1], Combine: Multiply) 
      β”‚     β”œβ”€β”€ MountainBase (Perlin: 7.4, Max: 0.361)
      β”‚     β”‚     └── MountainDetail (Perlin: 28.4, Max: 0.24)
      β”‚
      └── HillsBiome (Perlin: 0, Max: 0, Mask: Mapping Curve [[0, 0, 0.25, 0.5], Combine: Multiply)
            β”œβ”€β”€ HillsBase (Perlin: 8.4, Max: 0.263)
            β”‚     β”œβ”€β”€ HillDetail (Perlin: 28.9, Max: 0.023)
            β”‚       └── Stones (Perlin: 18.2, Max: 0.223, Perlin Noise with mapping curve, zero out)
0.0 ─────── hills
0.3 ─────── hills
0.5 ─────── mountains
0.7 ─────── mountains
1.0 ─────── maybe nothing (depends)

Procedural Map Overview

How the Map Works

1. Terrain Node (Max 0.212)

  • The root height range; everything else adds/subtracts from this.

2. Biomes Node (Perlin 4.1, Max 0.79)

  • Generates a noise map from 0 β†’ 1.
  • Each child biome only activates in a subrange of that map.

MountainBiome (0.45 – 0.6 slice)

  • Only points where the Biomes noise is between 0.45 and 0.6 contribute to mountains.

  • Combine Multiply β†’ multiplies this mask with the child Perlin noise:

    • MountainBase (7.4, Max 0.361)
    • MountainDetail (28.4, Max 0.24)
Biomes value β†’ MountainBiome
0.0 ─────── no
0.45 ────── start
0.525 ───── full
0.6 ─────── end
0.7 ─────── no
1.0 ─────── no

HillsBiome ([0 – 0.25 β†’ fade to 0.5])

  • This is a trapezoid/fading mask: full hills from 0 β†’ 0.25, then taper off to 0 at 0.5.

  • Combine Multiply β†’ child nodes inherit the mask:

    • HillsBase (8.4, Max 0.263)
    • HillDetail (28.9, Max 0.023)
    • Stones (18.2, Max 0.223, Thresholded)
Biomes value β†’ HillsBiome
0.0 ───── full
0.25 ──── full
0.5 ───── fade to 0
0.6 ───── 0
1.0 ───── 0
 

Putting it all together

0.0 ───── Hills
0.25 ─── Hills still full
0.3 ───── Hills fading, mountains still 0
0.45 ──── Mountain start
0.5 ───── Mountain full, hills fading
0.6 ───── Mountain end
0.7 ───── Nothing (or minimal)
1.0 ───── Nothing
 
Perlin Noise and Max Value Breakdown

1. Terrain (Max: 0.212)

  • Top-level terrain node.
  • Max: 0.212 – caps overall height/amplitude.

2. Biomes (Perlin: 4.1, Max: 0.79)

  • Defines where biomes appear.
  • Perlin: 4.1 – frequency of variation (low = smooth, high = choppy).
  • Max: 0.79 – amplitude of the biome mask.

3. MountainBiome (Mask: Trapezoid [0.45–0.6], Combine: Multiply)

a. MountainBase (Perlin: 7.4, Max: 0.361)

  • Frequency of base mountain noise.
  • Max caps base mountain height.
i. MountainDetail (Perlin: 28.4, Max: 0.24)
  • Adds fine-grained detail (small rocks/ridges).
  • High frequency β†’ tight jagged noise.

4. HillsBiome (Mask: [0–0.25 β†’ fade to 0.5], Combine: Multiply)

a. HillsBase (Perlin: 8.4, Max: 0.263)

  • Base hills; slightly higher frequency than mountains.
i. HillDetail (Perlin: 28.9, Max: 0.023)
  • Tiny bumps for subtle roughness.
ii. Stones (Perlin: 18.2, Max: 0.223, Thresholded)
  • Adds scattered rocky details.
  • Thresholded β†’ noise becomes rocks/no rocks.

βœ… Summary

  • Perlin value β†’ frequency of noise (low = smooth, high = jagged).
  • Max value β†’ amplitude / height / intensity.
  • Mask & Combine β†’ controls placement.
  • Hierarchy β†’ large features first β†’ small details later.
MountainBiome Slice Explained

Biomes Node Output

The 0.45 here doesn’t directly refer to world height. It refers to the value of the Biomes node’s noise output, which is a number between 0 and the Biomes Max (0.79 in your config).

Perlin: 4.1
Max: 0.79
  • At each (x, y) point on the terrain, the Biomes node generates a value:

    BiomesValue = PerlinNoise(x, y) * 0.79
    
  • Range: 0 β†’ 0.79

  • This value is not the world height. It’s a mask / control value that decides where biomes appear.

MountainBiome Slice

Mask: Trapezoid [0.45 – 0.6]
  • β€œActive” points are those where:

    0.45 ≀ BiomesValue ≀ 0.6
    
  • Only these points contribute to mountains.

  • This doesn’t mean the mountains start at height 0.45 in the world. It means the mountain shape will be generated where the Biomes noise falls in that range.

Child Nodes (MountainBase / MountainDetail)

  • They have their own Perlin and Max values, which do affect actual height.
  • The MountainBiome slice acts like a mask, limiting where the height contributions are applied.

Summary

  • 0.45–0.6 β†’ slice of noise values, not height
  • Height is determined later by the child nodes’ Perlin * Max

Final Result

description