This post outlines some of the technical issues and solutions connected with the development of In Ruins, the second game produced for my PhD research.
In Ruins is an ambient exploration game with simple platforming and power up mechanics. It’s based on a small island of ruined castle walls and crumbling towers. This environment is procedurally generated using an extension of some traditional rogue-like dungeon generation techniques. More generalised information about the game and a download of the software is available at this link. A description of the approach developed for In Ruins is outlined below.
3D Rogue-like world generation
Although the world of In Ruins is 3D the architectural blueprint is based on a 2D floorplan generator. This generator can produce a range of layouts as illustrated below.
The generator is based on some of the ideas outlined in the d20 Random Dungeon Generator and operates in the following manner.
1. A world definition dictates the number of desired ‘rooms’ and the range of dimensions (width/height) of those rooms. This definition also describes the overall dimension of the world, which is used to build a 2d array grid that is used for the placement and arrangement of rooms, corridors and doorways. The definition allows for ranges of variation in parameters such as room size, corridor length and floor height.
2. A function AddRooms() attempts to place the desired number of rooms into the total base grid. There are a few placement routines that allow symmetrical layouts, sparse layouts, linear and constrained layouts etc. Each routine checks the validity of any newly placed room against the location of all previously placed rooms (checking for the intersection of walls and floor areas). The placement functions also ensure that there is sufficient space between rooms to allow for corridors and to avoid walls overlapping to double thickness. This is achieved by trapping all the placement locations and room dimensions to EVEN co-ordinates and room dimensions to ODD sizes. This ensures that the connections between rooms will line up correctly. When placing rooms in the grid the algorithm marks individual cells in the interiors as room tiles and those on the perimeter as wall tiles. This helps when checking the validity of subsequent room placements and also helps when locating doorways. Whne a room is placed it is recorded in a list, storing its ID, its x,y location and its
3. A function AddExits() places a number of exits in the wall perimeter of a room, it uses the centre-point and dimensions recorded for a room to calculate the location of potential exits which are then checked against the grid to see if the cells are currently marked as walls. If the exit is valid then the cell is marked as a doorway in the master grid.
4. The system then executes the AddCorridors() function. This steps through the master grid and whenever it encounters a cell marked as a doorway it attempts to ‘grow’ a corridor. The corridors are made by placing corridor tiles in a series of spans, starting at the discovered exit. Each span is of 2 cells length, to ensure it remains on the correct odd/even spacing and will connect to other doors. The corridors are then recursively generated from the endpoint of the last span. There is a chance for corridors to change direction or split at the end of any span. The probability of this is controlled by a series of variables in the general world definition.
The image below demonstrates these features as follows: empty grid cells are black, corridors are white, room outer walls are red, doorways are green and the interior of each room is assigned a random colour. Door cells also indicate their exit direction, n,s,e,w and interior room cells also store their unique id. This allows later functions to decorate or alter each room specifically. A webplayer of this prototype is available here to demonstrate the algorithms.
5. The system then extends this 2D floorplan by assigning floor heights to each room. The corridor cells also have height levels that are adjusted dependent on the room heights that they connect to. The corridor heights are modified in a recursive process that creates stairs between connected room of different heights. The plan is then used to generate a 3D ‘maze’. Rather than rooms being enclosed as they would be in a traditional rogue-like, each room is treated as a plateau. This leads to the construction of spaces like the one in the image below. A webplayer of this state of the prototype is also available here.
The construction process of the 3D version was then extended with the placement of various prop items and the variation of block forms.
The range of styles possible is quite large, as indicated in the following images.
To ensure that the resulting 3D model is graphically efficient the code that constructs the world from the floorplan calculates the neighbours for each cell and selects a 3D block that connects appropriately to adjacent cells. The image below shows the 16 blocks designed for all the possible connections in a Von Neumann neighbourhood. A good explanation of how this technique works for traditional tile games can be found here.
The mesh construction could be done procedurally, but by using a Look-Up table of prefab forms the code can substitute alternatives to create a more varied and organic look. A selection of three cube forms and their variations is illustrated below. The cubes themselves are also textured procedurally, using a custom tri-planar mapping technique. This means that however tall the individual columns become the texturing on the will not stretch or deform. This shader technique will be covered in a future post.
The final stage of generation involves dropping pre-designed tree models and grass prefabs onto the geometry. This is done by selecting a room from the list of those placed and then positioning the items within the rooms dimension and at the height the room is set to. This process could also be used to place specific items in specific rooms or place objects adjacent to walls or doors.