Making Procedural Content Look GREAT
I'm going to talk about how to get from images like these:
using what I call a Decoration Generator. The awesome tileset shown above was created by Oryx Design Lab and is fantastic, I highly recommend checking it out.
Note: I love ASCII games and this article isn't meant to imply that tile-based games are better than ASCII games. Not at all! Instead this article simply details a method to make tile-based games look great.
A Decoration Generator is a generator that takes a level as input, and outputs a good looking version of it. Other procedural content generators do level layout, loot and monster placement, etc. first. After that has been done that output is sent to the Decoration Generator which figures out the cool looking details such as wall corners, alternate tiles, shadows, decorative blood splashes, etc.
Decoration is single stage, multi pass
Decoration is single stage. This means you go through the stages of content generation (determine theme and difficulty, place rooms, place monsters, place items, etc.) before decorating, then do the decoration all at once.
Decoration is generally multi pass. This means the Decoration Generator alters the dungeon in several passes applying a Decoration Algorithm during each pass.
Examples of Decoration Algorithms include placing wall corners, replacing tiles with random alternatives, and placing cosmetic effects.
I personally find implementing each pass individually makes for clean code and gives you the flexibility of turning some passes on and off and running others multiple times as desired.
This algorithm is very simple. Each tile has several alternative tiles it could be replaced with. Iterate over every tile and for each one that has alternatives, randomly decide if it should be swapped out with an alternative tile and if so which one. The cracks in the floor in the dungeon room shown above were placed with this method.
This algorithm was used on the walls and water to take care of corners and edges. For these tiles you check a tile's neighbers and you select the final tile based on which of the neighbors are of the same tile type.
This algorithm places shadows on tiles.
Essentially every tile type has a true/false state for each of the two questions:
- Can this tile cast a shadow?
- Can this tile receive a shadow?
For every tile if it can receive a shadow, and the tile above it can cast a shadow, it's time to place a shadow.
These are small algorithms that scan for certain conditions and then do a small modification if those conditions are met.
For example replacing ground with bridges, placing spiderwebs in corners, placing blood spatters and bones, etc. would qualify.
There are simple algorithms that should be used only when appropriate - for example an algorithm to place spider webs is great in a crypt but terrible in a king's hall (unless the king is undead of course!)
A couple examples of keyhole modifications include:
If there is a ground tile with another ground above and beneath, but water to the left and right, make the ground tile a north-south bridge.
If there is a ground tile with another ground to the left and right, but water above and below, make the ground tile an east-west bridge.
If there is a ground tile with wall tiles above and to the right, place a top right corner spiderweb.
If there is a ground tile with wall tiles above and to the left, place a top left corner spiderweb.
Pick a random ground cell.
Place a random blood splatter on that cell.
Place a random number of blood splatters in nerby cells (e.g. up to 1-3 splatter tiles, each 1-2 cells away).
Find any large scary monsters.
Place several (e.g. 2-5) bones tiles near the monster (e.g. 1-4 tiles away).
Statues (and other stand-alone walls)
Find a wall that is surrounded by ground on all four sides.
Replace the wall with a tile that look cool by itself.
Examples of good stand-alone walls would be statues, pillars, wells, coffins, altars, tables, tombstones, braziers, thrones, computer consoles, etc.
There are many ways of represented decorated cells as data.
Two of my favorites are:
Lots of tiles method: You have a
wall2_eastwest_damagedtile, etc. This is a fine approach. In this case the majority of your tiles will never be generated by you level creation code, instead level layout places only
walltiles and the decorator then replaces them with
wall_northwest, etc. tiles.
Tile type and tile image method: Cells in your world have both a tile type and a tile image, but the mapping from types to images is one to many. Thus the type is
wallbut the image could be
wall_grey_damaged_south.png. In this case your world generation places walls but leaves the images alone, and it is the responsibility of the Decoration Generator to assign out all the images.
One fun thing to do with Decoration Generators is to create Decoration Themes. A Decoration Theme is essentially a set of Decoraton Algorithms and tiles that look good when used together.
For example a crypt theme would specify which walls are appropreate for a crypt, it would place coffins instead of statues and it would place lots of broken ground tiles, bones and spiderwebs.
There are lots of things you can do here, but my one main piece of advice is to keep the decoration code as separate as possible from the layout / generation code. This keeps the entire procedural content code base cleaner and easier to work with.