2D terrain generation for XNA
Saturday, January 5th, 2013This is a small program for generating tilebased landscapes in XNA.
The logic behind the random world
Even though this program only saves maps as 2D, the generation has some 3D data in it.
The map is stored as a double array of bytes:
byte[,] _map = new byte[50,50]; //the map (stores height for each tile)
Each tile stores the height of the tile (a value between 0 and 255), and to begin with they are all zero.
To begin the creation of the world, about one fifth of the cells receive a random height between zero and 255. This simulates a young planet dotted with tall mountains. All tiles below the ocean level (which is 10 - see lower left corner of program) are colored blue.
This initial generation is done using this bit of code
//create the new map byte[,] map = new byte[width, height]; int allCells = width * height; //figure out how many dots to begin with int amountOfFirstFillCells = (int)( allCells * percentageFillAtStart); //add random heights to all the wanted cells for (int i = 0; i < amountOfFirstFillCells; i++) { Point p = map.GetRandomCellPosition(); map[p.X, p.Y] = (byte)_rnd.Next(255); }
The byte[,].GetRandomCellPosition() is an extensionmethod I've created to help with the mapgeneration. You can get all the code at the end of this article.
Smoothing
As time passes (the user right-clicks), the mountains are worn down by smoothing them, so hills are created at their feet. This is done by calculating the average of the height of all neighbors for every tile and then elevating or lowering the tile accordingly. Here you can see the landscape from initial values over six iterations of smoothing:
The smoothing is performed with this bit of code:
//for calculating the average value of neighbors of a tile: float tileSum = 0; //the sum of the height of the neighbors float numberOfNeighbors = 0; //the number of neighbors for this tile //to store the smoothed version of the map byte[,] destinationMap = new byte[50,50]; //get the neigbors and go through them foreach (var neighbor in sourceMap.GetNeighborContents(x, y)) { numberOfNeighbors++; //increase the number of neighbors found tileSum += neighbor; //and store the new sum of heights } //calculate the average of all neighbors float averageForNeighbors = tileSum / numberOfNeighbors; //find out what the difference between this tile and the neighbor average is float difference = averageForNeighbors - sourceMap[x, y]; //introduce a little randomness float randomPct = Math.Abs(difference * .1f ) * (_rnd.Next(6) -2); //use a fifth of the difference to raise/lower + the randomness destinationMap[x, y] = (byte) MathHelper.Clamp( (sourceMap[x, y] + difference * .2f + randomPct), 0, 255);
The most important thing to notice is that we are using a new doublearray for storing the values of the smoothed tiles as we go over the map. The reason for this is that if we updated the values of each tile in the original doublearray as we went along, then the neighbortiles would get an average calculated from the already smoothed tiles, and we would skew the distribution of smoothing. In the code I swap back and forth, calculating from one doublearray and putting into the other, and then vice versa.
Drawing
When drawing the map, all tiles that are higher than the current sealevel get a shade of green, the taller they are, the lighter it is.
The result
With CTRL+S the map is saved into a textfile for easy access from other platforms.
The width and height is stored at the top.
All the tiles that are higher than the current sealevel are stored as Xs and the rest as spaces.
Where to go from here?
This mapmaker only has the contours of the map. To populate the map with forests, swamps, deserts, etc. it would be pretty easy to make an algorithm that spreads these types of tiles out from random points on the map using flood fill algorithm. That would require that you store the different tiletypes, for example as integers.
Here's the code
The complete application (XNA 4.0, VS 2010).
Only the doublearray extensionmethods. (right-click > "save as...")
An extended version which can save heightmap and includes code for loading (but no support for it in the application)