Generating a Procedural 2D Map in C#: Part 1 - The Attempt

Trying my hand at procedurally created 2D tile maps with C# from the nostalgic times of Pokémon.

Generating a Procedural 2D Map in C#: Part 1 - The Attempt

I ran into this post called I added beachlife to my random Pokémon map generator by u/party_in_my_head and my Pokémon nostalgia kicked in. Similarly, this really cool similar looking work by u/c35683 got me excited too.

As a nerd in professional and casual life, I really wanted to do the same from scratch - and to look the same. Hunting around for Pokemon styled tilesets I found a few and decided to play with this one with credits to these people (thanks!). But any other tileset will do, you feeling fantasy, maybe a little sci-fi? It's all good.

The Initial .PNG

I'm going to be honest, this first run is awful and skip ahead if you're not interested in a short lived idea.

Using an idea of procedural tilemap generating, I found this blog post that outlined having each four corners of a tile as a type of terrain. In C#, a representation of terrain:

public enum TerrainType
{
    Grass,
    Water,
    Sand,
    Unset,
    OutOfBounds
}	

The first three explain themselves but for the latter two:

  • Unset: This was used to define yet-to-be worked out terrain for a tile.
  • OutOfBounds: Mostly used for calculations. For example, when a tile on the upper left corner of the map wanted to know of a neighbouring tile terrain above or to the left which is outside of said map, this value is returned.

Then the tile itself:

public class Tile
{
    public TerrainType TopLeft { get; set; }
    public TerrainType TopRight { get; set; }
    public TerrainType BottomLeft { get; set; }
    public TerrainType BottomRight { get; set; }

    public string ImageFilePath { get; set; }

    public Bitmap Image => new Bitmap(ImageFilePath);

    public Tile(TerrainType topLeft, TerrainType topRight, TerrainType bottomLeft, TerrainType bottomRight, string filePath)
    {
        TopLeft = topLeft;
        TopRight = topRight;
        BottomLeft = bottomLeft;
        BottomRight = bottomRight;
        ImageFilePath = filePath;
    }

    public Tile()
    {
        TopLeft = TerrainType.Unset;
        TopRight = TerrainType.Unset;
        BottomLeft = TerrainType.Unset;
        BottomRight = TerrainType.Unset;
    }
}

A simple representation of the corners as well as a path to get to the tile image used when generating the map. To which the map looks like:

var map = new Tile[length, width];

I then created my tiles by taking the above tileset, opening Paint.NET and snipping out the 32x32 tiles - simply starting with grass and sand. The tile folder afterwards:

The algorithm to cobble these together went something like:

  1. Set down a first tile.
  2. Use a random adjacent tile that matched the two terrain labels for that side. E.g. if the right side of a tile is entirely grass such as the gggg.png tile above then any of ggsg.png, gsgg.png or gssg.png could be used.
  3. Continue until map is filled.

The first attempt was on a 5x1 sized grid and often looked something like...

... A glitch. Very rarely would it turn out nicely. Which becomes further obvious when we increase the map size:

Ultimately it became time to throw away this idea because the pieces like gggs.png could match against tiles that didn't really match the sliver of grass/sand designed to be a transition piece and looked rather disjointed.

Looking at How It's Really Done

Maths. To be honest, I want to avoid as much of that as possible in my code. Though this comes from whatever the equivalent term for "mouth feel" is for eyes. I just prefer my code to look nearly English readable rather than looking like C++. (Though then the long enterprise variable names start coming in, but that's not here nor there for now).

Again on the hunt for a starting point, I came across this post that gave a nice quick understanding of what I quickly found to be everyone's favourite: Perlin Noise. We can boil this down to: we get a 2D grid of numbers and use those to represent height of each grid cell. These numbers transition smoothly to it's neighbour which makes the gradients look like transitions between different terrain heights.

Back to avoiding maths. There's a lot of implementations of Perlin Noise out there including ones that are mixed with other gradient noises. Off to NuGet! I'll be honest, in my search I just wanted one I could take an example of and move on.  A couple down the list and SimplexNoise gave me what I wanted on its GitHub page. The core maths is different from Perlin Noise but is touted to be smoother looking and in the haste of wanting to see something on screen, that suited me just fine.

Using the new library (still copied nearly straight from GitHub):

Random random = new Random();
SimplexNoise.Noise.Seed = random.Next(); 
int length = 50, width = 50;
float scale = 0.01f;
float[,] noiseValues = SimplexNoise.Noise.Calc2D(length, width, scale);

I was able to generate a new grid with "height" values. The tileset for this only contained full water, sand and grass tiles meaning to pick out what type of tile represents a cell was easy:

if (noiseValues[x, y] <= 80)
{
    map[x, y] = waterTile;
}
else if (noiseValues[x, y] <= 120)
{
    map[x, y] = sandTile;
}
else
{
    map[x, y] = grassTile;
}

Where water were the lowest range, sand was a little higher and grass was everything else, resulting in:

Exciting! Actual tangible pictures. Oh and the pictures are created via:

var bitmap = new Bitmap(grid.GetLength(0) * 32, grid.GetLength(1) * 32);

using (var g = Graphics.FromImage(bitmap))
{
    for (int x = 0; x < grid.GetLength(0); x++)
    {
        for (int y = 0; y < grid.GetLength(1); y++)
        {
            g.DrawImage(grid[x, y].Image, x * 32, y * 32);
            //g.DrawString(((int)noiseValues[x, y]).ToString(), new Font("Arial", 10), Brushes.White, x * 32, y * 32);
        }
    }
}
  • The multiplier *32 is simply the size of the tiles in pixels.
  • map[x, y].Image relates to the image representation of the Tile object.

The commented out line will just print the "height" of each cell on the cell which looks like this:

The Next Steps

I'd like to move toward a multiple pass model where it looks something like:

  1. Create the terrain
  2. Smooth the transition between terrain types
  3. Decorate terrain

A fun little project so far! Make sure to check out the later posts, when all the code above is scrapped for something way tidier.

This is a series of producing procedural maps in C#. All parts: Part 1, Part 2, Part 3.