Building an Infinite Procedurally-Generated World
spin.atomicobject.com
spin.atomicobject.com
So, instead of creating infinite worlds I'd like to see a lot more focus on procedurally-generating very interesting finite worlds. For games with a simple 2d tile-based grid, we then don't have to limit ourselves to just crafting noise-based functions -- as we can create the entire world at once. Games like Dwarf Fortress, Spelunky, Nethack, Terraria, etc., all follow this approach, and I'm trying to do the same in my own game, Moonman. So get our your IDEs and start creating house, face, forest, creature, item, and cloud generators. :)
But even with an infinite landscape, I think you could improve the variety by using a generator at different scopes - if your landscape is just one constant type of terrain, like a field, things will get boring. You might have a wide variety of rivers and trees, but the basic idea is the same. But if you have another generator controlling the terrain type - going between plains, desert, mountains, etc., that would be really interesting. At the small scope, variation in trees and water is still the same level of change, but there is also a larger scope that changes the overall feel at a much larger radius.
Instead of {noise(x,y)>0.5 = desert} let's simulate eons of weather, geological events, floods, and then feed that back into the lore of the game world. Dwarf Fortress is one of the few games I'm aware of that attempts this level of world generation. Unfortunately it's quite impenetrable as a game, due to its complexity.
As for Perlin noise, check out Simplex noise. It's what Perlin designed AFTER Perlin noise. I use 4D Simplex fractals to create my game world which tiles seamlessly when scrolling up/down as well as left/right. And I'm filling the world with random-but-connected cities, factions etc. That's where interesting emergent gameplay comes from.
BTW, I have a few writeups (quite technical) on the correct implementation of Simplex noise on spiralcode.wordpress.com
When I did my (abortive) procedural world generator, I tried summing Perlin noise with a circle, and treating values below a certain threshold as water, with values above the threshold as land. That worked okay, because it would give a believably jagged coastline with offshore islands, but it was still obviously just a circle when viewed at scale.
Does anyone have a quick-and-dirty idea for partitioning land and water?
It gets better as he goes along as he talks about adding various terrain features, and eventually also rivers which cut through the landscape.
Also, cannot recommend Amit Patel's stuff enough. That guy is a fountain of knowledge for all things gamedev.
You'd still want various kinds of local mutation to create interesting features, though. For inspiration, you might look at how games like Angband and Crawl combine procedurally-generated overall level design with pre-constructed "vaults". A similar technique, with enough variations to avoid looking obvious, could work to embed more unusual features in an otherwise procedurally-generated set of continents.
Beyond that, you might also want a dedicated coastline-and-river generator, which picks appropriate high mountains, puts the top of a river there, and has it wend its way down altitude (eroding as needed) until it reaches the "ocean". Throw in some appropriate generation of beaches, riverbanks, and ocean cliffs.
I had tried to avoid this by doing Perlin noise + distance from the center of the map, which makes an approximately-circular "continent" with an okay coastline. Amit Patel's article says "we can draw the coastline any number of ways," which is true - I'm just wondering what those ways are.
("Look, this is Perlin noise" made this a disappointing article for me. It's the sort of thing you saw on GameDev.Net fifteen years ago...)
This image shows 16 possible world maps (I found it useful to just have them appear next to each other in the world so I could quickly get an overview of the consequences of tweaking parameters): http://imgur.com/dthr7O2
This is the algorithm that generated the world (written in C#). permutation is a random ordering of the numbers 0 to 255, repeated twice. HermitePoints take an x and y coordinate and a slope. Noise functions take an x and y coordinate, a number of octaves, a frequency and a permutation. Turbulence and ridged noise output results in the domain [0, 1], simplex noise in the domain [-1, 1].
private static Chunk GenerateChunk(short chunkX, short chunkZ, int seed, byte[] permutation)
{
var chunk = new Chunk(chunkX, chunkZ);
int worldMapSize = 256;
short maxHeight = (short)Math.Min(worldMapSize >> 2, Chunk.CHUNK_HEIGHT);
float[] worley = new float[2];
HermiteSpline continentCurve = new HermiteSpline(new[]
{
new HermitePoint(0f, 0f, 0f), new HermitePoint(0.07f, 0.03f, 1f), new HermitePoint(0.12f, 0.1f, 0.7f),
new HermitePoint(0.21f, 0.18f, 1f), new HermitePoint(0.24f, 0.2455f, 0f), new HermitePoint(0.26f, 0.26f, 1f), new HermitePoint(1f, 1f, 1f)
});
HermiteSpline continentMask = new HermiteSpline(new[]
{
new HermitePoint(0f, 0f, 0f), new HermitePoint(0.25f, 0f, 0f), new HermitePoint(0.4f, 1f, 0f), new HermitePoint(1f, 1f, 0f)
});
HermiteSpline plateMountainCurve = new HermiteSpline(new[]
{
new HermitePoint(0f, 0f, 0f), new HermitePoint(1f, 1f, 3.5f)
});
for (short x = 0; x < Chunk.CHUNK_SIZE; x++)
{
for (short z = 0; z < Chunk.CHUNK_SIZE; z++)
{
int globalX = (chunkX << Chunk.CHUNK_SIZE_LOG2) + x;
int globalZ = (chunkZ << Chunk.CHUNK_SIZE_LOG2) + z;
//Subdive the world into squares, each of which contains an independent world map
//The edges of each square are lowered so that each map is separated by oceans
float xSeparator = (float)Math.Sin((globalX & (worldMapSize - 1)) * MathHelper.Pi / worldMapSize);
float zSeparator = (float)Math.Sin((globalZ & (worldMapSize - 1)) * MathHelper.Pi / worldMapSize);
float rectSeparator = (float)Math.Min(1, 3 * Math.Min(xSeparator, zSeparator));
float circleSeparator = xSeparator * zSeparator;
float mapSeparator = rectSeparator * 0.375f + circleSeparator * 0.575f + 0.05f;
//Use turbulence noise to get the typical clumped shape of continents and add some simplex and ridged noise for the thinner shapes
float continentNoise1 = Noise.Turbulence(globalX, globalZ, 8, worldMapSize, permutation);
float continentNoise2 = Noise.Simplex(globalX, globalZ, 8, worldMapSize * 0.16f, permutation) * 0.5f + 0.5f;
float continentNoise3 = Noise.Ridged(globalX, globalZ, 8, worldMapSize * 0.5f, permutation);
continentNoise2 *= continentNoise2;
continentNoise3 *= continentNoise3;
float continentHeight = continentNoise1 * 0.5f + continentNoise2 * 0.25f + continentNoise3 * 0.125f;
float baseHeight = continentCurve.Map(continentHeight * mapSeparator);
//Add mountains caused by convergent continental plate boundaries
float continentMult = continentMask.Map(baseHeight);
float plateMountainNoise1 = Noise.Ridged(globalX, globalZ, 8, worldMapSize * 0.4f, permutation);
float plateMountainNoise2 = Noise.Simplex(globalX, globalZ, 8, worldMapSize * 0.2f, permutation) * 0.5f + 0.5f;
float plateMountainNoise = plateMountainNoise1 * 0.66f + plateMountainNoise2 * 0.33f;
float plateMountainHeight = plateMountainCurve.Map(plateMountainNoise) * continentMult;
//Apply the map separation
float finalHeight = baseHeight + plateMountainHeight;
//Convert the height from range [0,1] to range [1,255]
byte height = (byte)(finalHeight * (maxHeight - 1) + 1);
for (short y = 0; y < height; y++)
{
chunk[(short)x, y, z] = (byte)((y + 1) * 255 / (maxHeight + 1));
chunk.SetStack((short)x, z, new short[] { height, (short)(Chunk.CHUNK_HEIGHT - height) });
}
}
}
return chunk;
}https://dl.dropboxusercontent.com/u/1094010/island_compiled....
I've never seen the Perlin approach as being sufficient for making terrain that is _realistic_, since real terrain is shaped through iterative processes, not analytic ones.
[0] Example: http://imgur.com/txVfRYV
There are tons of good terrain generators. Try Google. VistaPro, from the 1980s, is the classic.[1]
So far, nobody has built a procedural city generator good enough to generate a detailed city at ground level. This is one of the better ones, but the result comes out looking like a modern prison.[2]
[1] https://en.wikipedia.org/wiki/VistaPro [2] http://orphancity.tumblr.com/
Here's what I did: https://thefiletree.com/espadrine/art/island.html?plug=none
Once you have access to random 2D data, it’s pretty straight forward to convert that noise into usable data. Simply set up thresholds for each tile type you want to support, eg:
* water if < 0.3
* grass if >= 0.3 and <= 0.6
* mountain if > 0.6
I recommend leaving the upper and lower cases open. If they are not left open, you may end up with holes in your map for unexpectedly high or low values. An alternative is to clamp or scale the noise values when you generate them.
If you expect to have more tile types, you should probably consider a categorical probability distribution (something like: water: 0.3, grass: 0.3, mountain: 0.4) and sample from it (instead of "hard-coding" it with the test you're referring to)