Building the dungeon, part IISeptember 9, 2011|
Juho already showed how the dungeon tiles are modeled so I thought it was time to give a quick glance at actual level creation process. This is also a very preliminary tutorial for making mods – but please note that details may still change and modding support will not make it to the initial release. But let’s get started!
It might be shocking news to you but we don’t actually have a level editor at all. All our levels are laid out in text editor because that was the fastest way to get started. The initial plan was to first do a few test levels with text files and then code a proper graphical level editor. Well time passed and we actually learned to like the text based system a lot. It actually has some really nice properties: first, because we are using a full blown programming language (Lua) we can use all kinds of nice programming techniques like create functions (i.e. pieces of script code) that help in repetitive tasks or even automate some aspect of level creation like place items procedurally in the level. We can also script all kinds of wacky features into level files without having to wait for our overworked programmer (yea, that would be me!) to add new features to the level editor. Another benefit is that we can easily search for items in the levels simply by doing Find in Files in text editor.
Now to make this process more practical we have implemented some tricks into our pipeline. The most important one is the feature to hot load assets into game while it’s running. So the typical iteration loop is something like this: I run around in the dungeon and want to add a side room to a piece of level, I Alt-Tab into text editor with the level file open, do the changes, save the file and *pop!* the changes get instantly refreshed in the game. I don’t have to load the level or play the game and walk the party to the right place so it saves a huge chunk of time. We can also hot load other type of assets like textures, sound effects, particle effects, material descriptions, and almost any type of asset supported by our pipeline.
Here’s a snippet of a level to get some idea how the code looks like:
-- tiny test dungeon
-- lines starting with two consecutive hyphens are comments or
-- notes that have no effect
<..##### ##.##### ##.##..# #.....## #.o.#### #...#### ######## ]]) setWallSet("dungeon") -- set party's starting location in the dungeon startingLocation(6,3,3) -- let's spawn some entities! spawn("portcullis", 2,3,2) spawn("alcove", 1,5,3):addItem(spawn("leather_boots")) spawn("torch_holder", 2,6,2):addItem(spawn("torch"))
Ok, let's walk through this piece of code.
First we have the map description which tells how the walls and corridors are laid out. '#' in the map represents a solid wall, '.' is empty space where the player can walk, '<' is stairway down, 'o' is a pit and so on. In this case we have a rather small 8x8 level. Next we tell the game to use a wall set called "dungeon". By changing this single line we can alter the look of the whole level drastically with just a few keystrokes. Next we set up the starting location for the party. All coordinates in level files follow the same format: (x,y,facing) where x,y are just the 2D map coordinates with 0,0 meaning the upper-left corner on the symbolic map. Facing is a number in range 0-3 which in this case tells the compass direction the party is looking at: 0 = north, 1 = east, 2 = south, 3 = west. So starting location for this small dungeon is at (6,3) and the party is facing west. At the next line we create, or "spawn" as we call the process, a new level entity, in this case a portcullis. "Spawn" is technically a function call with four parameters: first the function receives the name of the entity to spawn ("portcullis"), and again map coordinates in the usual format: (x,y,facing). In this case a portcullis is spawned on the south edge of a map cell at coordinates (2,3). It's important to realize that In Grimrock all doors are placed on the edge between two adjacent cells. So we could as well place the portcullis in (2,3,2) or (2,4,0), meaning south side of cell 2,3 or north side of cell 2,4, and it would be the same thing. Ok? Next the code spawns a wall alcove and its location is set the usual way by setting coordinates and facing on the map. The alcove in this case is spawned to the west wall (facing 3) of cell (1,5). The purpose of alcoves in the game is to hold items so the example also shows how to spawn an item. The syntax may look a bit cryptic at first but it's very logical and flexible really. Basically if we break the line into parts, it just says: spawn an alcove, spawn an entity called "leather_boots" and place it on the alcove. By now you should be able to see what the last line does. Yep, you got that right, it spawns a torch holder at (2,6,2), spawns a torch and inserts it into the holder. A torch holder is a special kind of wall alcove but it only accepts a single torch to be placed into it. Ok, that's it for now! In a later blog post I could tell more about how to actually create triggers and functions which are the basic building blocks of puzzles and traps.