A deep dive into my NES toolchain
I havenāt posted much lately but Iāve been working steadily on the game. With most of the major engine systems in place Iāve been focusing on writing and building out levels.Ā
Months ago I did a full pass on the game in a graph paper notebook and blocked out all of the major areas for the facility. I highlighted key item locations, characters, major plot points, and more or less designed the structure of the game.Ā
Now I am spending most of my time actually building the rooms I designed months ago. Art, story, and gameplay are all starting to come together. To that end,Ā I now have to be very specific with regards to what characters appear in each room, how the rooms connect, what the dialog is for characters and item descriptions, and so on.
The toolchain I have in place for building levels is really complicated. It requires me to use a mix of Windows and Mac tools on different physical machines. I have one tool for creating the art, another for building level graphics, another (custom) editor for building level collision and entity placement. Then, all of that gets complied into the game code which has to be updated with dialog and any custom code required to make the level do stuff. Itās a bad environment for creative work as making small changes can require a whole round trip through the pipeline.
So, to help the situation, Iāve been using Twine as the main tool for writing the game:
I can treat each in-game room as a passage in a twine story. Passages have connections which match the room connections in the game. I arrange the passages in the Twine editor in a way that mirrors the physical layout of the in-game rooms.Ā
This letās me write some dialog then run through a sequence of rooms and see if the dialog flows properly. What happens if a player skips a room or visits characters in a different order? Does the dialog still make sense? Does the tone fit?
I can write and edit and delete quickly without fear of messing up a bunch of work. Then, when Iām happy with how things feel I can start doing the levelĀ layout knowing I shouldnāt need to make many big changes.
Level builds typically start in Aseprite and are really fluid. At this stage things are still really flexible.Ā
This is the initial concept for the Shower level:
I need to make sure that for any given room the choices I make here need to:
Be renderable with a maximum of 13 colors
Use only tiles from one of my tilesets
Any new tiles added here have to fit within the available space of my chosen tileset
When I have something here that is starting to work I usually try to get it in game as soon as possible. Palette colors on the NES can be wonky and inconsistent depending on what is doing the rendering so a choice that looks great using Asepriteās NES palette might look meh in Fceux and eye-scorching in Nintendulator or real hardware. Itās common to make a lot of round trips at this stage to find a good balance without just using the same palettes everywhere.
Laying out NES background data
The next step is to rebuild the level in NES Screen Tool using tiles from the tileset. This tool generates data which the NES uses to load background graphics which make up the level you walk around in.
Hereās what the Shower level above looks like when built out in NES Screen Tool with some palette adjustments:
You can see the tileset used to build this level on the right. That is the holding_cells tileset. It still has a lot of room for cool new art.
At this stage I will usually do a lot of tweaking to the position of various items in the level. I may add or remove props or inspiration might strike and Iāll create all new sprite art to include in the level.
I will also try lots of palette variations here as itās very fast to do.
When the level layout is more or less complete, I export a bunch of things:
A .map file for the level which I use when making edits
The background data for the level as a C header file
The updated CHR data for the tileset
The palette for the level as a C header file
A bitmap file of the tileset in case I want to edit it in Aseprite
A bitmap file of the level layout to use as a reference in my custom map editor
(If I could make only one change to my current process it would be to make the above steps scriptable.)
Next I add the level toĀ my SCP project in my custom map editor. I import the bitmap I generated previously and use that as a guide to mark up the level with collision information, NPCs, and triggers for dialog and game events:
That data gets exported into the game as C code which looks like:
The big block at the top defines the type of every tile in the level (e.g. walkable, non-walkable, trigger, exit, etc.).Ā
Some tiles (up to 16) can also have 8 bits of extra metadata. This is used so exits can define where they link to and so triggers can indicate what procedure should run when they are activated.
The spawns define the type and location of entities and pickups in each level.
Finally the level state is 8 bits of persistent data which is kept in memory for every level in the game. The level editor uses this to mark which doors are closed and which are opened but game code can use those bits to keep track of pretty much anything. Those bits can be read even if the player is in a completely different level. Simply put, this allows a switch in one level to open a door in another level.
With just the above done I can load the level in game, walk around, and check the art.
To get the dialog and triggers working I need to add two files. The first is the dialog definition for the level:
The dialog is all hand-written and is based on the dialog in the Twine passage for the level. Here is where I make sure that each line actually fits in the dialog box. I also add markers for quotes (\x26), line breaks (\x01), and page breaks (\x02).
This is where Twine is such a life-saver. Making changes to dialog in C code with all of the control characters is a giant pain in the ass.
The second file I add contains the scripting for the level:
The details of this are destined for another overly-long technical post. For now, the important thing is this is where we hook up the dialog lines from the previous file to the triggers we defined in the map editor.
This all has to be done by hand because dialog can change based on the game state and activating a dialog will, in some cases, trigger actions to happen in the world. This is the code that ties all of that together.
When you put all of that together you get a finished game level:
It can take several days to go through all of the writing, design, and art for a small simple level like this. Then, the technical buildout and revisions typically take one or two evenings of head-down work.
It is a lot of effort but Iām pleased with the results so far.
If you read all this, wow, thanks for sticking it out. Iām happy to answer any questions you might have.