My lizard is the Lizard of Programming
I've been trying to think of a way to show the programming side of development. Graphical things are easy to explain visually, but source code is a bit harder to tell you about.
I could show you pages of code that I've written, but I don't feel like there'd be a lot of value in that. Even if you're someone who already understands the programming "language" being used, most of what is written in code is using a vocabulary that belongs only to that project. It's hard to show you a meaningful piece of code out of the context of the whole program.
This is part of why programming is such a powerful tool. You make your own definitions. Among other things, programs are made up of reusable pieces of code, sometimes called subroutines or functions. Once you define something, you can reuse it as much as you want. An artist might paint a wall by painting 1000 bricks one by one, but a programmer would write one "brick" function and then just run that same code 1000 times.
You build a computer program up from components like this. Small subroutines do simple things, and then you write higher level subroutines that mostly just delegate tasks to other smaller ones, and you can keep stacking subroutine inside subroutine like nesting dolls, as far as you need to. (A subroutine can even be used inside itself, if the situation calls for it.)
This is also the great curse of programming. Because so much code is connected to and dependent on other code, making a small change can easily affect the entire system. Small errors may have catastrophic consequences, and symptoms can be separated from their cause by many layers of code. For many reasons like this, programming is unpredictable and volatile work. I think it's the one part of game development that carries the greatest risk of failure.
I'm not complaining, though. Programming work is tricky, but it's doable, and more importantly it's necessary. I'm just trying to explain how it's different from the other tasks I have to do. I'd like to show you two examples of what the NES code for Lizard looks like. First a small sample of code, showing what the low-level detail looks like, then a whole boss outlined at a much higher level.
By the way, I'm still planning to make Lizard open source some time after release, so if any of you are interested in seeing it all, eventually you will be able to.
A Low-Level Example
Here's the code that makes a "coin" work. I'm going to attempt to explain what it means line by line, but don't feel bad if it doesn't hold your interest. This is kind of a raw information dump, and source code is gnarly stuff that I wouldn't blame anyone for not wanting to tangle with.
It's written in assembly language, which is the most primitive form of programming language. If you know this particular dialect you should be able to understand the grammar, and the basic machine instructions (3 letter words in dark blue), but most of what is going on in even a tiny little fragment like this is made of words and terms unique to this game. A programmer can usually make an educated guess about their meaning, but when that fails you can always go looking for the definition (it will be hiding in the code, somewhere).
We're looking at a subroutine called "dog_tick_coin". Right off the bat there's a bunch of prerequisite knowledge about what this code is for. The term "dog" is a name I picked for all the interactive objects in my game. There can be 16 dogs active in any room. Every dog has a "tick" subroutine that gets run once every frame. Whenever a tick routine is called, the "Y" register will contain the index (a number from 0 to 15) of the dog it belongs to.
On the first line of the routine, we see "lda dgd_COIN_ON, Y". The instruction "lda" reads a value from memory, and the rest of the line tells it where in memory to look. The name "dgd_COIN_ON" is another of mine; each dog has a few bytes of RAM it can use to remember what it's doing between ticks. I picked a naming convention with the prefix "dgd", which I think was supposed to mean "dog data" but I've long stopped thinking of it as anything but "dgd". The Y suffix uses the index of the dog. It lets me put each dog's information into its own slot in memory.
The next line ("bne") makes a decision (or "branch"): if the coin was "on", it skips the next line, but otherwise it exits the subroutine ("rts") without doing anything else. So, the function of these first few lines of code is just to allow the coin to be turned off once it's collected.
Next is "DOG_BOUND 3,0,4,7". This is a "macro" that calculates a hitbox around the coin's location; the four numbers specify the corners of the box. It then calls another subroutine ("jsr") called "lizard_overlap", which checks if that hitbox overlaps with the player. If it does not, we skip the next few lines with another branch ("beq"). You'll notice the skipped code is indented; I think most assembly language programmers do not indent their code like this, but it's common in other languages and I find it a useful visual aid.
So, if the player did overlap with the coin's hitbox, there's a sequence of steps here. First we load the number 0 ("lda #0") then we store that number into dgd_COIN_ON to turn the coin "off" ("sta dgd_COIN_ON, Y"). Next we find out the coin's number (every coin has a unique number to identify and keep track of it), which was stored in memory at a place named "dog_param", and with this number in hand, we call the subroutine "coin_take". This routine will record that this particular coin has been taken. Finally a sound is played with another macro ("PLAY_SOUND SOUND_COIN"). There's a few more instructions here: "tya, pha" and "pla, tay". These temporarily remember and restore the dog index from the Y register, because the "coin_take" routine needs to reuse it for something else.
The last thing this routine does is adding 1 to an animation counter ("inc dgd_COIN_ANIM, X"). The value in this counter is used to animate the spinning of the coin. The "tya, tax" just before this is a little bit curious; the NES has two registers for indexing, called X and Y, and there are a number of instructions that can use X or Y but not both. In this case the increment instruction ("inc") only works with X, so I have to transfer the index from Y to X. Even worse, there's no instruction to do that directly, either, so I have to transfer from Y to another register called A ("tya"), and from there again to X ("tax"). There's a lot of this problem in NES assembly, where it feels like there should be an instruction that does something, and there really isn't, but you can usually make do with the ones you have.
Finally the subroutine ends and returns to the code that called it ("rts"). In this case it goes back to a loop that updates all 16 dogs, and when that's finished it goes back to a sequence of things that happens once per frame: read the gamepad, tick the player, tick the dogs, draw the player, draw the dogs, etc. When finished, it will repeat the process for the next frame, and eventually calling this tick again for that frame.
This was a real example from my game's source code, with no attempt to clean it up for presentation. My aim here isn't to show you perfect code, I just wanted to give you a small idea of what this stuff actually looks like. This particular routine is fairly typical, and I think it's a little bit ugly, but it works properly and it's efficient enough. I'm sure I could make it shorter or faster or just more elegant if I worked on it some more, but I won't.
So that was what the relatively simple task of updating a coin object looks like in Lizard's code. Next I will show you something bigger...
Anatomy of an Octopus
If you played the alpha demo, you may have encountered the octopus boss. If you missed it, and would like to try it, get the demo and enter the password JJKJN on the continue screen and swim straight up. Sorry about that pesky jellyfish in the way; this has changed since the demo. All the bosses are now directly next to a save point.
So what's she made of? Well, there's about 1.5 kilobytes of machine code, and about 1 kilobyte of sprites and sprite tiles, and maybe a sound effect or two. Here's what all her sprites look like in the Lizard tool:
The machine code is generated by an "assembler" program that turns my assembly language source code into the raw stuff that the NES actually runs. This boss has about 16 kilobytes of text in her source code, or a little less than 800 lines, depending on how you want to measure it.
This image shows all that source code in four columns. I've numbered the start of various sections, just so you can see in a very rough visual way how much code each of these things takes as I describe them:
1.0. Memory: names a bunch of locations in memory that are going to hold the state of the octopus; they have a "dgd" prefix like I described in the low level example. There are 12 bytes named here, and they're going to store an animation counter, a "mode" state, fixed point velocity and position, keeping track of the egg she might be holding, and a few other things.
1.1. dog_init_bosstopus: every dog has an "init" subroutine that gets called when a new room is entered. In this case, it randomizes the time until the first time the boss moves by about half a second, just so that she won't be entirely predictable. I try to have some kind of random element in most of the dogs, so that you can't memorize a pattern or control its behaviour, but instead have to react to what it's doing.
1.2. dog_tick_bosstopus: once per frame the octopus will go through this list of tasks:
1.2.0. Touch: If the lizard touches the eggs along the bottom of the room, turn it to bones. I made the bottom of the room into a hazard to keep the player from being able to rest there during the fight.
1.2.1. Lizards: this part handles special reactions to different lizards you might be wearing.
1.2.2. Modes: this section is a collection of different "modes" the octopus might be in. Only one of these modes will be run in any given frame.
220.127.116.11. Pause mode: this waits for a moment, then transitions to another mode with a swimming action. You might be surprised that it's one of the biggest blocks of code, but it does a lot of different stuff depending on which mode it's supposed to transition to. It's kind of a central hub of behaviour. After the pause, she will do one of the four things:
18.104.22.168.0. Swim sideways and slightly upwards, gaining height.
22.214.171.124.1. Swim sideways, maintaining height.
126.96.36.199.2. Swim toward the left, and descend toward the eggs.
188.8.131.52.3. Pick a specific egg to grab and begin moving toward it.
184.108.40.206. Dive mode: this waits in place for a short "hint" period to let the player know she's about to dive. The length of this wait period is one of the main control factors I used to adjust the difficulty of this boss; with more hint time it's easier to avoid. Once the hint period expires, she rushes down to the bottom, hoping to hit the player. Once she touches the bottom, she returns to the pause mode to dispatch the next action (which will either swim upward, or toward the eggs, depending on what's already happened).
220.127.116.11. Throw egg mode: this has another hint period, very similar to the dive mode, then instead of diving at the player she throws the egg. The eggs are actually a different dog, which will be described further down. Once it's thrown she will return to pause mode to await the next action.
18.104.22.168. Sad mode: when the octopus destroys all her eggs, she becomes sad, and the boss fight ends. This opens the exit door, turns off the music, and handles a few others things related to beating a boss.
22.214.171.124. Get egg mode: this will move directly toward a particular egg (already chosen in the pause mode), grab it, then go back to pause.
126.96.36.199. Swim mode: in this mode, the octopus will be floating horizontally in a direction already set up by the pause mode. If the player is directly underneath it, there is a chance she will dive toward the player, or throw an egg at them. If not interrupted with a dive, after a short time she will return to the pause mode for a brief rest before swimming again.
1.2.3. Movement: some of the modes above will set up some conditions for motion, like a starting velocity, but actually moving the octopus happens in one central place. Here it uses fixed point math to apply the velocity, and then adjusts for gravity (accelerating downwards) and drag (slowing down horizontally). It also checks for collision, to keep her from going through the walls.
1.2.4. Egg: if the octopus is carrying an egg, this moves the egg dog to her current location.
1.2.5. Touch: if the octopus touches the lizard, it turns to bones.
1.3. dog_draw_bosstopus: Every dog has a "draw" subroutine to put sprites on the screen. It basically selects a sprite based on its current mode, velocity, or other state information, then calls a sprite drawing subroutine to put it on the screen.
2.0. Memory: two bytes to store a current mode, and an animation counter.
2.1. dog_init_bosstopus_egg: empty, just returns. There's nothing to set up, the eggs always start out doing nothing.
2.2. dog_tick_bosstopus_egg: executes the current mode, and turns the player to bones if it's touching them. There are three modes:
2.2.0. Wait mode: stays in its current position until told to do something else by the octopus.
2.2.1. Dive mode: falls toward the player.
2.2.2. Break mode: animates breaking, then erases itself.
2.3. dog_draw_bosstopus_egg: normally just draws a plain egg, but if in the break mode it will choose a sprite that animates a little pop and breakdown.
So that's the whole boss, really. All of this code is what makes it happen. It went through several revisions on the way to this form. I start with some design goals, and ideas about how they will work, but it really changes a lot as you start implementing things and realize what's practical and what's not.
Space limitations are a significant factor here, too. I's a difficult task trying to get complex behaviour out of a reasonably small amount of code. On a modern platform I wouldn't really have to worry much about code size because it tends to be insignificant compared to graphics data and other things, but on the NES it has a big impact.
Between the constraints of design, space, graphical limitations, and available computing power, it's been a tremendous challenge trying to make satisfying boss encounters on the NES! I hope you'll enjoy them. I also hope this article has given you a little bit of an idea of what it's like to program an NES game.
So, what have I been doing? Just working on bosses. That's all that's left. Half of them are complete at this point, and the other half are in various stages of completion. I'll try to talk more about them in a later update.
As always, I'm hesitant to give out a timeline for when it's going to be finished. I hope not too much longer, but all I can do is keep working on it every day. I promise it will be done! I really can't wait until all of you get to play it.