Noise Generated 2D Terrain

I’ve posted quite a few topics on this issue this year, I was up until recently working with a partner on this who did most of the terrain noise generation mathematics because well, my math abilities just stink hehe. Unfortunately life happened and that person is no longer working on the project with me. So now that I have to do this myself which is fine I figure I may as well make sure I understand everything as I am thinking of doing a code re-write to optimize things. I am hoping that some of you familiar with 2D terrain generation can assist in this. I know that there are some utilities in the asset store that already exist to help with this, I even own a few myself, and that there are countless tutorials online which I have already perused. But I have learned over recent months that noise generated terrain is no easy task, especially for a first real game project. So anyways, enough talk here is how our system works right now:

  • Map Generator takes map data (mapsize in tiles, chunksize in tiles) and creates a 2d array of tile data (non-gameobject) to hold tile data, and also a 2d array of chunks. Chunks have box colliders attached as triggers.
  • Default mapSize for testing is 4096 x 1024 tiles. Default chunkSize is 64 x 64 tiles.
  • We assign a default material of either stone or dirt based on the Y depth of the tile. So by default the bottom half of the map is stone, top half is dirt. We loop over the tiles several times (one for each layer). Each “layer” of the map generation process has a different task. For example the default material assignment is layer 1. Layer 2 is where we use noise to create chunks of stone in dirt, chunks of dirt in stone. Layer 3 is where we use noise to add materials (ore, gems, clay, etc.) based on chance. Layer 4 creates pockets of caves. Layer 5 creates worm caves. Layer 6 creates carves the terrain and replaces topsoil with grass, beaches with sand, etc. Layer 7 goes into biome generation which is where development has currently halted.
  • The repeated iteration over millions of tiles using noise has created map creation time of about 9s given our testing map size. This is actually a small map so that time is getting a bit long and leads me to believe this method of generation is inefficient and is driving my thoughts of a recode.
  • After map creation, when a chunk is triggered to load its mesh, it queries the map for the tile data it should contain and uses that to create a mesh.
  • Player object being within a chunk’s distance of a chunk triggers that chunk’s mesh to be generated. So typically there will be the chunk the player is in, the chunk above, below, left, right of the player containing chunk, and the upper left, lower left, upper right, lower right of the player containing chunk will all get loaded. 9 in total.
  • As the player moves, chunks more than 1 chunk size away will destroy their meshes, and chunks now within 1 chunk’s distance of the player will load their meshes. The chunk game objects & their box trigger colliders are never destroyed, they simply wait for the player to get close again.

That is our current process, it is the culmination of months of learning and while it needs refinement it is definitely a step in the right direction. With that having been said, I want to get some input on whether or not you experienced terrain generators out there think it is efficient. Beyond that, I feel that I have a good grasp on the noise concept. But I am still struggling quite a bit with the terrain contour, particularly when it comes to differing contours. Obviously I do not want to use the same wave length to generate my entire map because then the entire map shares the same terrain type (rolling hills or mountains or flat, etc.). However, it seems I cannot simply say okay, X192 is where biome X begins, so I want to change my wave length now and create a flatter terrain that is not so mountainous. If I try to do that since it is a sampling of a different terrain type I wind up with a shear rise or drop of several tiles in terrain.

Finally, here are the questions I have:

  • What can I do to correct the contour problem?
  • Does the current process sound efficient?
  • Is anybody willing to work with me pro-bono just a little, on getting up to speed in the generation process?
  • Do you have any other tips or tricks that have helped you create similar projects?

Shameful bump ;(

One big performance boost you can get, is by iterating over each tile only once. Don’t process each layer in it’s seperate pass, but decide what material a tile should be in a single pass. This is fairly easy to do with perlin noise. Try to minimize the need for accessing neighbouring tiles. Maybe do 2 passes if you need to: the deterministic pass (no need to look at neighbours) and a pass for the neighbours (cellular automata) if you need it.

You might also want to look into simplex noise, as it apparently is a bit faster in some cases (especially with more dimensions, not sure for 2D)

Cellular automata for caves & resources, of course! You can’t see it but I am so face-palming right now! That helps a ton for the most of it.

What it really boils down to now is how to stitch together various noise samples so I can have differing terrain (i.e. flat forests, mountainous regions, dune-filled deserts). This is probably where I am missing some kind of step.

So you sample the noise aka:
float noise = Noise.GetNoise(blockX, blockY, scale, etc, etc.).

Then what I am doing is something similar to:
if noise >= 0.45f, tiles[blockX, blockY].material = 0 (air) or tiles[blockX, blockY].material = 1 (solid).

Depending on what I pass the noise algorithm of course I get big mountains or flat terrain, etc. But what if I want to restrict my mountains between X64 & X192? And what if after my mountain area I want to create a flat desert between X192 & Xxx? Or maybe in the beginning of the map I want my terrain to start out below sea level and rise to sea level to create an ocean/beach area on the edge of the map (as seen in Terraria)? How could I accomplish those things? If I suddenly change what I am passing the noise algorithm then I get weird results like starting at that block position the terrain will be 10 blocks higher than the previous one (or lower) because I am suddenly sampling a different section of noise.

Well, raising or lowering the overall terrain is easy, as you just have to add or substract some value from the final noise output. To do this only on the edges of the world, you have to make some kind of ‘falloff’ formula and add or substract that from the noise value. So for example, you can use the distance from the centre as the noise value, and like a ‘distance^n’ to increase/decrease the falloff, or divide the distance by some value to make the map bigger. Just play with it a little to see what satisfies you.

As for controlling the mountain ranges between certain x values, this is quite tricky to do. I recommend you to create a noise value ‘mountainess’ which is just some other noise samples, then multiply the output of that with your other terrain noise. This will create the flat areas and mountain areas. Controlling these exactly within coordinates is still hard, but you can play around with it for a while.

IIRC you shouldn’t really input noise values into the frequencies of other noise values, as this will result in the sudden changes in the terrain you described. Especially use the + - * / operators on noise value outputs, with other noise outputs to get results.