Hi all. My entry into the compo is called Lunar Limit. It's a 80s-style arcade shooter programmed for the NES.
With this game, I wanted to push the NES system further than seen before. I wanted to display as much shit as possible on the screen at once without slowdown and without flicker.
In the end I managed to accomplish this goal. Here's how:
Sprite Limitations
The NES can render up to 64 8x8 pixel sprites per frame. In Lunar Limit, about 40 of these sprites are used for the player and various effects, leaving only 24 sprites remaining for enemies and enemy bullets. In practice, this would equate to only 8 enemies and 16 enemy bullets on screen at once; not a very compelling number.
In addition to the 64 sprite limit, there's also an 8-sprite limit that applies per-scanline, where a scanline is simply a horizontal row of pixels stretching across the screen. Trying to render more than 8 sprites per-scanline is simply not possible. If you tell the NES to render more it will only display the first 8 you gave it.
The 8-sprite limit is what causes many old NES games to flicker. The flicker is caused by alternating the display order of sprites each frame such that sprites invisible one frame will become visible on the next.
Personally, I hate the look of this flicker effect, especially when seen on modern LCD displays where each frame turns into an epilepsy-inducing eye strain. Introducing flicker was out of the question for my game. An alternative had to be found.
Introducing Backgrounds
Along with sprites, the NES is also capable of rendering a full-screen of 8x8 tiles colloquially referred to as the background layer. The background layer, while fiddly to work with, is not restrictive when it comes to the amount of objects it can display.
Here's an idea: What if enemy bullets were rendered using the background layer?
This is the technique I ended up using, but it came with a whole slew of other challenges. Below, I'll explain the problems and how I solved them.
Grid Alignment
Backgrounds are made up of 8x8 tiles, and those tiles are aligned to a grid. This means that anything rendered to the background layer must be grid-aligned. Unfortunately, this results in movement which is just too jerky for rendering bullets.
The jerkiness can be reduced by shrinking the grid size, or rather, by faking a smaller grid size. Rather than rendering a bullet as a single 8x8 tile, it is possible to render it as one of four tiles representing a 4x4 tile:
In actuality, additional tiles will be needed to render multiple bullets on the same tile. The total number required is 2⁴ = 16.
You may be wondering why I stopped at 4x4. Why not use a 2x2 or even 1x1, for example? Well, the answer is memory. The NES has only enough memory to hold 256 tile graphics, and using a 2x2 grid would require 2¹⁶ tiles = 65536, which far exceeds the NES's capability. Don't even get me started on how much memory a 1x1 grid would require.
Bandwidth
A second problem with backgrounds is that they're slow to update. Like, really slow.
During each frame of an NES game, all graphical updates must occur in a tiny window of ~2200 CPU cycles called VBLANK. Of those 2200 cycles, 500 of them will be used to update sprite memory, leaving only 1700 cycles for updating the background. Now, given that it takes exactly 8 cycles to update 1 background tile, and that in total there are 960 tiles per screen, this means that updating the entire screen takes 960 * 8 = 7680 cycles, which is way more than the 1700 cycles available. Because of this, updating the whole background in a single frame is simply impossible.
Lots of games work around this by blanking the screen every time the background needs changing. Watch a playthrough of a NES game, such as Battlekid, to see what I mean. Every time the background changes the screen goes black for half a second while the memory gets updated. It's a necessity of the hardware; not a stylistic choice.
An alternative to blanking the screen is to update only part of the background each frame, such as one-fourth of it. 7680 cycles divided by 4 is 1920, which is more cycles than available, but because the amount of tiles being updated (960 / 4 = 240) is less than 256, an optimization can be made. The NES has this special region of memory called the zero page, which is the first 256 bytes of its memory. Accessing zero page memory is 1 cycle faster than accessing it elsewhere. If background data is stored in the zero page then updates will take 7 cycles per tile as opposed to 8. Calcuating again, 240*7 = 1680 cycles, which just barely fits into the 1700 cycles available.
To Be Continued
These blog posts have a character limit and so stay tuned for a second post.
Edit: Part 2