# Procedural 2D map generation

Published on

## Introduction

In this article, I will show you how to create simple 2-dimensional map based on Simplex Noise. We are going to write a map generator in Python using The Python Arcade as a game engine and OpenSimplex library as a noise generator. Let's go!

### Prerequisites

• Basic knowledge about python.
• Basic knowledge about game engines.

## What is this noise about?

I don't want to make a math science here. Let's keep things simple here.

We want our terrain:

• to make it looks like a real terrain
• to make it repetitive while using same seed
• to make it unique while using different seeds

Basic concept looks like this: there are known coordinates like X and Y and random constant called seed. We want to obtain Z coordinate (height) for each (X, Y) point. The magic is that with same seed there will be always same result. In my example I generate 2D map, but there is no obstacles to make it 3D. I created ten 16x16 sprites, each for different representation of Z coordinate. Z is going to have values from -1.0 to 1.0. Everything below 0.1 is going to be ocean, then I made some greenery, rocks and snow on top of mountains.

Preview:

### Code review

Let me explain best parts:

from opensimplex import OpenSimplex
import uuid

Why reinvent the wheel? We are going to use opensimplex library which gives freedom in creation of Simplex noise. In this example I use uuid to generate a truly random grain. Let's move on to main methods:

def generate_seed(self):
return (uuid.uuid1().int >> 64)

def generate_world(self, x, y, seed):
tmp = OpenSimplex(seed)
z = tmp.noise2d(x=x, y=y)
return z

Random seed generation is optional. In this example we are going to view maps generated by different seeds.

def setup(self):

# Set the background color
# Sprite lists
self.seed = self.generate_seed()

def update(self, delta_time):
self.seed = self.generate_seed()
for x in range(self.x_start, self.x_end + 8, 8):
for y in range(self.y_start, self.y_end + 8, 8):
z = self.generate_world(x=x * 0.01, y=y * 0.01, seed=self.seed)
if z >= 0.9:
tile = arcade.Sprite("sprites/map/biom-regular-height-90.png", SPRITE_SCALING_TILE)
elif z >= 0.8:
tile = arcade.Sprite("sprites/map/biom-regular-height-80.png", SPRITE_SCALING_TILE)
elif z >= 0.7:
tile = arcade.Sprite("sprites/map/biom-regular-height-70.png", SPRITE_SCALING_TILE)
elif z >= 0.6:
tile = arcade.Sprite("sprites/map/biom-regular-height-60.png", SPRITE_SCALING_TILE)
elif z >= 0.5:
tile = arcade.Sprite("sprites/map/biom-regular-height-50.png", SPRITE_SCALING_TILE)
elif z >= 0.4:
tile = arcade.Sprite("sprites/map/biom-regular-height-40.png", SPRITE_SCALING_TILE)
elif z >= 0.3:
tile = arcade.Sprite("sprites/map/biom-regular-height-30.png", SPRITE_SCALING_TILE)
elif z >= 0.2:
tile = arcade.Sprite("sprites/map/biom-regular-height-20.png", SPRITE_SCALING_TILE)
elif z >= 0.1:
tile = arcade.Sprite("sprites/map/biom-regular-height-10.png", SPRITE_SCALING_TILE)
else:
tile = arcade.Sprite("sprites/map/biom-regular-height-00.png", SPRITE_SCALING_TILE)
tile.center_x = x
tile.center_y = y
self.tiles_list.append(tile)

def on_draw(self):
self.tiles_list.draw()

In each update:

1. arcade.SpriteList() with 16x16 sprites is cleared
2. seed is randomly generated
3. for each X and Y on the screen Z dimension is calculated
4. depending on Z (height), sprite (water/ grass/rock/snow) is selected
5. sprites are added to the arcade.SpriteList()
6. back to point 1.

## Preview

Code with sprites is avaivable on my github here.

TO-DO: It would be cool to also have an example where seed is static and we only move on single axis.