Room Styling in Lenna's Inception
This week I implemented room styling in my game Lenna’s Inception. Room styling builds upon my previous work to procedurally generate dungeons with lock and key puzzles, Metazelda, to detail what is contained in each room down to the level of individual tiles.
Before I explain how room styling works, it’s useful to understand a few things about the dungeons in Lenna’s Inception, because these form constraints on the kind of things the room styler can do, and provide useful simplifications.
First of all, all rooms are the same size – 16x11 – and are aligned in a grid. Larger rooms can be faked by joining up four adjacent rooms with open edges. This is simplifies both Metazelda and the room styler, but there is no technical reason why variable-sized rooms cannot be done.
Each room has up to four exits – one in each compass direction. This again is a simplification, but it seems likely that I’ll eventually lift this limitation. Each exit can be one of these kinds:
A normal exit is simply a gap in the wall for the player to pass through to the next room.
A mob-locked exit is a door that does not open until the player has killed all enemy mobs in the room.
Red, green and blue locks can only be opened by the player after collecting the key of the corresponding color.
The generically-named equipment lock, which requires the player to have a particular piece of equipment to pass through the exit. For example, an equipment lock for the spring equipment, which enables the player to jump, might be a hole tile placed next to the exit so that the player has to jump over it. A lock for the sword might involve slashable bushes blocking the doorway.
An open edge indicates that the entire wall on that side of the room has been removed to combine the room with the next one into a larger room.
By the time the room styler runs, several things have already happened:
Metazelda has run and finished, providing the complete dungeon layout, including room positions, doorways between rooms and key and lock positions, etc.
Mob-locked doors’ positions have been decided.
Cycles of rooms linked with normal exits have been turned into a larger 2x2 screen room (these are the open edge exit kind).
The precise positioning of doors within the room (how far along the walls they are) has been decided.
These decisions cannot be undone, so the room styler must first check that a style is compatible with the decisions already made before applying it.
The room style interface
The room styles are organized into a set of classes, each of which provide the following:
A weight value, used to compute the probability of selecting this room style, relative to the other room styles’ weight values. By default, the weight is 1.0.
A method to check that the preconditions for the room style are met. If the conditions are not satisfied, the room style is excluded from consideration.
A method to apply the styling to the room.
Plain and desert styles
The plain and desert styles are the simplest room styles. They have no preconditions and so can always be used when others cannot. They each have weight 0.5.
The plain style will most frequently make no changes to the default contents of the room, but with a probability of 0.2 it will line the walls with alternative floor tiles.
The desert room style replaces default floor tiles in the room with alternative floor tiles and places up to three randomly-positioned bushes in the room.
Vertical and horizontal divider styles
Vertical and horizontal dividers split the room into two using a column or a row of blocks, with a gap at a random point along it. This produces an interesting effect on combat, as well as aesthetics. The player’s sword can reach over the blocks to attack enemies on the other side, and while passing through the gap the player is less able to escape an attack. This is interesting in rooms with skeletons, which do not approach the player and so cannot be hit from the other side of the divider. Skeletons fire arrows from a distance so the player must carefully time their approach through the gap to avoid being damaged.
The preconditions for the divider styles are:
No open edges.
There are exits on the east and west sides of the room for a vertical divider.
There are exits on the north and south sides of the room for a horizontal divider.
No exits would be blocked or covered up by a divider going through the center of the room.
Block arrangement style
The block arrangement styler produces symmetrical arrangements of blocks in the middle of the room. This can provide cover and/or obstacles during combat.
The way this works is by splitting the room into four quarters, producing a random arrangement of blocks in one quarter, and then reflecting it for the other three. To ensure it remains possible to reach all exits from the room, the original pattern that is reflected is either 4x3 or 5x2 in size. This means there is always either a vertical or horizontal path through the blocks, and there is a 2-tile gap between the blocks and the walls.
The preconditions for it at the moment are that there are no open edges, but this is not strictly necessary.
The cross-divider style splits the room into four compartments, in a similar style to the vertical and horizontal dividers, with three gaps to make each of them reachable. In addition to that, there is a 3x1 space in each compartment that is randomly filled with pots, blocks, bushes and the default floor tile.
The preconditions check that there are no open edges, and that placing the blocks at the edges of the room will not cover up any exits.
The bridge style is the most advanced room style that Lenna’s Inception currently has. This style generates a bridge over a bottomless pit that takes up most of the room.
The preconditions are that there are no open edges. The weight is normally 1.0, but goes up to 4.0 if there is an equipment-locked exit in the room and the dungeon’s equipment is the spring.
There are many ways to produce a bridge that links up all of the exits, but the way I chose to do it was with a cellular automaton.
The state of the automaton is a 10x5 grid of cells representing the the state of the middle of the room – zero in a cell means a hole tile goes there, and non-zero represents a floor tile. Initially all cells are zero.
Each exit from the room is assigned a unique power of two.
For each exit, the cell nearest that exit is filled with the exit’s number.
The bitwise-or of all exit numbers is the exit mask.
At this point the room looks as follows. The white box is the boundary of the grid, and the cell’s value is written over the tile. The southern exit is equipment-locked, and the equipment is the spring, which is why there is a hole between the tile inside the grid and the exit from the room.
Then the state is evolved as follows until every non-zero cell of the grid has a value equal to the exit mask:
For each cell X in the grid with non-zero value V: Choose one random compass direction, D If the cell Y in direction D from X is within the grid and does not have value V: If cell Y's value U != 0: Update every cell in the grid with either value U or value V to have value U|V Else: Set cell Y's value to V
This has the effect that the initial sets of floor tiles grow outwards organically until they meet each other:
Grids for rooms with only one exit can be prevented from terminating too soon by adding another power of two to the exit mask and one random cell in the grid.
These are all the room styles I’ve implemented, but they are still extremely primitive. The design of the room styler is flexible and extensible, so more styles can easily be added in future. Some examples might be rooms containing jumping puzzles, mazes, or block-pushing puzzles such as Sokoban.
blog comments powered by Disqus