Procedural Dungeon Generation: A Roguelike Game

18

Attached Files

The following files have been attached to this tutorial:

.capx

Stats

19,516 visits, 29,793 views

Tools

License

This tutorial is licensed under CC BY 4.0. Please refer to the license text if you wish to reuse, share or remix the content contained within this tutorial.

Published on 6 Aug, 2014. Last updated 19 Feb, 2019

Overview

In this tutorial, we will learn how to create a Roguelike-style game. This style involves "procedural generation", which means that the level design (rooms, hallways, player/item/enemy placement) is randomly determined every time the game starts, but the layout is generated intelligently (in particular, every room can be accessed by a hallway leading to it).

Here we will be implementing the algorithm presented at http://gamedevelopment.tutsplus.com/tutorials/create-a-procedurally-generated-dungeon-cave-system--gamedev-10099 using Construct 2. The main steps in this process are:

0. Fill up the level with small squares with the Solid behavior.

1. Create a total of N rooms with random sizes at random locations.

2. Create a horizontal hallway and a vertical hallway between pairs of rooms, linking room 1 to room 2, room 2 to room 3, ... , room N to room 1 (we have chosen to connect the last room to the first so that there are no "dead-end" rooms in our level).

3. "Dig out the rooms." In other words, remove all the Solid objects that overlap room/hallway sprites.

4. (Optional, but recommended for efficiency): Remove any of the remaining blocks that do not border a room/hallway.

5. Place the player in a room, place an item in each room, and place enemies in some of the hallways.

Stone and Room Placement

First, in this example we set up the project with a layout size of 1600-by-1600 (and when we are finished, a window size of 400-by-400, but for testing purposes a window size of 1600-by-1600 to match the layout size so that we can visualize the entire level). We use nested For loops to generate a grid of objects named "Stone", which are 32-by-32 and have the Solid behavior. We also create a TiledBackground object called "StoneBorder", using the same image as the Stone object and also having the Solid behavior, and we create four 1600-by-32 versions of this object and position/rotate them to use as boundary walls for our level.

Then we create a certain number of rooms, randomly setting their size and position as shown in the event sheet below. It is possible that some rooms will overlap, but that's okay -- the end result will be interestingly-shaped rooms that contain more than one item.

For each Room, we spawn an associated object called a RoomCenter, which will be useful (for filtering purposes) when we need to create the Halls. We also set an instance variable called "ID" for Rooms and RoomCenters; the hallway objects we create will connect a Room with ID=X to a RoomCenter with ID=X+1. In order for the final Room to connect to Room 1, we use a little trick: change the ID of RoomCenter 1 to be one more than the total number of rooms.

At this point, one possible level that could be generated looks like this:

Hallway Placement

Next, we connect the Room with ID=X to the Room(Center) with ID=X+1 using two hallways: one horizontal and one vertical.

Horizontal hallways need to have width equal to the horizontal distance separating the rooms, calculated using abs(Room.X - RoomCenter.X), and the height can be any constant, somewhat larger than the player/enemy sprite dimensions. These hallways will be placed to align vertically with the Room (set Hall.Y to be equal to Room.Y); since the sprite anchor is in the center by default, the X coordinate needs to be the midpoint between the two rooms, calculated using (Room.X + RoomCenter.X) / 2.

Vertical hallways are sized/placed similarly, except that horizontal/vertical calculations are switched. The width is a constant and the height is abs(Room.Y - RoomCenter.Y); the X coordinate should be equal to RoomCenter.Y and the Y coordinate should be equal to (Room.Y + RoomCenter.Y) / 2.

At this point, one possible level that could be generated looks like this:

Border Placement and Stone Removal

At this point, we could just destroy all the stones that overlap either a Room or a Hall, and we would have a functional level. However, this leaves lots of extra Stone sprites that require additional rendering time, and that can never be collided with, so they don't influence the gameplay. Therefore, we take the extra step of creating Border objects: set each Room and each Hall to spawn a Border object whose size is set some fixed amount larger than the Room/Hall that spawns it.

Then when removing Stone objects, we want to destroy any object that overlaps a Room -or- overlaps a Hall -or- is not overlapping any or the Border objects. This latter condition is a bit tricky to implement. It is tempting to set up the event:

Condition: Stone X (invert) is Overlapping Border --- Action: Destroy Stone

However, it is almost guaranteed that every stone can find one border that it is not overlapping, thus the condition will be true for all the stones and they all become destroyed. So, instead we set up two events: give the Stones a boolean instance variable called StoneBorder, initialized to false. After the Borders have been created, if a Stone is overlapping a Border, set the value of StoneBorder to true. Finally, destroy all Stones whose StoneBorder variable is set to false.

At this point, one possible level that could be generated (prior to Stone removal) looks like this:

Player/Item/Enemy Placement

At this point, you could place an item in the center of each Room for the player to collect, place the player in Room 1 (offset a bit from the center of the room), and randomly choose a few Halls to place enemies in (placing enemies in Halls rather than Rooms reduces the chance that they will be too close to the place at the start of the game. To make the enemies interactive, you could add the Line Of Sight behavior, and have them chase (move towards) the player when the player is in sight (I usually change the range of this behavior to half of the window size, so they only begin to chase you when you can see them).

Finishing Touches

At this point, you will want to make sure that your window size is smaller than your layout size (since the game wouldn't be nearly as fun if you could see everything at once). Also, you should make all your Room/RoomCenter/Hall/Border objects invisible; they have served their purpose. It is also a good idea to include a GUI layer (with Parallax set to 0,0) containing a Text object that states how many objects you have collected and how many remain. These features have been implemented in the Capx file attached to this tutorial. Other fun additions to consider include giving the player a weapon to swing or fire at enemies, or rooms that contain traps that you can lead enemies into to destroy them. Lots of possibilities exist, your imagination is the limit!

.CAPX
  • 2 Comments

  • Order by
Want to leave a comment? Login or Register an account!