Table of Contents

One of the more popular pages on my site is about polygonal map generation. Making those maps was a lot of work. I didn’t start there. I started with something much simpler, which I’ll describe here. The simpler technique can make maps like this in under 50 lines of code:

I’m not going to explain how to draw these maps; that’s going to depend on your language, graphics library, platform, etc. I’m only going to explain how to fill an array with map data.

#Noise

A common way to generate 2D maps is to use a bandwidth-limited noise function, such as Perlin or Simplex noise, as a building block. This is what the noise function looks like:

We assign each location on the map a number from 0.0 to 1.0. In this image, 0.0 is black and 1.0 is white. Here’s how to set the color at each grid location in C-like syntax:

for (int y = 0; y < height; y++) {
  for (int x = 0; x < width; x++) {      
    double nx = x/width - 0.5, ny = y/height - 0.5;
    value[y][x] = noise(nx, ny);
  }
}

The loop will work the same in Javascript, Python, Haxe, C++, C#, Java, and most other popular languages, so I’ll show it in C-like syntax and you can convert it to the language you’re using. In the rest of the tutorial, I’ll just show how the loop body (the value[y][x]=… line) changes as we add more features. At the end, I’ll show a complete example.

#Elevation

Noise by itself is just a bunch of numbers. We need to assign meaning to it. The first thing we might think of is to make the noise correspond to elevation (also called a “height map”). Let’s take the noise from earlier and draw it as elevation:

The code is almost the same, except for what’s inside the inner loop; it now looks like this:

elevation[y][x] = noise(nx, ny);

Yes, that’s it. The map data is the same, but now I call it elevation instead of value.

Lots of hills, but not much else. What’s wrong?

Frequency

Noise can be generated at any frequency. I’ve only picked one frequency so far. Let’s look at the effect of frequency. Try moving the slider to see what happens at different frequencies:


freq=

It’s just zooming in and out. That doesn’t seem very useful at first glance, but it is. I have another tutorial that explains the theory: things like frequency, amplitude, octaves, pink and blue noise, etc.

elevation[y][x] = noise(freq * nx, freq * ny);

It’s sometimes useful to think of wavelength, which is the inverse of frequency. Doubling the frequency makes everything half the size. Doubling the wavelength makes everything twice the size. The wavelength is a distance, measured in pixels or tiles or meters or whatever you use for your maps. It’s related to frequency: wavelength = map_size / frequency.

Octaves

To make the height map more interesting we’re going add noise at different frequencies:

 +  +  = 
elevation[y][x] =    1 * noise(1 * nx, 1 * ny);
                +  0.5 * noise(2 * nx, 2 * ny);
                + 0.25 * noise(4 * nx, 2 * ny);

Let’s mix big low frequency hills and small high frequency hills into the same map. Move the slider to add smaller hills to the mix:


Now that looks a lot more like the fractal terrain we want! We can now get hills and rugged mountains, but we still don’t get flat valleys. We need something else for that.

Redistribution

The noise function gives us values between 0 and 1 (or -1 and +1 depending on which library you’re using). To make flat valleys, we can raise the elevation to a power. Move the slider to try different exponents.


exp=
e =    1 * noise(1 * nx, 1 * ny);
  +  0.5 * noise(2 * nx, 2 * ny);
  + 0.25 * noise(4 * nx, 4 * ny);
elevation[y][x] = Math.pow(e, exponent);

Higher values push middle elevations down into valleys and lower values pull middle elevations up towards mountain peaks. We want to push them down. I use power functions here because they’re simple, but you can use any curve you want; I have a fancier demo here.

Now that we have a reasonable elevation map, let’s add some biomes!

#Biomes

Noise gives us numbers but we want a map with forests, deserts, and oceans. The first thing to do is to make low elevations into water:


water=
function biome(e) {
    if (e < waterlevel) return WATER;
    else return LAND;
}

Hey, that’s starting to look like a procedurally generated world! We have water, grass, and snow. What if we want more things? Let’s make the sequence water, beach, grassland, forest, savannah, desert, snow:


Terrain based on elevation only
function biome(e) {
  if (e < 0.1) return WATER;
  else if (e < 0.2) return BEACH;
  else if (e < 0.3) return FOREST;
  else if (e < 0.5) return JUNGLE;
  else if (e < 0.7) return SAVANNAH;
  else if (e < 0.9) return DESERT;
  else return SNOW;
}

