-
Noah: Our next speaker is
Herbert Wolverson.
-
Herbert is a hobby game developer,
and he has been since the 1990s.
-
He developed Nox Futura,
One Night in the Dungeon,
-
and a bunch of 7DRLs.
He also wrote a book
-
about Rust roguelike game development,
and he's currently writing another book
-
about learning Rust through making games.
-
This talk is called
Procedural Map Generation Techniques,
-
and this is acctually something I'm
super excited about, as I've always
-
wanted someone to give a talk
about this topic.
-
So without further ado, here's Herbert.
-
Herbert: Hi. Welcome to
Procedural Map Generation.
-
I'm Herbert Wolverson of
Bracket Productions,
-
which is really just my consulting company.
-
I'm gonna be talking to you about
using algorithms to generate
-
maps, and then using more
algorithms to fix the maps you
-
just created and find out if they're
even usable.
-
There's a lot of content to cover,
so I'm gonna be going on
-
at quite a rate of knots.
-
So, who am I?
As Noah just said, I'm the author
-
of the Rust Roguelike Tutorial,
on the address on the screen there.
-
I talk way too much on Twitter
@herberticus. If you ever want to
-
get in touch with me, /u/thebracket
on Reddit is the best way to find me.
-
I love hanging out on the roguelike
dev forums, answering short answer
-
questions with SAS. I do have a book
coming out very soon, Hands-on Rust:
-
Effective Learning through 2D
Game Development and Play,
-
which will be out on PragProg.com.
-
I promise I won't keep promoting that,
but I really wanted to mention it,
-
and all the sourcecode that goes with
this talk can be found on GitHub
-
at that address. That'll be repeated
on the final slide of this presentation.
-
So we're gonna give a quick
shoutout to Rogue, from 1980,
-
because we really wouldn't be here
without it. It's one of the
-
seminal and first uses of procedural
generation that really defined
-
the genre. It splats out up to nine rooms,
connected randomly.
-
It's been said that they did that
because they needed to keep
-
the game small and really couldn't
fit in 50 beautifully hand-designed
-
levels, but really that created one
of the strengths of the genre
-
in the first place. When you start
a new game, you have a different
-
map every time. So every time you
play, instead of memorizing that
-
I need to go up, up, up, right,
you're learning instead that
-
I should interact this way with
different monsters, and interact
-
this way with rooms, navigate like
this, learning the system rather
-
than learning the game itself,
giving you effectively infinite replay.
-
Also, any talk on procgen really
has to mention Dwarf Fortress,
-
because no other procgen game
has ever managed to cram this
-
much procedural generation into
one binary.
-
If you haven't played it,
go out and do so.
-
Support them on Steam, and
tell them how awesome they are.
-
They generate everything from
a massive overworld with
-
sweeping mountain ranges,
dense forests, volcanoes,
-
demon-infested fortresses,
fill it up with procedurally-generated
-
civilizations who either like
or hate each other, and then
-
you can drill all the way down
to Urist and find out which
-
proedurally-generated deity
he worships, which procedurally-generated
-
tavern he drinks in, and learn all
about his quest to cross the land
-
and find the perfect sock.
-
At the mid-scale, you can zoom in
to any particular land block on the map,
-
find out that it is beautifully rendered,
still matches the overall shape
-
of the overall world above it,
the trees gain foliage, lose foliage,
-
depending on their type and the
type spawns appropriate to the biome.
-
In other words, there is pretty much
every procgen technique that you
-
can think of in this game.
Hang out in the Bay 12 forums
-
and that is a great place to
learn about them.
-
So one thing that's clear from
both of these games is that,
-
while they're random, their randomness
doesn't define the game.
-
The randomness is fed into an
algorithm that generates something
-
that approximates what you want
to get, but ensures that it is different
-
every time. So, we're gonna
start looking at some algorithms
-
for doing that.
-
A simple room placement.
If you've done any of the
-
roguelike development tutorials,
then you've seen this one.
-
You start with a solid map. You
pick a random rectangle.
-
If there isn't a room already there,
you fill in the rectangle.
-
You keep adding rooms until you
meet whatever criteria you've
-
come up with for enough,
and then you join the rooms
-
together with corridors.
Let's watch this in action.
-
As you can see, it's placing rooms
randomly. When you see the
-
red rectangles with the exclamation
marks, they tried to generate one
-
that overlaps, so you have to discard
it and try again.
-
And then it joins them together with
corridors. In this case, they went
-
with a simple dog leg algorithm,
that randomly switches between
-
being either vertical first or
horizontal first.
-
That's pretty much exactly the
algorithm from the Python
-
roguelike dev tutorial that the
roguelike dev subreddit recommends
-
that you start with.
-
So a good refinement of that is
Binary Space Partition rooms, or BSP.
-
Binary Space Partition is fancy
developer talk for chopping in half.
-
It gives a similar result to
random room placement,
-
but it pretty much guarantees
that everything is better spaced out.
-
You'll find that it's used in Nethack,
amongst other games.
-
So to do BSP, you divide your map
in half, and you randomly decide
-
whether you want to divide vertically or
horizontally, and divide that half in half.
-
Then you keep dividing your halves
all the way down until you have
-
a space that's roughly the right size
you want to use for a room.
-
And I personally like to then
add a gutter of one tile around it,
-
to ensure that you don't get rooms
joining together, unless that's
-
what you want. So watching that
in action,
-
you see that there's absolutely no
rejections, the rooms are nicely
-
spaced out, and you wind up with
a pretty usable map.
-
So complete change of gear,
Cellular Automata.
-
I personally love this one, because
it involves an ordered-looking
-
natural map from complete chaos.
You know, I said you don't always
-
start with complete random?
Well, this one, you do.
-
You can literally make half your
map walls half your map floors,
-
randomly picked, no chance of
getting through it,
-
and you'll end up with something
you can use.
-
If you played Conway's Game of Life
or seen articles about it,
-
it's the same principle here.
So once you've got your map,
-
you make a copy of it,
you iterate every tile that isn't
-
on the edge, and you count the number
of neighbors, including diagonals.
-
If there are no neighbors, then it
becomes a wall. If there's one to
-
four neighbors, it becomes empty.
Five or more neighbors, it becomes
-
a wall. Now, you can and should
tweak these rules to suit your game.
-
Let's watch this in action. Completely
random soup quickly turns into a
-
far more natural looking structure that
can actually be turned into a
-
pretty useful map. And it's a really
simple algorithm, really fast,
-
and given the same seed, you get the
same result every time.
-
Next one to talk about is Drunkard's Walk.
This is another popular one for beginners.
-
So the basic principle of Drunkard's Walk
is that you start with a solid map, find one
-
Umber Hulk, and ply it with beer until
he's staggering randomly.
-
Place him somewhere on the map.
As he staggers, he randomly smashes
-
through walls, and carving out a map.
Don't bother worrying about whether
-
he backtracks. Let him stagger wherever
he wants. Pick a maximum distance for him
-
to travel, or her. I'm really not sure how
Umber Hulk's gender works.
-
If they travel too far, they pass out,
and you can pick another
-
Umber Hulk, spawn them somewhere
within the open map, and let them smash.
-
Then you stop spawning Hulks when
enough of your map is open.
-
In this case, I've used one-third of the map.
-
So watching this in action, you can see
that each Hulk is staggering
-
completely randomly, carving out
more and more of the map.
-
One big advantage of this is that you
can guarantee that your map will
-
be contiguous. It won't generate
any areas that your player
-
can't reach. It tends to give you
maps that looks like it was
-
carved out by water, ideal for
creating limestone caverns and similar.
-
Another algorithm, Diffusion Limited Aggregation.
-
I highly recommend the RogueBasin
article on this one.
-
So for the simple version of this,
you start by digging out a small
-
target seed, and then you pick a random
point anywhere on the map, a random
-
direction, and shoot a particle.
If you're really lucky, the particle
-
will hit one of the already open areas.
If it doesn't, you keep shooting until it
-
hits something. Something I've
heard from a lot of military friends.
-
In the event that you do hit a target
area, then you carve out the last solid
-
area that you passed through. This
tends to give you a very winding,
-
open map. Again, it's guaranteed
to be contiguous.
-
And there's a lot of ways that you can
tweak the algorithm to make something
-
more interesting.
-
So the Central Attractor is much
more likely to always hit the target.
-
You randomly spawn your starting point
and then shoot the particle directly
-
at the middle of the map.
This pretty much ensures that you
-
get an open space in the middle,
ideal, for example, for you to put
-
a dragon with his hoard, and then
a more interesting pattern forming
-
around the edges, which could be where
the hobbit would lurk while he
-
torments the poor dragon. On the
right, you can see what happens if we
-
use the same Central Attractor
algorithm, but simply apply
-
a symmetry down the vertical.
You start to get maps that look
-
a lot like they either want to eat
you, or maybe it's a butterfly,
-
maybe it's a Space Invaders character.
-
I recommend using this one sparingly,
but a bit of symmetry like that can be
-
applied to any algorithm when you
want to produce something
-
that looks a lot less random.
-
Another algorithm that everyone should
have in their toolkit is the
-
Voronoi Diagram.
-
So to make one of these, I do
recommend Googling it.
-
You randomly or deliberately place
seeds all over your map.
-
You randomly place them if you
just want to produce something random.
-
If you place them very deliberately -
for example, you can equally space
-
them all over the map - you can
produce something that looks
-
very, very much like a beehive.
-
And then you iterate every point
on the map, and it joins the
-
area belonging to the closest seed.
There are various fancy algorithms,
-
like Delaunay triangulations, to do this
quickly. In the source code I've
-
provided, I just brute forced it
and did it the hard way.
-
Now, one neat trick for customizing
what you get is that you can
-
use a different distance algorithm
to determine which group
-
every tile joins. So on the left, I've
used Pythagoras, and it gives you
-
relatively smooth edges. Using Manhattan
distance, which is the same distance
-
that you get if you ask a New York
taxi driver how far it is to get to
-
somewhere, a number of blocks north
plus number of blocks east,
-
it gives you a much more manmade-looking,
sharp-edged structure. And Chebychev
-
is a similar distance algorithm, and
tends to give you somewhere in between
-
the two. So what do you do with
Voronoi diagrams? Well,
-
the first thing you can do with them is
find the edges, place walls there,
-
and wind up with an alien cell structure.
Another good thing to do is
-
decide that when you're spawning monsters,
you'll spawn the monsters together
-
within a Voronoi cell.
-
So, for example, you've decided that the
orc king should always be
-
seated with his retinue. Then,
you should probably spawn them
-
in the same cell together, if possible.
Likewise, if you decided that orcs
-
must never, in fact, be near dark elves,
make sure that you spawn them in
-
far away cells. You can also use this
for very effective city generation.
-
In my PROCJAM game, Apocalypse Taxi,
I used the edges of Voronoi cells to
-
determine where the roads went, and then
randomly populated the content of each
-
cell with something like heavy industrial city,
light industrial, and it worked pretty well.
-
Combined with other techniques, this can
be extremely useful.
-
The next one up is Perlin/Simplex Noise,
which is used all over the place,
-
more than you might think.
-
So Perlin/Simplex Noise are basically
a bunch of gradients combined together
-
with a few variables we'll talk about
in a moment.
-
You can generate it in either two or three
dimensions. If you look in X/Y value,
-
it gives you a number between -1 and 1.
-
And the nice thing is, if you look at
an adjacent one, it will be smoothly
-
moving either up or down,
relative to the neighboring squares.
-
It's also continuous, so if you decide
to look from X 1 through 2,
-
and then decide to zoom in to
1.5 through 2, you'll actually get
-
exactly the same shape as you saw
on the right-hand side in the first one.
-
So what do the variables mean?
Now, the number of octaves
-
give how many different gradients it's
mixing in. Gain is how long the various
-
gradients last. Lacunarity adds in
randomness, and as you can see,
-
from the picture on the right-hand side,
which I guess is my left, this starts
-
to feather it in and make it look a lot
more random.
-
Now, frequency is how frequently
each of the various octaves
-
peaks. So, what I recommend you do
is find a Perlin/Simplex Noise tool
-
and simply play with these values until
you get something that you like.
-
So what can you do with them?
The answer is actually quite a lot.
-
The most common use is to make
an overworld. Because Perlin
-
is deterministic by random seed,
every time you use the same seed,
-
you get the same map.
Now, what I've done here is
-
altitudes from -1 to 0 blued out
as water, altitudes from 0 to .75
-
are in green, shaded as terrain,
altitudes above that are marked
-
as mountains. And this is very common.
You'll find this used in a lot of games.
-
And as I mentioned, you can zoom in.
So as you zoom in, you start to see more
-
of the gradient. The problem is
that the gradients are actually
-
kind of dull.
-
Now, you can fix that. The best way
to fix that is to make a second layer
-
of noise. So in this case, we've got a
second layer of noise that is much more
-
bumpy, has much more fine-grained
detail, but looks terrible as
-
a continent as a whole.
And then as you zoom in,
-
you change the percentage of each
of the two gradients that you're
-
mixing together. So when you're zoomed
all the way out like that, you're seeing
-
the relatively coherent overworld.
As you start to zoom in, you see
-
bays, peaks, valleys, troughs.
And the great thing is, this is
-
actually really easy to implement.
There's a lot of really good
-
Perlin/Simplex libraries out there.
I highly recommend it.
-
You can use it for anything from making
a pirate game with cool seas
-
to sail around, or just building the
overworld for your map.
-
You can also use it if you want
to generate realistic-looking
-
clouds, particles. You can even make
wood grain with it, with the right values.
-
And that brings me to one point
that I can't emphasize enough.
-
You very rarely need to use just
one technique on a map.
-
So here, on the top left, we've used
WSP technique to produce a
-
pretty linear-looking dwarven fortress.
On the right, Cellular Automata
-
has made a really open-looking
sort of cavern system. It might
-
be a dangerous underworld.
So running with that theme,
-
I simply took the left half of the
dwarven fortress and the right half
-
of the Cellular Automata, and you
have what looks like a fortress
-
that suddenly breaks out into a nasty
underworld. Well, I thought,
-
a better theme for that might be that
the dwarves were digging, they
-
came to the underworld, and they
needed to protect themselves.
-
So I added a prefab, a predesigned
section in the middle, adding fortifications.
-
And so, with two very simple techniques,
you've produced a map that actually
-
tells a story, rather than just
being a bunch of random rooms.
-
Another popular combination is
take a map and then use DLA
-
to fire particles at it, and blast
parts of the map away.
-
This starts to look like dwarves failed
to pay their maintenance bill,
-
or had a serious water problem,
as the map becomes more and more
-
organic-looking while keeping its
basic structure.
-
I mentioned prefabs before.
Prefabs are a great idea.
-
So you can make a massively
random map, but if you start injecting
-
human design elements into it, then you
get the chance to really make the game
-
your own.
-
So I've got a prefab here that is
definitely not a trap. It's death
-
traps surrounding a treasure chest.
You've probably seen that in a lot
-
of console RPGs. You enter a
room, you see a chest,
-
with nothing guarding it.
You can be pretty sure that
-
something bad's about to happen to you.
-
So the room structure makes it relatively
easy to place a prefab. You can go
-
through the room list, find a room that's
bigger than the prefab, and you know
-
the prefab fits there.
-
On a map that doesn't have rooms,
it can be a little more tricky.
-
What I usually do is I pick random
locations, look and see if the prefab
-
will fit, and place them. If it won't,
then I don't place it there.
-
If I've got a nice, big list of prefabs,
then I'll keep going until I've added
-
a few of them and been careful not to
add all of them, so the game is
-
different next time I play.
-
Very quick change of gears,
because the next set of algorithms
-
all are going to rely on this one,
the Dijkstra maps.
-
I highly recommend that you go to
RogueBasin and read
-
The Incredible Power of Dijkstra Maps
on there. It is an excellent article.
-
And Dijkstra maps are a very powerful
tool if you're into making roguelikes.
-
I recommend that you learn about them.
-
To make them, you start with one
or more starting points.
-
In this case, the blue knight. The rest
of the map gets marked to a sentinel
-
value, meaning you can't go there.
And then you look at all the tiles
-
adjacent to your starting point.
If you can walk into them, you put
-
a 1 on them. You can get there
in one step away from the starting point.
-
Then you keep going. So every
walkable tile that's next to a 1
-
gets a 2, and a 3, and a 4, and a 5.
-
Eventually, you have a complete
map that tells you, first of all,
-
the sentinel value means that you
can't go there. So now, you've got
-
a list of all the cells you can't use.
Likewise, the numbered tiles tell
-
you how far away you are from
a starting point.
-
So the first big use for this is when
you've generated something like
-
Cellular Automata, that can give you
chunks of a map that you can't get to.
-
Pick a central point, find one
that's open, generate a
-
Dijkstra map, and then all of the
tiles that are open that have the
-
sentinel value on your Dijkstra map
can't be reached, and I've highlighted
-
those in red on the map.
You can delete those, because
-
nobody can get there. Or,
if you've got a game system
-
that encourages tunneling, then go
ahead and hide the stuff that needs
-
to be reached with tunneling
in those areas.
-
This can also help you with placing
a starting point on your map.
-
Making sure that you cull the
unreachable areas before
-
you pick a starting point ensures
that you won't place the player
-
somewhere that they can't
get out of, and it avoids the
-
always embarassing case of the
map that's only got two tiles
-
that you can actually walk on.
-
That has happened to me.
-
So typically, to place a starting point,
if I know that I want to be on the
-
left, I'll place somewhere in the
middle on the left, and then I'll
-
simply look around for a neighboring
tile that is open and hasn't been culled
-
by the Dijkstra map.
-
To find an endpoint, you can
use the same thing, if you want
-
to go left to right. A lot of
games prefer to have more of a
-
structured progression.
Once you've culled the map,
-
you know that anywhere on the map
can be - that is still open after you've
-
removed the areas you can't get to,
it's safe to place an exit.
-
So you find the rough area you want
to put the exit and you place on a nearby
-
open tile. I've gone ahead and marked
the optimal route through that map.
-
You can also, if you really dislike
your player, use Dijkstra to find
-
the least accessible part of the
map and put the exit there.
-
If they're starting in the middle,
this is a really bad idea,
-
because three-quarters of the map
is basically irrelevant.
-
However, if you want to hide something,
this is a great way to do it.
-
Find the starting point, then find
the least accessible point on
-
the map, hide something there, and
you force the player to go on a
-
treasure hunt, if they're gonna get
the bonus item.
-
That leads to one of my favorite
techniques for refining a map,
-
once you've generated it.
I call it the "Hot Path."
-
Once you've got your start point
and your end point, you generate
-
the path between the two.
I use ASTAR. In this case,
-
I think I used a Dijkstra map and just
walked the distance between the two.
-
Then you make another Dijkstra map,
using all the points on the path as
-
your hot path. Then, on that Dijkstra map,
everything with a tile value of under,
-
say ten, is the hot path and is
close, but not necessarily directly
-
on the best path through the dungeon.
-
So what can you do with this?
Well, if you prefer a less-branchy game,
-
you can use it to remove the parts of
the map that are completely irrelevant.
-
If your game involves a great deal of
progression from, say, left to right,
-
this is a good way to do it, and a
good way to not force the player
-
to explore huge, winding labyrinths
with no real purpose to them.
-
If you're using a room-based generation,
then this can be even more useful.
-
I've marked in yellow the rooms that are
on the hot path that go directly
-
from point A to point B.
Also, that means the grey
-
rooms aren't really necessary.
Or are they?
-
Maybe the grey rooms could be culled,
if you just want to have a go to room,
-
go to room, go to room type of game.
Or, the grey rooms could be where
-
you hide bonus things. You might have
the yellow rooms mark some breadcrumbs
-
to give the player a clue as to where
to go, and teach them by putting
-
really scary things in the grey area
that are off the beaten path
-
and only to be done once you're
a little more experienced.
-
You can also use this knowledge
to order the progression of the story.
-
So you know that if the player's gonna
go through this dungeon, they're
-
probably gonna go through one to nine.
-
And that's okay. If you're telling
a story, let's say your grandfather
-
tells you that it is your destiny to go out
and save the world, he's probably
-
going to want to tell you that somewhere
around room one, so you can't avoid
-
the old windbag. Somewhere around
two, somebody should show up and
-
tell you that your destiny is futile,
you should abandon it,
-
give up, and the whole adventuring
thing isn't for you, and so on.
-
Alternatively, you might have decided
that you wanted to do the old
-
saw of the locked door that has a
key somewhere on the map.
-
Supposing that you've decided that room
five is gonna be where the locked
-
door shall be, it would be a good choice
because you can do a flood-fill and
-
discover that there's nowhere else,
there's no alternates to going through
-
the exit from room five.
-
So you place a locked door there, and
now you absolutely know that the key
-
has to be somewhere between rooms
one and four, for this to be a
-
solvable puzzle. It would really suck
if you decided to spawn the key
-
in room six, and leave yourself with
a map that cannot be solved
-
without finding some other way to
open the door. You know, unless
-
of course that's your point.
And really, that is the point
-
of what I've been trying to say,
is guide your randomness
-
and then use algorithms to check
your randomness, to make sure that
-
it matches what you want.
-
I've written quite a bit about
other algorithms on my
-
roguelike tutorial, tried really hard to
be language-agnostic, even though
-
all the examples are in Rust.
So, I do recommend checking there
-
for a few more. I wanted to talk about
Wave Form Collapse, and realized
-
that I needed another 30 minutes.
-
Alright, so like I said before, the source
code for this talk is all up on my GitHub.
-
That repost should have gone public
a few minutes ago.
-
And just to be a terrible shill one more
time, my book
-
Hands-on Rust: Effective Learning through
2D Game Development and Play
-
will be in beta on PragProg relatively
soon, next few weeks, as long as
-
I get off my butt and finish editing it,
and in your favorite bookstore
-
pretty soon. If there's any questions,
I'd be happy to take them.
-
Noah: Wonderful. Thank you so
much for that talk.
-
Herbert: Thanks for having me.
Noah: Yeah. We have a few questions.
-
The first one is from Darren Gray,
who asks, "How do you make
-
generated maps not look very
samey?" For example, he says that
-
he can always notice when an
overworld is generated with
-
Perlin noise.
-
Herbert: Yeah, that's actually a fun one,
and I play spot the Perlin.
-
One of the best things to do is
to generate multiple noise maps
-
and mix them together. So if you've
decided that this region is mountains,
-
start mixing in height values from another
more mountainous-looking height map.
-
That gets you something that is,
as well as just going up,
-
actually goes up and down, up and
down, and starts to look more like
-
mountains and less like a boring
little gradient.
-
You can also have a lot of fun
generating your Perlin, and then
-
run something like a Russian
on it. Running a simpler
-
Russian simluator, especially if you
decide that some rocks are
-
harder than others, gives you
a beautiful map in no time.
-
I think that's how World Machine works.
-
Noah: Cool. Our next question is
do you know any techniques for
-
generating vertical structures,
or Z levels in map generation?
-
Herbert: That is something I am
honestly terrible at.
-
I've seen Wave Form Collapse
used to make some absolutely
-
amazing Voxel-based cities. I can't
honestly pretend that I know how
-
to do that.
-
Noah: Cool. That looks like all
of the questions that people have
-
put in.
-
But thank you so much, and
then I'm sure if you hang out
-
somewhere in the social space, people
will come and ask you more questions.
-
Herbert: Yes, I should be around
there and on the Discord tonight.
-
I'll be editing my book and alternating
between the two. Thank you.
-
Noah: Cool, thanks so much.