Part 3: Digital Cartography: procedurally generated Maps!
This is one of the more interesting areas of developing a roguelike game. Procedurally generated
Content offers the player a more varying experience since the game changes every time they load it.
Blog posts, websites, entire books have been dedicated to the kinds of algorithims employed in
producing and modifying procedurally generated content. For the sake of this tutorial, we're staying
simple, using an algorithim very much like the one in /u/TStand90's python tutorial, but modified
to work better in Swift. Alot of work we did in part 2 was place holder work to get the functions
we needed for this section working so we could better utilize them now. The first thing we're going
to do is modify the tiles array initializer code so that instead of creating a grid of tiles that aren't
blocking, we will now make a grid of tiles that ARE blocking, so the code for our makeTiles() function
should look like this:
The reason for this is because of the algorithim being used to create our rooms and dungeons. Picture a blank slab
of clay. this blank slab is our initialized array, completely uniform with no where to go. What our algorithim does
is "carve" out rooms by changing blocks of Tiles from blocking = true, to blocking = false. These blocks are the rooms
next, our algorithim starts "reaching out", flipping the value of a tile from blocking = true to blocking = false sequentially
as it grows outward. These are the tunnels that connect the rooms. So our dungeon is composed of three main features:
1) Rooms - large areas carved out of the grid that are walkable.
2) Horizontal "tunnels" - areas branching off of a room from left to right and right to left
3) Verticle "tunnels" - areas branching off up and down from a room
I should mention at this point that the branches grow until they encounter another branch or another room.
Since rooms are just glorified rectangles, were going to create another struct to make working with rectanges
a little less tedious. in your MapEtc.swift file, add the following code:
This struct holds the coordinates for the dimensions of the room we will be building
Since we now have an area we would like to turn into a room, we need to add a function
to our GameMap class that will change the tiles within that area from blocking = true to blocking = false
this is the "carving out" a room that was discusses above. the code for this function is as follows:
when all is implemented properly, you can make 2 calls to makeRoom using two of the Rectangle structures as
arguments that function should look like this:
and after calling that function, you should end up with a screen that looks like this:
Two rooms is only better than one if you have a way to get to both rooms. As we spoke of earlier
we need two more functions in our GameMap class. One to make horizontal tunnels, and another to make verticle
tunnels, add the following functions to your GameMap class:
Now we need to modify our Rectangle struct to have two functions which will allow us to know 2 important things about
the rooms we are creating: 1) At what coordinates the center of the room lies, and 2) if the rectangle being created
collides with one that already exists. do to this, add the following changes to Rectangle struct:
Now, lets pull all the pieces together to make a procedurally generated map. The thinking behind this
algorithim is this: set a limit as to the total number of rooms we want, keep creating non intersecting (as much as is possible)
rectangles untll the limit has been reached. randomly choose wheter each new room will have a tunnel going away horizontally(50%) or
have a tunnel going away vertically(50%). once these conditions have all been met, we will have our map. to do this
we start off by completely erasing our makeMap function. (the one that just made two rooms and displayed them)
and starting over. Our new makeMap function is going to look like this, ill go through it piece by piece below for a better explaination
the first step as we mentioned is that we will be making a loop, that each time it completes will result in a room and a tunnel
being laid. below that, we use the tcod.getRandInt(min:max) function from the swiftTCOD wrapper to generate random
values for the width, height, and starting x and y positions for the room to be made. the values passed to the getRandInt
function ensure that the rooms will lie within the parameters of our map. Once those values are generated we
we initialize a new instance of a Rectangle with the parameters we just generated.
If this is the first Room generated, we dont need to do anything else through this iteration of the loop, so we just
pass the Rectangle to the makeRoom function, add the room to our array of rooms, increment the room counter (numRooms),
and have the loop start through for the next round through.
This is where the algorithim gets interesting:
When all the parts are brought together and are working as they should,
the end result will be your procedurally generated map
Keep in mind that there is ALOT that goes in to even a simple procedurally generated map like this
the internet is awash in fantastic resources and myriad discussions on ways of making maps.
Complex maps, cool looking maps, maps that spell things, your imagination is the limit!
experiment, create, fiddle with the number of rooms, the length of the tunnels, the max size, etc
change the layout, the order in which their made. try to make a circular map! for example, heres the difference
wuth doing nothing but changing maxrooms = 15 instead of 30:
The challenges involved can be truly rewarding, and i promise youll walk away a better programmer
albeit a slightly frustrated programmer.
go make some coffee and get ready for part 4!