About

AfterZhev is a small RPG that runs on a single 16 MHz ATmega2560 microcontroller, plus some passive components, and is compatible with the Arduino Mega 2560. Perhaps unwisely, it was written entirely in AVR assembly.

AfterZhev tells a tale of betrayal, the story of a brave messenger risking everything to recover a stolen letter. Along the way, you encounter bandits, foxes, and a secret cult. You can complete the game in less than four minutes, but first-time players will probably need an hour or so.

The game is sort of intended to be played on a physical microcontroller along with a VGA monitor and a NES controller, but you can also play online here!

Details

The ATmega2560 microcontroller is a pretty powerful 8-bit chip, but still pretty restricted. It runs at 16 MHz, has 8 KB of SRAM, 256 KB of program memory, and 4 KB of EEPROM. Of this, AfterZhev uses all but three bytes of SRAM (mostly for the 120x66 framebuffer), 200 KB of program memory, and about 84 bytes of EEPROM (for a saved game). In addition, around half of CPU time is spent generating a 60 Hz 640x480 VGA signal. All game logic and rendering is performed during the vertical blanking interval plus a lower portion of the image interval, and as a result, AfterZhev is tightly linked to a 60 FPS framerate.

AfterZhev is written entirely in AVR 8-bit assembly. I started writing the game in C, but quickly ran into difficulties. The entire game runs within an interrupt service routine (questionable, sure), which meant that the standard ISR prologue and epilogue had to account for nearly every register, wasting precious memory and cycles. However, marking the ISR as NAKED_ISR resulted in frequent crashes when optimizations were enabled. Since avr-gcc generates very suboptimal code without optimizations, I turned in frustration to assembly.

Using assembly meant that I could optimize everything, and carefully allocate registers and memory. On the other hand, it also mean that I had to carefully allocate registers and memory. For example, here’s a subroutine that renders a string displaying an item’s effect on a stat (e.g. strength, dexterity, etc):


; Render an item stat boost with color.
;
; Register Usage
;   r20-24          calculations
;   r25             stat boost value (param)
;   X (r26:r27)     working framebuffer pointer
;   Y (r28:r29)     framebuffer pointer (param)
;   Z (r30:r31)     stat abbreviation pointer (param)
render_item_stat:
    push r25
    ldi r21, 6
    clr r23
    call puts
    subi XL, low(4*FONT_DISPLAY_WIDTH+FONT_DISPLAY_WIDTH/3) ; puts changes X
    sbci XH, high(4*FONT_DISPLAY_WIDTH+FONT_DISPLAY_WIDTH/3)
    pop r21
    ldi r23, 0x18
    ldi r20, '+'
    cpi r21, 0
    brge _rit_write_stat
    ldi r23, 0x04
    ldi r20, '-'
    neg r21
_rit_write_stat:
    call putb
    mov r22, r20
    call putc
    clr r23
    ret

I didn’t want to use the stack more than absolutely necessary (push and pop are an exorbitant two cycles apiece, and memory was also precious), so I manually tracked register allocations for each subroutine. This was painful and resulted in more than a few bugs. For consistency, I allocated registers vaguely following the avr-gcc ABI:

In practice, I frequently broke every one of these conventions (except for messing with r2-r12).

Art

With an effective screen resolution of 120x66, pixel art was pretty much inevitable. I originally hoped to use assets from opengameart.org, but quickly found that existing character sprites and tilesets were far too big, generally around 32x32 pixels. For AfterZhev’s tiny screen, I wanted character and tile sizes of 12x12 pixels. In the end, I ended up making all the game art myself, with the help of Pedro Medeiros’ superb Pixel Art Tutorials.

Choosing colors was unexpectedly difficult. AfterZhev uses 8-bit R3G3B2 colors, which results in a large but unwieldy palette. A lot of the colors are either under or over saturated and many don’t look good together. I’d much sooner be given a 32-color palette with nice colors than the 256-color 8-bit palette.

Fonts

I considered some incredibly tiny fonts, such as this 3x3 one, but they were pretty much unreadable. In the end, I went with a Brian Swetland’s and Robey Pointer’s 3x5 “Tom Thumb” font, which struck a really nice balance between size and legibility. I also used Matthew Welch’s “Tiny” font in places where I needed something even smaller.

Music

For a few months, I planned to re-arrange Alexander Borodin’s Polovtsian Dances in AfterZhev, but eventually decided against it. The music is really beautiful and it seemed vaguely disrespectful. Instead, I spent a week trying things out on the piano until I came up with something that sounded pretty good.

Thoughts

Some things that worked out well

Some lessons learned

Source code

The repository is hosted on GitHub, along with instructions for compilation. On Linux, everything should work once you’ve installed the dependencies; Windows will probably be more difficult. Also, I can’t in good conscience recommend using the AfterZhev engine in other games, but if you do, I’d love to see them!

Construction

AfterZhev outputs a 60 Hz 640x480 VGA signal:

You can use proper digital-to-analog circuitry for each color channel, but since 8-bit color is pretty imprecise and the VGA impedance is fixed, it works pretty well just to weight the channel pins with 1:2:4 resistors. I used 1k, 2.2k, 4.7k resistors since that was what I had on hand.

AfterZhev also outputs audio on pins PC0-PC7 (37-30 on Arduino), where PC7 (30) is the most significant bit of an unsigned 8-bit sample, and PC0 (37) is the least significant bit. I used an R-2R (R=100 ohms) resistor ladder as a digital-to-analog converter. Without an amplifier, the main thing is to keep current less than 20-30 mA through any pin.

Finally, AfterZhev uses a NES controller (I’ve successfully tried both an original Nintendo controller and a clone):

I originally implemented the VGA circuit on a breadboard, and later a solderable perfboard, but ordered a PCB from JLCPCB for the final version. It arrived in about a week and was far easier than wiring up the two dozen or so resistors by hand. Here’s the schematic and the final populated PCB:

Resources and inspiration