The game that would eventually become Get It! began its life as an assembly programming project for a computer architecture class. The following page consists of the progress reports and presentations that I was required to produce over the course of the semester. While this is by no means an exhaustive discussion of 8 bit programming techniques, it is my hope that some of this information may prove useful to other fledgling game designers and programmers of the NES. Because I didn't complete the game by the end of the semester, there are some major gaps in what this report covers (most importantly the music engine). There are also some differences between some of the solutions I found early on and how I ultimately engineered the final game. (And if you're curious, I got an A)
I am in the process of learning assembly language for the Nintendo Entertainment System (NES) and programming a video game for that system. As I am nearing graduation with my CIT degree, I feel that it is important for me to get as much experience programming different types of computer systems as possible. I have been planning to learn assembly programming for quite some time, and this project seemed like the perfect opportunity to do so. There are several reasons why I chose to program the NES. First, it is a fairly simple system with an 8 bit CPU and 16 bit address bus. Learning to program for the NES is a good way to gain experience with assembly programming in a limited environment that doesnít involve some of the complexities associated with assembly programming a modern PC. The NES CPU is a variant of the MOS 6502 CPU which was used in many older personal computers and video game systems. The ĎNES on a Chipí, a single microchip which contains all of the NESís components, is used in many small toys and electronic devices even today. Learning to program for this architecture is a valuable skill in the consumer electronics marketplace. As a side note, I grew up playing the NES, and have always wanted to learn how to program it, and this is my opportunity!
I decided that the best approach to programming a game for the NES would be to start learning the basics of the system, and allow my ideas for the game to arise organically out of what I learn to do and when I learn to do it. It didnít make sense for me to design a game ahead of time, only to find later that I couldnít actually program it in time. I started by creating a character that I could move around on the screen with the controller. This character is just a simple square with eyes. From there, it made the most sense to design a game in which this playable character would have to avoid enemies and collect items. I designed a simple enemy character (a circle with eyes) which moved around the screen randomly for the player to avoid. In order to provide feedback to the player, I would have to design some kind of collision detection system. My first approach was to use each characterís center pixel for its coordinates on screen. Then I divided both the playerís and the enemyís horizontal and vertical coordinates by 16. This effectively divided the screen into a grid of 16 by 16 pixel squares. Then I checked for equality between the player and enemy coordinates on this grid. This system worked passably well, but not perfectly. Depending on where each character was located on the screen, it was possible for the player and the enemy to overlap substantially before a collision was detected. After studying some more, I learned how to use the Branch Carry Clear (BCC) and Branch Carry Set (BCS) instructions, which can be used in the same fashion as > and < would be used in Java and C#. This allowed me to create a bounding box around the enemy which would always detect a collision if the player entered it. I found this system to be much more satisfactory.
Another problem that I had to solve was how to generate random numbers. No computer is capable of generating a truly random sequence of numbers, so complicated algorithms are used to generate pseudo-random numbers. Finding a simple and effective method of generating seemingly random numbers on such a simple computer was challenging. The solution I arrived at was to have the program loop through and add together the first 256 memory addresses whenever it wasnít busy with some other task. This sum is saved in its own address. Because the contents of every memory address are constantly changing based on the conditions of the game, this sum would have the appearance of randomness. And it would produce different results every frame because it would always have added a different address right before it was used. Note: I eventually decided to use a different system to generate random numbers because this proved not to be random enough.
The period of time after the television finishes drawing one frame on the screen and before it begins drawing the next frame is called v-blank. Understanding v-blank is crucial to programming the NES because all updates to the Picture Processing Unit must be made at this time. Because of this, every program for the NES must be divided into two main sections: the main loop, and the non-maskable interrupt (NMI). The main loop contains the code that the CPU is running most of the time. The NMI occurs at the beginning of v-blank, and the code within this subroutine is executed at exactly this time, regardless of what line of code in the main loop is currently running. This means that any code in the main loop must be written so that it can be interrupted at any moment without the game crashing. I had some difficulty with this. Fortunately, because my game is short, I am able to fit most of the code inside the NMI, avoiding this problem almost entirely.
I decided that I want my game to allow for two players, and to have several enemies, one of which should always chase after the player closest to it. I had a very difficult time figuring out how to accomplish this. After a lot of work, I finally put together an algorithm where the enemy calculates the distance to both players using a simplified distance formula, and uses BCC and BCS instructions to determine what direction it should move in to chase after that character.
Next week is spring break, and I intend to make a lot of progress on my game. I need to work on perfecting the graphics, creating backgrounds, programming wall collision, adding items to be collected and any power-ups those items may give players, and also sound effects and music if I have time. Iím right on schedule based on the timeline I submitted in my proposal. I really want to make this a fully functioning and playable game when Iím done.
Resources:the NES has an extensive amateur development community. Almost everything that Iíve learned so far has come from the following sources:
March 30 Update
My game is starting to look like a real video game. Since my last progress report, Iíve added a background, collectible items, and refined several elements of both the gameplay and the program. Iíve also solved several problems that I encountered along the way. Many of those problems revolve around the quirks of programming for the NES in particular. This project is not just about learning 6502 assembly, but becoming intimately familiar with the nuances of the NES hardware.
NES backgrounds are rather complicated. The entire background is composed of 8 by 8 pixel tiles. One screen is 32 tiles in width and 30 tiles in height. In addition, there are several bytes of attribute data, which determine what color palette should be used for each background tile. Altogether, a single screen background takes up one kilobyte of memory. One entire background is stored in a nametable in the PPU. There is room in the PPU for two nametables. My game does not use scrolling, which greatly simplifies drawing backgrounds, but I learned how to manipulate nametables for scrolling anyway, just in case it became important to me later. Address $2005 controls how far the screen has scrolled in either direction. As the screen scrolls, the Ďcameraí pans over the two nametables that are stored in the PPU. The screen can scroll up to 256 pixels this way, but once the edge of the second nametable is reached, it canít go any further. Obviously, most games have more than two screens. Larger levels are created by switching the nametables once the screen has scrolled past 255, and then replacing the tiles in the next nametable while itís not visible. This creates the illusion that the level is more than two screens wide, but there is a catch. V-blank isnít long enough for the entire background to be replaced in one frameóit can only be replaced one column of tiles at a time. Itís complicated, but when done seamlessly, it works very well.
As I mentioned before, my game doesnít use scrolling, but I had another problem to solve. I had to figure out how to handle collisions between characters and walls. I researched how this is done in other video games, and I found a solution that works quite well.
Every moving object has Ďhot spotsí. These are pixels where wall collisions are detected. In my game, I use the center pixel of each side, so every object has four hot spots. Then I had to find a way to determine which background tile each hot spot is on during any frame. Since the background tiles are all stored in a nametable, I wrote a section of code that converts a hot spotís horizontal and vertical coordinates into the address of that background tile in the nametable (it took me a lot of time and effort to figure out how to do this. I had to divide both the vertical and horizontal coordinates of the hot spot by 8, then bit shift them in different directions by different amounts and store the result in two bytes, both a high byte and low byte which I could reference by indirect addressing). Then the tile at that address is checked. Tiles $59 and over are solid wall tiles, and cannot be passed through. If a hot spot is on a wall tile, then the objectís position is incremented or decremented in the opposite direction, forcing it out of the wall. I found that I had to do this check twice per frame, effectively pushing objects out of walls twice as fast as normal because it was possible for players to clip through walls with careful maneuvering.
I decided that it would be more interesting if the collectible items in the game were moving around the screen rather than staying stationary. The code to accomplish this was not complex or difficult to write, but it had a nice effect on gameplay. I wanted items to be able to appear anywhere on screen, so I used my random number generator to place items. This created another problem. Sometimes, an item would be randomly placed inside a wall, where it would not be accessible. I had to program it so that items which are placed inside of walls would have their coordinates incremented automatically until they Ďpopí out into the play area. I also had a problem with items being placed off the bottom of the screen. This was possible because the screen is only 240 pixels in height, but my random number generator was picking numbers as high as 255. An item placed off the bottom of the screen would also be inaccessible. I first tried to solve this by making the program pick a new random number if the first one was too high, but sometimes it got stuck in an infinite loop (I think my random number generator isnít quite as random as Iíd like it to be). After some trial and error, I decided to just decrement the itemís position until it appears back in the play area. This seemed like the best solution, although sometimes it takes several seconds for items to work their way out, they always seem to do so eventually.
I thought it would be a good idea for the players to begin each new life in a safe zone that enemies cannot enter. At first, I created the safe zone with a simple bounding box that, upon being entered by an enemy, that enemyís direction control would flip. I did this with exclusive or. If the enemy was moving up, it would switch to moving down, and if it was moving left, it would switch to right. I found that I just wasnít satisfied with this, because enemies tended to get trapped around the bounding box, shaking back and forth without moving. Next, I changed the safe zone so that it was made up with solid wall tiles rather than tiles that can be moved through. This way, enemies would interact with the safe zone the same way that they interact with solid walls. The players start in the safe zone, but once they leave it, they canít re-enter. This works because when the player begins a new life, all four of the playerís hot spots are in the safe zone. The game increments the playerís position in all directions at once, with a net effect of zero movement. When one hot spot leaves the safe zone however, the player is pushed out and must complete the level.
One of the problems with programming a game with a visible score is converting the score from binary or hexadecimal into the usual decimal numbers that players expect. I read about several solutions to this problem, and the one I decided to use is also probably the simplest. Each digit on the scoreboard is represented by a separate byte in memory. When that byte reaches $0A, it is reset to $00 and the next higher byte is incremented. Similarly, when a byte is decremented to $FF, it is reset to $09 and the next byte is decremented. This creates the illusion of base 10 numbers on screen. Itís also convenient, since the scoreboard is made up of a row of background tiles, and each tile is represented by a byte in the nametable anyway. And since I created my background tiles so that the first ten correspond to the digits 0 Ė 9, it works out very logically. The only problem is that the second playerís scoreboard simply reads ďPRESS START!Ē prior to beginning gameplay. The ĎAí in ĎSTARTí was triggering the rollover to the next digit, so I had to create a second ĎAí tile which was neutral in this regard.
In the game, the key item is what should take the player to the next level. At this point, though, I havenít programmed any items other than gems and hearts, which effect the scoreboard. When the key is implemented however, the level will increase by the following process. First, the palette for the background tiles will be updated by incrementing the values in the relevant palette. The NES palette is based on the HSL color model, so this will select a new group of colors that all share the same hue, but different lightness values, automatically creating a visually pleasing matching color scheme. Also, the screen will be redrawn using the next tile in the background character data. This functionality has not been implemented yet, but I plan to do it soon.
Although it has little impact on the way Iíve programmed my game, I learned how to use the NES stack. One good use of the stack is handling synchronization of the main program loop with the NMI that occurs during v-blank. Because the NMI can occur at any time during the main loop, it can drastically alter the contents of the CPU registers before returning to the main loop, altering the game state. By pushing the register contents into the stack at the beginning of the NMI, and popping them at the end, the current game state can be preserved.
Although the game is in a roughly playable state now, I donít think it is nearly finished. I still have to finish programming the scoreboard and implement different item collection routines (each item will effect players and enemies in different ways, and Iím still not sure exactly how I want to do this). I was unhappy with my first maze design, but I havenít designed something better yet. Iíd like to program the game so that the second player can join in at any time. I definitely want to program some simple sound effects, but I will probably not have time to write music for the game due to the complexity of NES sound programming. Regardless of what I manage to finish by the end of the semester, I fully intend to completely finish the game this summer and release it online for other people to play. I would like to eventually add a title screen (I could use the second nametable for this since I donít use it for the actual game) and real music. Although I canít submit it with this progress report, Iím willing to show my source code if necessary. I obviously donít expect you to read it line by line, but it represents a lot of effort on my part, and I think thatís important.
April 17 Update
I have programmed some rudimentary sound effects for the game. As I mentioned before, NES sound programming is rather complicated and time consuming, and I felt that it wasnít the most important part of making the game playable for the purposes of this particular project. The NES has five sound channels. Two produce square waves, and are generally used for melody and harmony parts in music as well as intense sound effects. The square wave channels also provide the greatest level of control, which makes them ideal for main sound programming. Then there is the triangle wave channel, which is generally used for bass lines and mellow sounds as well as muted sound effects. The triangle wave channel has fewer controls than the square wave channels, making it somewhat less versatile. Next there is a channel that produces white noise (a random waveform). The noise channel is generally used for drums in music and harsh sound effects such as explosions. It has two modes with 16 different sound types for each mode, giving it the ability to mimic a wide range of non-pitched musical instruments and sound effects. The fifth sound channel plays back low quality digital audio samples. This channel is usually used to playback extra percussion sounds and occasionally voice recordings. The frequency, amplitude, and other attributes of each sound channel are determined by values written to addresses starting at $4000.
In my game, I use the square wave channels to produce a basic beeping sound when the player collides with either an item or an enemy. I used different pitches to indicate what they player is colliding with. Initially, I had trouble figuring out how to make the sound play for the correct duration. At first I tried to time the sound by frame, but it seemed fairly difficult to get the sound to last the correct length of time. Then I discovered that each sound channel has a built in automatic length feature, which causes each sound to be played for a default length of time that I found was essentially what I wanted. This made programming sound much easier. By writing the correct values to the square wave channels during the program loops that control collision detection, I was able to produce the simple sound effects that I wanted. Programming background music for the game proved to be too difficult an undertaking for me to tackle in the given time constraints, but I definitely want to add some music after the semester is over and I have more time.
I have also programmed the feature that increments the palette value for each level when the key item is collected. One problem I encountered has to do with a peculiarity in the NES palette. The NES palette actually has several different values that produce the color black. In fact, the last two columns of the palette are all different shades of black. This means that for two levels in a row, when the background palette is on the E and F columns, the background becomes effectively invisible. In addition to this, color $D0 is technically outside of the NTSC television color standard and should not be used. My personal television doesnít have any problem displaying color $D0 (which shows up as a deep dark black), but apparently some televisions have difficulty displaying it correctly. Because of this, it is necessary to make my palette selection code skip over columns D, E, and F in the palette when moving from one level to another to prevent the game from displaying incorrectly.
Iíve decided that the best way to handle item collection is to create a status register for each player and enemy in the game, as well as for the game itself, in several memory addresses. This way, I can write different values to each register depending on the condition that player or enemy is in at any given time. For example, if a player collects an item that has an impact on the player (such as making the player invincible), a particular value would be saved to that playerís status register. Items that effect enemies (such as freezing them in place) would change the enemy status registers. In addition, each player and enemy also has a timer, which determines how long any particular status should last. Each timer is incremented each frame. It takes about 4.26 seconds for each timer to count all the way from 0 to 255, which I believe is enough time for any status to be in effect. While it is possible to use two 8 bit memory addresses and the carry bit to perform 16 bit calculations, I donít think it will be necessary for me to do that in my game. Because my game is so simple, very few pieces of information require more than 8 bits.
Overall, the game code is written with two players in mind, but there is currently no way to switch between a one player and two player game without changing the source code and reassembling the program file. I believe that the best way to handle a two player game would be to change the overall game status that I mentioned before. My goal is for the second player to be able to join in at any moment by pressing start on their controller. Currently, in order to assure correct enemy chasing behavior, the second player sprite is hidden underneath of the first player sprite during single player mode. This was the easiest way I could think of to solve the problem, but it is not ideal. Because only eight sprites can appear on any given scanline, and each object is two sprites wide, the second player character needlessly uses up two of them while hidden. This causes other objects on the screen to disappear momentarily under some circumstances. Ultimately, I decided that it didnít matter, since the movement of objects on screen is relatively quick, no eight sprites remain on the same scanline for very long, producing only momentary flickering, which is fairly normal in older video games.
Just out of curiosity, I spent some time experimenting with the boot up sequence. Most of the code for starting the system I initially copied directly from a tutorial program that I found online, but I decided that it was important to figure out what each command actually did. Essentially, the beginning of each NES game program must contain a header with information about mirroring and mapping, certain values must be written to the PPU to prepare it to handle graphics, and itís also a very good idea to clear the system memory. At first I felt that it was not a good idea to copy this code line by line, but I discovered that all of these things are important to a properly functioning game, and decided to leave them as I found them since virtually every game must contain this section of code to work right.
While I believe that the game is in a fairly playable state right now, and it has reached a level of completion that I feel is adequate for the purposes of this assignment, I still want to finish it up this summer and give it a serious release. It would be a shame not to share it after all of the work that Iíve put into it, and I donít think itís at all bad for a first attempt at this sort of thing. Working on this project has taught me a lot about assembly programming, and it has also given me a deeper understanding of how computers work on a fundamental level. Iíve learned skills and information that I donít think I would have unless I tackled something like this. It is especially relevant because the 6502 and its variants are still in use today in many consumer electronics and toys. Aside from the general skills of assembly programming, itís possible that I might end up programming a system that uses the same or a similar architecture someday as part of my job.
If you enjoyed this blog entry, then you might also enjoy The Ultimate Metroid Walkthrough.
© 2016 Peter J Hopkins