Hey, looks cool! You’ll want to change the numbers and biomes for your game. Crysis will have a lot more jungles; Skyrim will have a lot more ice and snow. But no matter what you change the numbers to, this approach is a bit limited. The terrain types line up with the elevations, so they form bands. To make it more interesting, we need to choose biomes with something other than elevation. Let’s create a second noise map for “moisture”:


Elevation noise on left; moisture noise on right

Now let’s use both elevation and moisture. In the diagram on the left below, the y-axis is the elevation (first diagram above) and the x-axis is the moisture (second diagram above). It produces a reasonable looking map:

 
Terrain based on two noise values

Low elevations are oceans and beaches. High elevations are rocky or snowy. In between we get a wide range of biomes. The code looks like this:

function biome(e, m) {      
  if (e < 0.1) return OCEAN;
  if (e < 0.12) return BEACH;
  
  if (e > 0.8) {
    if (m < 0.1) return SCORCHED;
    if (m < 0.2) return BARE;
    if (m < 0.5) return TUNDRA;
    return SNOW;
  }

  if (e > 0.6) {
    if (m < 0.33) return TEMPERATE_DESERT;
    if (m < 0.66) return SHRUBLAND;
    return TAIGA;
  }

  if (e > 0.3) {
    if (m < 0.16) return TEMPERATE_DESERT;
    if (m < 0.50) return GRASSLAND;
    if (m < 0.83) return TEMPERATE_DECIDUOUS_FOREST;
    return TEMPERATE_RAIN_FOREST;
  }

  if (m < 0.16) return SUBTROPICAL_DESERT;
  if (m < 0.33) return GRASSLAND;
  if (m < 0.66) return TROPICAL_SEASONAL_FOREST;
  return TROPICAL_RAIN_FOREST;
}

You’ll want to change all of those numbers to match the needs of your own game.

Alternatively if you don’t need biomes, smooth gradients (see this article) can produce colors:

 

With either biomes or gradients, one noise value doesn’t produce enough diversity, but two is pretty good.

#Islands

For some projects I want the boundaries of the map to be water. This turns the world into one or more islands. There are lots of ways to do this, but for my polygon map generator I used something relatively simple: I changed elevation e = e + a - b*d^c, where d is the distance from the center (scaled to 0-1). Another option would be e = (e + a) * (1 - b*d^c). The constant a pushes everything up, b pushes the edges down, and c controls how quick the drop off is.


a=
b=
c=
seed=
d = 2*max(abs(nx), abs(ny))
d = 2*sqrt(nx*nx + ny*ny)
e = e + 0.05 - 1.00*pow(d, 2.0)
e = (e + 0.05) * (1 - 1.00*pow(d, 2.0))

I’m not completely satisfied with this and there’s a lot more to explore. Should it be Manhattan distance or Euclidean distance? Should it be based on distance to center or distance to edge? Should it be distance squared, or linear, or some other exponent? Should it be adding/subtracting, or multiplying/dividing, or something else? Try Add, a = 0.1, b = 0.3, c = 2.0 or try Multiply, a = 0.05, b = 1.00, c = 1.5. Which parameters you like will depend on the needs of your project.

Why stick to standard mathematical functions at all? As I explored in my article on RPG damage, everyone (including me) uses mathematical functions like polynomials, exponentials, etc., but on a computer we’re not limited to those. We can draw any kind of reshaping function and use it here, using a lookup table, e = e + height_adjust[d]. I haven’t explored this.

#To infinity and beyond

The calculation of the biome at position (x,y) is independent of calculations at any other position. This local calculation results in two nice properties: it can be calculated in parallel, and it can be used for infinite terrain. Put the mouse over the minimap on the left to generate a map on the right. We can generate any part of the map without generating (or having to store) the whole thing.

#Implementation

Using noise for generating terrain is a popular technique, and you can find tutorials for many different languages and platforms. The map generation code is pretty similar across languages. Here’s the simplest loop, in three different languages:

Once you have a noise library, these are all pretty similar. Try opensimplex for Python or libnoise for C++ or simplex-noise for Javascript. There are lots of noise libraries for most popular languages. Alternatively, you may want to spend time studying how Perlin noise works, or implementing it yourself. I didn’t.

Once you have found a noise library for your favorite language, the details will vary (some will return numbers from 0.0 to 1.0 and others from -1.0 to +1.0) but the basic idea is the same. For a real project you may want to wrap the noise function and the gen object into a class but those details aren’t relevant here so I made them global.

