(rss)
Oct. 11, 2008
Game tech dabbling

I've been re-visiting some code I wrote quite a while ago which generates a virtually infinite number of generic 2D RPG maps, with grass, sand, road, trees, and water, like this:

Perlin Noise based 2D RPG map

Here's the algorithm I have used, implemented in Python, using a Perlin Noise generator:

detail = abs(perlin.Noise2D(x / 300.0, y / 300.0))
desert1 = -perlin.Noise2D(x / 100.0, y / 100.0)
desert2 = perlin.Noise2D(x / 10.0, y / 10.0)
desertp = desert1 * detail + desert2 * (1.0 - detail)
waterp = perlin.Noise2D(x / 150.0, y / 150.0)
roadsp = (abs(perlin.Noise2D(x / 50.0, y / 50.0)))
treep = max(0, perlin.Noise2D(x / 300.0, y / 300.0)) + perlin.Noise2D(x / 3.0, y / 3.0)

oasis = desertp > 0.95
lake = desertp < -0.6
river = desert1 < 0.6 and abs(waterp) < 0.075
desert = desertp > 0.6 and desertp < 1.0 or desert1 > 0.8
roads = abs(roadsp) < 0.05
riversideRoads = desertp < 0.6 and abs(waterp) < 0.11 and abs(waterp > 0.08)
trees = treep > 1.15

if lake or river or oasis:
    DrawWater(x, y)
elif roads or riversideRoads:
    DrawRoad(x, y)
elif desert:
    DrawDesert(x, y)
elif trees:
    DrawGrass(x, y)
    DrawTrees(x, y)
else:
    DrawGrass(x, y)

One problem is that I couldn't find a decent perlin noise generator for Python that was easy to install, so I had to roll my own, and it's dog slow (apologies to any dogs who are reading this). I even tried re-implementing it as a Python extension in C but it was still only twice as fast. Psyco also did nothing to help the situation, which basically means it isn't slow because of bad optimisation but because the algorithm is very CPU intensive. The good news is that a few nights ago, while I couldn't sleep, I think I came up with a really quick way of generating random fractal based landscapes by caching the results of previous, higher order position lookups. If I'm right, it kind of turns the Perlin algorithm inside out to prevent redundant calculations. Of course, it will trade off speed for higher memory usage, but that's ok in this situation. I haven't tested my idea out yet, but hopefully I will get around to it and make my map algorithm much faster.

Another problem is that my existing maps won't wrap onto a torus without discontinuities, which means you have to have infinitely large maps for people to explore, which go on and on forever. I'd rather the maps wrapped so I could wrap them around planets and stuff. Fortunately, I think that with the judicious use of modulus operators my new algorithm should wrap pretty nicely.

Characters

I came up with these vector art characters which I think would be suitable for a 2D RPG style game.

Vector art RPG characters

They're SVGs made in Inkscape, which is highly scriptable, so I am thinking about prototyping a character creator screen which will allow you to deck them out in different colours, clothes, accessories, etcetera, and then use Inkscape to render variations in the background.

3 years, 6 months ago - Sun 20 Dec 2015
P D

Hi! Thanks for this, it looks great! Is there any way we could get the code you wrote for python for the perlin noise? it doesn't matter to me if it's slow!

3 years, 6 months ago - Sun 20 Dec 2015

I am sorry, I don't think I have that code any more. However there is this which is probably much better:

https://pypi.python.org/pypi/noise/

3 years, 6 months ago - Mon 21 Dec 2015
P D

Hi Chris, thanks for the quick reply!

I am really intrigued by your algorithm as from the screenshot you provided the results seem stunning and perfectly suited to what I need, but I can't figure out what the parameters are... Do you generate a single 2d perlin noise function which produces a ''smoke'' of sorts and then use the x/300 etc. values on THAT map to decide what each point in the ultimate image will be?

Or do you call the perlin noise function for each pixel's coordinates for the entire end image size?

I'm not sure if I'm being clear enough here...

Is it that you do something like

noise = perlin.Noise2d()

and then do

for each x,y in end image size: detail = abs(noise(x / 300.0, y / 300.0)) desert1 = -noise(x / 100.0, y / 100.0)

OR actually call a new perlin.Noise2d function for EACH x,y pixel coordinate pair for the end image's size? This seems to be what's happening from your code snippet, but I'm wondering how this could produce coherent results...doesn't the Noise2d function produce different results every time it is run? Or is it deterministic somehow?

I guess I'm essentially just asking what the x and y parameters represent here, and if the noise function is called once then referenced or if it is actually called many times per pixel.

I really want to understand this excellent algorithm. :)

3 years, 6 months ago - Tue 22 Dec 2015

Hi P D,

Here is the same algorithm implemented in Javascript:

https://github.com/chr15m/jsGameSoup/blob/master/demos/noise.html

And a very similar copy in ClojureScript:

https://github.com/infinitelives/infinitelives.procedural/blob/master/src/infinitelives/procedural/maps.cljs#L7

The Perlin/Simplex noise function you use should:

  • Be seedable.
  • Return some consistent result for some set of coordinates and seed.

So in your example above basically you should be able to do something like:

noise = perlin.Noise2d(seed=42) noise.get2d(x, y)

The seed is what makes the noise function return the same value for some x and y and seed. Some implementations have an implicit seed and are seeded with a random number at creation time.

Leave a comment

(use markdown syntax if you like)