Lunar Assault 64
About
Lunar Assault 64 was my submission for the 2020 N64Brew Game Jam. During the game the player follows Wren Buster as they fell various Lunarbeasts on the moon. The game incorporates the theme of size by having large creatures to destroy, as well as a little protagonist's hopes and dreams in a big world.
My big inspirations for the game were Sony PlayStation and Sega Saturn games, which usually mixed rudimentary 3D graphics with 2D art to create dramatic and emotional stories less-frequently seen on the Nintendo 64. I wanted to broaden and recontextualize the sort of game typically seen on the console.
Media
Screenshots
-
Typical ingame perspective during a level
-
While the player is zoomed in
-
Dialogue interlude
-
Flavour text before starting a level
Box Art
Making the Game
Before the Jam
I didn't have much of an interest in homebrew game development until I watched Rachel Simone Weil's talk from MAGFest 2019. In the talk, Rachel puts forward the idea that a game console is a cultural artifact that can be examined and expanded on. Her words really resonated with me, as I felt like some of my feelings on the Nintendo 64 weren't shared with the public consciousness. This inspired me to attempt to develop a homebrew game that broadened the Nintendo 64's library.
Going in, I knew that I didn't want my jam submission to be like a typical Nintendo 64 game. Many of the best-selling and well-remembered Nintendo 64 games took advantage of the console's unique hardware features such as antialiasing, z-buffering, and texture filtering. The cartridge medium of the console also put a higher price on storage and production compared to discs. I think that pushed a lot of Nintendo 64 games to be big-budget works that appealed to general audiences. It's easy to see how the reduced production costs of CDs on the Sony PlayStation enabled more esoteric titles with smaller print runs. Making a quirky and personal game for the jam seemed like a really good idea.
I think the Sony PlayStation appealed to a lot of Gen-X players who grew up with Nintendo and Super Nintendo games. As those audiences got older, they appreciated the themes and experiences found in titles like Final Fantasy VII, Metal Gear Solid, or LSD: Dream Emulator. The Nintendo 64, meanwhile had games like Mario Kart 64 or Pokemon Stadium which were rather geared for me when I was a young child. I wanted to inject some of my personal experiences as an adult into a console I played before I came of age.
Visually, I knew I could turn off a lot of the Nintendo 64's trademark features. It was possible (and more performant!) to render triangles without z-buffering, antialiasing, and texture filtering. The SDK included the gspF3DLP.Rej
microcode, which improved triangle-processing speed while skipping perspective correction. gspF3DLP.Rej
also yielded a larger vertex buffer for 3D models. These features appealed to me, as a PlayStation-looking game might help distance my submission from something like Super Mario 64 or Banjo-Kazooie.
I had also been inspired how games like Crime Crackers and Bulk Slash showcase a character's portrait alongside the 3D action. It can help inject a protagonist into something otherwise very gameplay-oriented.
-
Bulk Slash
-
Crime Crackers
Implementing the Game
The Environment
The game's levels are a 128x128 array of unsigned bytes. Each byte represents a height. Upon game start, the map iterates the heights and generates display lists of quads for the heights. The ground height is smoothed out for the four corners of the quds via bilinear interpolation.
I had some experience with three.js and Phaser in the past, so I wrote a small level editor in JavaScript that mimicked the display list generation. I was able to paint and shape with the mouse and then save them as a binary file to be included in the ROM. The "tiles" that make up the map also have an index of 0 to 15, which determine what section of a 32x32 texture to use. Only one load into TMEM happens for rendering the environment and actors. Wren and the Lunarbeasts are drawn with vertex colouring.
The environment has a lot of quads, so to improve performance z-buffering is disabled. The level was divided into 4x4 tile sections, where each tile has the vertices and rendering commands for an area. We don't sort the quads by position on render, but rather loop through rendering them based on the direction the player is facing. This isn't perfect and produces strange artifacts, but I think it contributes to a unique look for a Nintendo 64 game which normally would have a "clean" 3D look that matched the coming decade of games.
Wren, the satellite laser, and Lunarbeasts are all rendered with Z-buffering enabled. The display commands switch between two cameras for precision purposes. A small, close one for z-buffered objects, and a larger, far one for the environment.
The Satellite Laser Attack
The Lunarbeasts and their limbs are made up of a display list, a transform, and a signed-distance field. Limbs could be parented onto other limbs and inherit their transform. They were more or less animated programmatically with interpolation and sine waves.
Checking if the player had correctly struck a weak spot involved raymarching from the scope across the environment. Since limbs and hitboxes were transformed, the rays would be computed against a hitbox's inverse matrix as well as the inverses of its parents. This didn't always work right and and some precision problems, but overall I felt the gameplay was compelling enough.
The game applies a slight amount of noise to all the colours in the environment, and that noise amount gets cranked up for a moment if the player is able to strike a weak spot. I did this to help give a "pop" feeling and imply that the player is looking through some sort of electronic display. The only downside of this effect is that it breaks a lot of Nintendo 64 emulators.
FMV Playback
FMV playback was expensive for Nintendo 64 games and wasn't very common. I knew that the SDK supplied a library to help implement this, and I wanted to try including it to introduce the premise of the game and help make it feel more unique. I quickly wrote out a premise for a minute's worth of dialogue in a text editor.
I blocked out an animation for the game's intro in Blender. It was a lot more fun than debugging when the RCP would hang up! Since the final video was going to be rendered at 240p, I didn't really have to worry about too much detail.
I don't exactly have the strongest voice for a narrator, so I called my Dad and asked him to record lines with a microphone from his home. It was a fun remote thing to do as the both of us were mostly indoors during the COVID-19 pandemic. After applying a little bit of reverb to his lines, they fit in with the video very nicely.
When I was a young child, the content of video games was a contentious issue and something my parents were conscious and worried about. It felt oddly satisfying to have a recording of my Dad saying the word "shitty" immortalized into a Nintendo 64 ROM.
I attempted initially have the video play back at 24 frames-per-second, but I found it tended to stress the decoder too much. CrashOverride was helpful in re-encoding and helping me understand the HVQM tools better.