For this simple project it doesn’t matter that much whether you use Perlin noise, Simplex noise, OpenSimplex noise, value noise, midpoint displacement, diamond displacement, or an inverse Fourier transform. There are pros and cons of each of these but they all produce similar enough output for this type of map generator.

The drawing of the map is going to be platform-specific and game-specific so I’m not providing that; this code is just to generate the elevations and biomes, which you’ll want to draw yourself in whatever style your game uses. Feel free to copy, port, and use it for your own projects.

#Playground

I’ve covered mixing octaves, raising the elevation to a power, and combining elevation and moisture to pick a biome. Here’s an interactive diagram that lets you play with all of these parameters, and then shows how the code is put together:


exp=
Elevation octaves:
e1=
e2=
e3=
e4=
e5=
e6=
Moisture octaves:
m1=
m2=
m3=
m4=
m5=
m6=

Here’s the code:

var rng1 = PM_PRNG.create(seed1);
var rng2 = PM_PRNG.create(seed2);
var gen1 = new SimplexNoise(rng1.nextDouble.bind(rng1));
var gen2 = new SimplexNoise(rng2.nextDouble.bind(rng2));
function noise1(nx, ny) { return gen1.noise2D(nx, ny)/2 + 0.5; }
function noise2(nx, ny) { return gen2.noise2D(nx, ny)/2 + 0.5; }
   
for (var y = 0; y < height; y++) {
  for (var x = 0; x < width; x++) {      
    var nx = x/width - 0.5, ny = y/height - 0.5;
    var e = ( * noise1( 1 * nx,  1 * ny)
           +  * noise1( 2 * nx,  2 * ny)
           +  * noise1( 4 * nx,  4 * ny)
           +  * noise1( 8 * nx,  8 * ny)
           +  * noise1(16 * nx, 16 * ny)
           +  * noise1(32 * nx, 32 * ny));
    e /= (+++++);
    e = Math.pow(e, );
    var m = ( * noise2( 1 * nx,  1 * ny)
           +  * noise2( 2 * nx,  2 * ny)
           +  * noise2( 4 * nx,  4 * ny)
           +  * noise2( 8 * nx,  8 * ny)
           +  * noise2(16 * nx, 16 * ny)
           +  * noise2(32 * nx, 32 * ny));
    m /= (+++++);
    /* draw biome(e, m) at x,y */
  }
}

A tricky bit: you need to use different seeds for the elevation and moisture noise. Otherwise they’ll end up being the same, and your maps won’t look nearly as interesting. In Javascript I use the prng-parkmiller library; in C++ you can use two separate linear_congruential_engine objects; in Python you can instantiate two separate instances of the random.Random class.

#Thoughts

What I like about this approach to map generation is that it’s simple. It’s fast. It’s very little code to produce decent results.

What I don’t like about this approach is that it’s limited. Local calculation means every location is independent of every other location. Different areas of the map don’t relate to each other. Every place on the map “feels” the same. There are no global constraints like “there should be between 3 and 5 lakes” or global features like a river flowing from the top of the highest peak down to the ocean.

Why do I recommend it then? I think it’s a good starting point, especially for indie games or game jams. Two of my friends wrote the initial version of Realm of the Mad God in 30 days, for a game competition. They asked me to help them make the maps. I used this technique (plus some extra features that turned out not to be that useful) to make a map for them. Months later, after getting feedback from players and looking at the game design a lot more, we designed the more advanced map generator using Voronoi polygons, described here. That map generator doesn’t use the techniques from this page, but uses noise very differently to produce maps.

#More

There are lots of cool things you can do with noise functions. If you search the web you’ll see variants such as turbulence, ridged multifractal, terraced, voronoi noise, and others. I’ve focused on simplicity for this article.

My previous map generation projects that influenced this one:

It bothers me somewhat that most of the code we game developers write for noise-based terrain generation (including midpoint displacement) turns out to be the same as audio and image filters. On the other hand, it produces decent results with very little code, so that’s why I wrote the article you’re reading. It’s a quick & easy starting point. I usually don’t use these types of maps for long; I’ll replace them with a custom map generator once more of the game is built and I have a better sense of what types of maps best match that game’s design. That’s a common pattern for me: start with something extremely simple, then replace it only after I better understand the system I’m working on.