Map Generation Tutorial Part 1: Perlin Noise
Welcome to this tutorial on map generation. For this tutorial I will be using Perlin noise. Perlin noise is a type of gradient noise that was developed by Ken Perlin in 1983. It is widely used in computer graphics to create natural-looking textures, clouds, terrain, and many other procedural generated structures, and today I will show you how to use it to make ASCII land masses.
The first thing we need to understand is how to generate Perlin noise. This code generates Perlin noise, it takes in x and y values and returns a double value, which can be used to generate a heightmap or other types of terrain maps.
public static double PerlinNoise(double x, double y) { double total = 0; double p = .25; // amplitude control int n = 1; // frequency control for (int i = 0; i <= n; i++) { double frequency = Math.Pow(2, i); double amplitude = Math.Pow(p, i); total += InterpolatedNoise(x * frequency, y * frequency) * amplitude; } return total; }
The PerlinNoise
function takes in x
and y
values and returns a double
value representing the Perlin noise at that point. The total
variable keeps track of the total noise value, which is the sum of the noise values at different frequencies. The p
variable is used to control the amplitude of each frequency, and the n
variable controls the number of frequencies used.
The function loops over each frequency and calculates the noise value at that frequency by calling the InterpolatedNoise
function. The frequency and amplitude values for each iteration are calculated using the Math.Pow
function. The total
variable is updated by adding the noise value multiplied by the amplitude.
The function then returns the final total
value, which represents the Perlin noise at the given x
and y
coordinates.
static double InterpolatedNoise(double x, double y) { int integer_X = (int)x; double fractional_X = x - integer_X; int integer_Y = (int)y; double fractional_Y = y - integer_Y; double v1 = SmoothedNoise(integer_X, integer_Y); double v2 = SmoothedNoise(integer_X + 1, integer_Y); double v3 = SmoothedNoise(integer_X, integer_Y + 1); double v4 = SmoothedNoise(integer_X + 1, integer_Y + 1); double i1 = Interpolate(v1, v2, fractional_X); double i2 = Interpolate(v3, v4, fractional_X); return Interpolate(i1, i2, fractional_Y); }
The InterpolatedNoise function uses the SmoothedNoise function to generate four noise values, which are then interpolated to generate the final noise value. The x and y coordinates are split into integer and fractional parts, which are used to calculate the noise values of the four corners of the grid cell. The fractional parts are then used to interpolate the noise values of the four corners to get the noise value of the point.
The InterpolatedNoise
function takes in x
and y
values and returns a double
value representing the noise at that point, using bilinear interpolation to smooth out the noise. The integer_X
and integer_Y
variables represent the integer coordinates of the nearest corner of the grid cell containing the point, and fractional_X
and fractional_Y
represent the fractional distance from that corner to the point.
The function then calculates the smoothed noise values for the four corners of the grid cell using the SmoothedNoise
function. The smoothed noise values are then interpolated using the Interpolate
function to calculate the final noise value at the given point.
static double Interpolate(double a, double b, double x) { double ft = x * Math.PI; double f = (1.5 - Math.Cos(ft)) * 0.5; return a * (1 - f) + b * f; }
The Interpolate
function takes in two noise values (a
and b
) and a distance value (x
) and returns an interpolated value between them. In this implementation, the function uses a cosine interpolation method to produce smooth results.
static double SmoothedNoise(int x, int y) { double corners = (Noise(x - 1, y - 1) + Noise(x + 1, y - 1) + Noise(x - 1, y + 1) + Noise(x + 1, y + 1)) / 16; double sides = (Noise(x - 1, y) + Noise(x + 1, y) + Noise(x, y - 1) + Noise(x, y + 1)) / 8; double center = Noise(x, y) / 4; return corners + sides + center; }
The SmoothedNoise function takes in a coordinate pair (x, y) and returns the average of noise values sampled from its neighboring coordinates, weighted to produce smoother results. Specifically, it takes the average of the four diagonal corner coordinates, the four side coordinates, and the central coordinate. The noise values at each of these coordinates are calculated using the Noise function.
static double Noise(int x, int y) { int n = x + y * 57; n = (n << 13) ^ n; int t = (int)(random.NextDouble() * int.MaxValue); t = (t * (t * t * 15731 + 789221) + 1376312589) & 0x7fffffff; return 1.0 - (double)t * 0.931322574615478515625e-9; }
The Noise function takes in a coordinate pair (x, y) and uses it to generate a pseudo-random value. In this implementation, it uses a hash function that takes the bitwise XOR of the coordinate value with a constant (57) and then uses a pseudo-random number generator to generate another value, which is then hashed using a polynomial equation. Finally, the result is scaled and returned as a double value between 0 and 1.
To use the Perlin noise code in your own program, you would need to first copy the code into your C# project, then you can use the PerlinNoise
function by calling it and passing in the desired x
and y
values. The function will return a double value representing the calculated noise value at that location, which you would then use to assign a character to a 2D array based on that value.
Here's an example program that uses the PerlinNoise
method to generate an ASCII map stored in a 2D array of characters:
using System; class Program { static void Main(string[] args) { int mapWidth = 50; int mapHeight = 20; double scale = 0.05; char[,] asciiMap = new char[mapHeight, mapWidth]; for (int y = 0; y < mapHeight; y++) { for (int x = 0; x < mapWidth; x++) { double noiseValue = PerlinNoise(x * scale, y * scale); char character = ' '; if (noiseValue > 0.5) { character = '#'; } else if (noiseValue > 0.3) { character = '-'; } else if (noiseValue > 0.1) { character = '.'; } asciiMap[y, x] = character; } } for (int y = 0; y < mapHeight; y++) { for (int x = 0; x < mapWidth; x++) { Console.Write(asciiMap[y, x]); } Console.WriteLine(); } }
The final product is pretty random and may fit your use case.. but for me this is not what I wanted.. I wanted to create an island or continent for my world and I will be showing you how to do that in part 2 of this tutorial. Thanks for joining me, I hope you have learned something.
Endless Prose
The long awaited 3rd addition to the Epic Prose series!
Status | In development |
Author | logicandchaos |
Genre | Role Playing |
Tags | Text based |
More posts
- Finishing the LibraryApr 05, 2024
- Text Adventure Library - Builder PatternMar 27, 2024
- Text Adventure Library - Slaying the Spaghetti Monster!Nov 22, 2023
- From Prototype to ProductionSep 25, 2023
- Redesign!Jul 22, 2023
- Improving the Name Generator using ChatGPTApr 20, 2023
- Creatures & Encounters!Mar 30, 2023
- Travelling Across The MapMar 23, 2023
- Exploring DungeonsMar 13, 2023
Leave a comment
Log in with itch.io to leave a comment.