Table of Contents

Custom Content:
https://lab46.g7n.org/~aholmes9/

Sus Avoid - An NES Development Adventure

Words to Those Beginning

New things tend always toward intimidation.
Incremental progress is always possible, and always beneficial.
Experimental projects need not impress, so long as they foster a sound mechanical understanding.

Point is, the NES is a limited system, 6502 assembly looks weird (especially if you don't know a form of assembly already), and the available tutorials aren't exactly exhaustive. That said, experimenting and discussing findings and problems with peers will lead you to success in this project, should you accept the challenge. There is no need to feel intimidation - only wonder for what new understanding can be had.

Making

When beginning my NES game project, I really wasn't sure what to make, so I made something. Just, anything, really. And what better a subject for my creation than two of the great scourges of the modern world - NFTs and Among Us. I present to you, Sus Avoid.

As I looked at the initial project page for the NES game, my thoughts were of how difficult it might be to get something running properly on the NES, considering I would have to emulate it and write assembly for it. In those two areas lie a world of little details that simultaneously greatly limit the scope of what you need to understand to make an NES game as well as force you into a world of thinking that strays far from common abstract thought and into one much closer to home for a computer.

Thankfully, the project page details the two major tools which you'll need to get started: a link to a tutorial by NerdyNights which serves as an excellent starting point as it both explains the fundementals of making an NES game, and provides whatever magic beans are necessary for getting things working: FCEUX (development-friendly emulator), and NESASM3 (assembler that aligns with the tutorial). Personally, I also recommend something like YY-CHR for designing sprites. Other than that, all you really need is a text editor, or an IDE of choice where you can write assembly.

As in any project, figuring out how to effectively organize your code is and was a high priority for me, and NerdyNights had the same idea - store the state of your game in a variable somewhere, and read it every frame so the program knows which rendering routines to go through. That's where I find the idea of a “stateful” program useful:

By storing a number corresponding to the currently state of the program in `gamestate` and reading it, the entire program can be easily broken up into several subsections corresponding to each one. There is more to unpack here, though: what's going on with those weird “Not” branches? Well, I'm glad I asked.

Given that the 6502 used by the NES is an 8-bit processor, it is always faster to work in terms of a single byte than to work with several at a time. As branching is such a common instruction, the 6502 only accepts an 8-bit address. Since the entire program is addressable only with a 16-bit address, the 8-bit address is used more like an offset than an address - you can either move 2^7-1 (127) units up, or 127 units down (the first bit is the sign of the number). Since the segments of the program are further away from that, something has to change in order for the program to be able to decide “here” and get “there,” and that's where anti-branching and JMP come in. JMP can accept a full 16-bit address, but does not discriminate on any condition - that means it's a less favorable operation, but it can move anywhere in the program. Given that, if branching is used to go around the JMP in the opposite case, then the same deciding effect can be had but with reach the extends to the entire program.

Since my game relies on randomness in order to spawn the nightmare-fueling enemies, I also had myself another hurdle - how can you produce randomness without being handed randomness from somewhere else? Because of the nature of the NES and assembly programming, I had to figure out how to make my own pseudo-random number generator without any help from a library. Thankfully, low-level pseudo-random number generators are well-documented, and it's not terribly difficult to take heavy inspiration from someone who already knows what direction to go and adapt it for your purposes. Mine ended up looking a little something like this:

While this does successfully update the random numbers as I intended, there's another problem: the seed. By the nature of pseudo-randomness, the process of updating the random number has to start somewhere, and that somewhere has to be hard-coded since there is no way to accept any sort of environmental noise input from the outside world - the NES simply does not have the hardware capabilities for that. There is, however, still one source of semi-random noise - an entity so illogical, and so integral to the idea of a game, that it was within me the entire time - the player. By taking the player's inputs as instructions to manipulate the random numbers, every time they play, they'll move at least slightly differently, (sparing those with the intent to manipulate the game's code) and that means each game will have a different sequence of random numbers. And so: randomness was solved.

For my particular game, two more challenges remained: one of hardware limitation, and one of conceptual barriers. The first, that of hardware limitation, was hiding in plain sight the entire time, and only reared its head as I put the finishing “polishing” touches on the title and end screens. The NES can only render 8 sprites per scan line. Since my game did not generally need to do this, I was unaware of this limitation for quite some time, but it is critical to understand so that developers don't tear apart their code thinking they have introduced some horrible bug when it's really just a limitation of the NES that needs to be considered.

With that out of the way, there was only one, seemingly simple, thing left to do: show the player's score.

It sounds simple on the surface - just put the sprites on the screen that correspond to the score number (in my case, frame count since the game's start) that's stored in memory. Well, there's one big problem with that - the average person (and even experienced computer scientists aren't really that fast at it either) doesn't read binary. In order to get past this issue, a few things needed to be put in place: sprites corresponding to each decimal digit, a manner of mapping the numbers 0-9 to sprites, and some method of converting a binary value into a series of values, 0-9, to be rendered on screen. After some research, NerdyNights came to the rescue again, but only partially, as this had to be adapted:

Sparing the details covered in the comments, the frame count is segmented into a few pieces, each of which is considered in succession to incrementally determine the decimal digit which denotes each decimal magnitude (place).

This is nowhere near an exhaustive list of the issues I encountered (more thoughts can be found in my journal, should it remain available), but it does cover some of the most daunting issues I found myself before.

I came into this as anyone else does - clueless, and wondering how those competent got to where they are.

The answer is simple: trying.

Retrospective

If you wish to hear some additional retrospective ramblings of mine, they can be found here:

10 minutes of silence interrupted occasionally by nonstop rambling