Procedural Worlds — 10 Biomes From One Terrain Algorithm
I don't have 10 level designers. I don't have one level designer. What I have is one terrain algorithm and a Dictionary.
The world catalog
Every world in Oliver's Racers — Green Valley, Red Mesa Desert, Alpine Snow, Neon Night City, all ten of them — is defined as a flat Dictionary of parameters in maps.gd:
"valley": {
"name": "Green Valley",
"desc": "Rolling meadows, a tidy town and snow-capped peaks. The classic.",
"sky_top": Color(0.18, 0.42, 0.82),
"sky_horizon": Color(0.72, 0.84, 0.95),
"ground": [Color(0.30, 0.52, 0.27), Color(0.27, 0.47, 0.24),
Color(0.22, 0.38, 0.20)],
"fog_color": Color(0.75, 0.85, 0.95), "fog_density": 0.0014,
"accent": Color(0.35, 1.0, 0.5), "dark": false, "slick": 1.0,
"scenery": {"trees": 150, "city": 22, "mountains": 16,
"snowcaps": true, "rocks": 14},
},Compare that to the volcano world:
"volcano": {
"name": "Ember Ridge",
"desc": "Black rock, drifting ash and rivers of glowing embers. Don't stall.",
"sky_top": Color(0.10, 0.06, 0.08),
"sky_horizon": Color(0.45, 0.16, 0.08),
"ground": [Color(0.16, 0.13, 0.13), Color(0.13, 0.11, 0.11),
Color(0.22, 0.14, 0.10)],
"fog_color": Color(0.30, 0.10, 0.06), "fog_density": 0.0026,
"accent": Color(1.0, 0.45, 0.15), "dark": true, "slick": 1.0,
"scenery": {"rocks": 70, "rock_color": Color(0.16, 0.14, 0.14),
"embers": 70, "mountains": 14, "volcano": true},
},Same structure. Different numbers. Completely different feel.
What each field actually controls
Sky: sky_top and sky_horizon feed directly into ProceduralSkyMaterial. Two colours and Godot gives you a convincing sky gradient. Night City has a near-black zenith with a deep indigo horizon. Sunset Shore has gold meeting dark blue. No skybox textures, no HDRIs. Just two colours.
Ground: Three colours that get composed into a tiled grid texture at runtime. The first is the base, the second is an alternate patch colour, the third is the grid lines between tiles. On snow it's all blue-whites. On the moon it's silver-grey. The ground_rough parameter controls how matte the surface looks — snow is 0.6 (glossy ice), volcanic rock is 1.0 (fully rough).
Fog: fog_density controls how quickly distant objects fade. The desert is hazy (0.0016), the volcano is thick with ash (0.0026), and the moon is crisp and clear (0.0008). Combined with the fog colour, this single parameter sells the atmosphere harder than any other setting.
Dark flag: If dark is true, the car gets headlights and the gates glow brighter. Night City, Ember Ridge, Luna Rally, and both premium worlds use this. It flips a switch in car.gd that spawns two SpotLight3D cones from the front of the car.
Slick: A global grip multiplier. Alpine Snow is 0.82 — everything slides a bit more. Luna Rally is 0.86 — moon dust, baby. Nova Rift is 0.8 — the slipperiest surface in the game.
Scenery scattering
The scenery sub-dictionary drives what gets placed around the track: broadleaf trees, pine trees, palm trees, sandstone mesas, city buildings, mountain peaks, craters, ember particles, and rocks. world.gd reads these keys and scatters the appropriate primitives.
The trick is that all scenery is built from BoxMesh, CylinderMesh, and SphereMesh primitives. No imported 3D models. A "tree" is a brown cylinder (trunk) with a green sphere (canopy) on top. A "mesa" is a wide tapered cylinder in sandstone orange. A "building" is a tall box with a different-coloured top face.
This sounds ugly. It isn't. With fog, distance, and the right colours, these primitives sell the landscape. At racing speed you're not studying individual trees — you're absorbing a palette. Green + blue sky + white peaks = mountains. Orange + haze + mesas = desert. Black + embers + red fog = volcano. The colour palette does 90% of the work.
Premium worlds
Two worlds are locked behind in-game currency: Aurora Peaks ($3,500) and Nova Rift ($8,000). Same system, different parameters. Aurora has rippling northern lights (simulated with the sky gradient — green-tinted horizon against a dark sky) and icy grip. Nova Rift is a cosmic purple dust bowl with glowing crystals (embers in violet).
"aurora": {
"name": "Aurora Peaks",
"desc": "Race glowing midnight snow under rippling northern lights. PREMIUM.",
"price": 3500,
...
"slick": 0.85,
},The price key is the only thing that makes a world premium. If it's absent or zero, the world is free.
The aha moment
Early on, I was trying to make each world feel unique by writing custom generation code — special terrain deformations, bespoke scatter patterns. I had maybe three worlds and the code was already becoming unmanageable.
Then I realized: the worlds don't need different algorithms. They need different inputs to the SAME algorithm. That shift turned "three worlds with custom code" into "ten worlds with one data table." Adding a new world takes about 15 minutes of colour-picking now.
Written by Oliver
Indie game developer building 3D web experiences in Godot and Next.js. I write about creative coding, procedural generation, and game feel.