Geoincursion is a Nintendo DS homebrew beat'em up game, and is the only game I have developed for which I created all content, original music, sounds, and graphics. I invested time and paid attention to the playability and depth of Geoincursion, with the goal to create a game that's as complete as possible, within a reasonable amount of time.
It has multiple levels, multiple enemies and my own take on a secondary resource/special move system. As with all my other projects, the program code is designed and written from scratch; it depends only on system-level libraries (libnds, etc.). As such, it does not use any engine-type libraries; it runs on its own, custom engine. Geoincursion took four months of evenings and weekends to complete. The whole game is crammed in a ROM file that is about as large as a low-quality MP3 song.
The action takes place across multiple countries of the world, with new enemy types introduced in each country. The point of the game is simple: beat up everything that moves. Spend energy to unleash special attacks or to heal yourself. There are no extra lives and there are no continues. Also, your life does not replenish between levels.
The game begins in Circus Park, in Bucharest, Romania. I chose this location because it is where I grew up.
I used to live in the middle of the three apartment buildings visible in the very first level (the one on the right in this screenshot), which were right at the edge of the park.
This is the circus in question.
Downloads
Geoincursion ROM - download this to play the game in an emulator (I recommend DeSmuME)
Source code - complete with resources, sounds, music, and source code
Soundtrack - All Geoincursion tracks in MP3 format
Dev kit with ready-to-compile source code - get this if you wish to compile the Geoincursion ROM yourself. This is a cut down version of devkitPro, which excludes examples, libraries targeting other platforms, and other things I considered unnecessary
Installation instructions (tested in Windows 7):
1. Unzip to c:\ndsdev
2. Add c:\ndsdev\msys\bin to your PATH environment variable
3. Create environment variable devkitarm and set its value to /c/ndsdev/devkitarm
4. Create environment variable devkitpro and set its value to /c/ndsdev
How to play
It's a beat'em up; walk to the right and beat up everything that moves, using the following buttons:
Y button - your basic attack which performs combos when tapped repeatedly; builds up energy
B button - a powerful strike; low energy cost
A button - magic attack which damages all on-screen enemies; high energy cost
X button - heal yourself; high energy cost
START button pauses the game play at any time.
Achievements can be earned by performing trivial tasks and actions, such as walking to the right or getting hit for the first time. I'm joking; there are no achievements in Geoincursion.
Known issues
(low impact) While falling through the air, enemies will sometimes be erroneously rendered behind other, standing enemies. Fixing this requires fundamental core changes which do not justify the visual benefit.
(low impact) Sometimes a high-pitched sound will play on the third and on the last level. It will play for a few seconds, and then stop. Despite my sincerest efforts, I was unable to identify whether this is because one of the sounds I recorded (or perhaps a track sample) contains invalid values, or whether it is due to a bug in the media player library. This issue has a low rate of incidence and no effect on game play, which is why it has remained in the game.
Development time breakdown
This is how I spent my time developing Geoincursion:
Programming
- 3 weeks on core (DS system level hardware abstraction and functionality for the higher layers)
- 2 weeks on animation and rules around character states
- 2 weeks on controller-type code (levels, enemy spawning, game state transitions)
Design
- 2 weeks on look and feel (life/energy gauges, UI), including implementation
- 1 week on balancing game play
Art and sound
- 2 weeks on music and sound effects
- 4 weeks on graphics (backgrounds, sprites, UI)
Game design
I wanted the game to be immediately playable. As such, there is a main attack which will be spammed, building up energy. I wanted the player to choose between a fun attack (the Power Strike) and saving up for either a necessary self-heal or an all-screen magic attack.
I chose the Y button for the main attack because it will be pressed with the tip of the player's thumb. I chose the B button for the intermittently-used Power Strike so that the player can press it with the joint of the thumb, while keeping the tip of the thumb on Y. This means that the thumb has to move out of position very infrequently (only when healing or using the magic attack).
As soon as I created the animation for the Power Strike, I found it extremely fun, so I was lucky that way. The player needs to be somewhat familiar with the game before he can use Power Strike effectively. The good part is that even when used ineffectively, I still found it fun!
Another design tenet was to often have many enemies on screen. I think I achieved this goal by sometimes having packs of over ten enemies at a time, without any impact on the performance of the game play. Many of the classic games that inspired me (Final Fight, etc.) limit the numbers of on-screen enemies to less than 8, usually. I wanted to surely have more than that.
I've created enough enemies to have a new enemy type in every country. However, having to rely on only one enemy type for the first country (although with several palette swapped version) made the level a bit repetitive.
In terms of AI, I've kept things simple, knowing that small differences in behaviour parameters will create enough diversity for my taste. The behaviour of the simplest enemies is enriched with additional layers for enemies who appear later in the game.
Boss sprites are based on the regular enemy sprites. However, their behaviour parameters are dialed up significantly, making them more dangerous when driven by the same AI as the regular enemies.
I chose not to replenish the player's life between countries because of two reasons; firstly, I wanted a more hardcore, 90s arcade feel to the game. Secondly, I wanted the player to have the responsibility and privilege of choosing how to spend the energy bar. Once you get better, you will spend less energy on healing, and more on fun stuff such as Power Strikes.
Some points throughout the game are meant to be harder, including boss fights and more difficult waves of enemies. Balancing these without going too far has taken more time than I anticipated. Still, I'm sure there will be players who have a much easier than anticipated time with passing these roadblocks.
Balancing the game was simply myself playing through it a bunch of times. The idea was that I be able to beat the game a good number of times, but not every time. In the end, my play through records show that I'm beating the game about once in each three tries; however, I make it to at least the second last boss the other two times in each three tries. This seemed like a good balance to me.
Programming details
I used devkitPro to develop Geoincursion, which includes a C/C++ compiler. I was happy at the opportunity to sharpen by C++ skills, as I have not yet used this language professionally. I did read about C++ goodies such as reference-counting pointers, but I chose to handle memory management (allocation/deallocation) myself, mainly because I wanted to gain some experience with this paradigm.
Given my initial lack of familiarity with the platform (it was the first time I attempted Nintendo DS development), I opted for a bottom-up development approach. The first three weeks were spent developing what I considered a core set of classes, providing abstraction from the platform. Some examples include system initialization, sprites and sprite pools, input, console and font, backgrounds, custom types, and a video frame runner. Other components developed later, such as actors, animators, level controllers, etc., relied upon the core set of functions without knowledge of the hardware.
I like few to no warnings generated when I compile my code, so I chose to use
-Wall -Wextra -Werror
in order for the compiler to check for a large number of warnings, and report them as errors. This actually paid off because it pointed me towards refactoring a few classes, which uncovered a bug which had been hidden throughout most of development.
I also tried adding -Wpedantic, to get the highest amount of warnings/errors possible. Unfortunately, the number of warnings/errors generated just from libnds's header files was just too high for me to fix. On the positive side, my own code only generated about twenty warnings/errors.
To build the ROM in release mode, run:
make
(don't forget to run make clean prior to this if you ran a tests build previously)
The hardest part of starting the development for Geoincursion was working for a few weeks without having anything resembling a game.
I created placeholders for the animations, and the image to the left shows what I've been looking at for about one and half months while developing.
Each animation "phase", such as walking, standing, being hit, was a series of numbers (to show me that the animators are correctly switching between frames), along with one or two letters which identify the "phase" (such as W for walking, S for standing, etc.)
Automated tests are an integral part of my day-to-day work, and Geoincursion was no exception. The test suite I wrote for it includes both system and functional tests. The system tests exercise things like sound and music playback, successive allocation and de-allocation of memory in order to identify memory leaks, etc. The functional tests target areas of higher algorithmic complexity, such as sprite re-prioritization.
To build the ROM in tests mode, run:
make tests=1
(don't forget to run make clean prior to this if you ran a release build previously)
The code is overall well compartmentalized, in my opinion. By far, the largest two classes are 400 lines long. However, the conditional branching is kept very low throughout the program. From scratch, the ROM takes one minute to compile, on a slightly older computer. Here is a graph of the number of C++ classes which make up Geoincursion, over time.
Graphics details
I want to begin by saying that I'm the farthest you can be from artistically talented. I was never good at drawing, and while I greatly appreciate pixel art, I never had either the patience or the skill to create beautiful pixel art. As such, I chose real-life images as my graphics sources. I figured that with a bit of processing, and given the low resolution of the Nintendo DS, it will look decently.
The backgrounds are based on my own travel photographs. I removed the bottom part of each photo and filled it with just a few 16x16 tiles repeated over and over. This is so that each background fits in memory, while still looking decently good at the top. The bottom part of the screen is where the game play takes place, so the top of the screen is never covered by character sprites, so it should look the best. Also, each of the backgrounds was put through a pixelator, to remove some of the realism.
Character sprites are based on frames of video. For all characters excluding Meiss, I filmed myself with a camcorder in my basement, while wearing various clothes. The riskiest move I performed was leaping backwards for the falling down animation. Several times I almost hit my head on the ceiling while doing that. After choosing the animation frames from the videos, I began cutting around the characters, and cleaning up pixels, etc. This was grueling and it's the activity I disliked the most, throughout development. It was extremely time consuming, repetitive and mechanical.
Right after I added sounds to the ROM, it stopped loading in the emulator and on real hardware. This was because the ROM had passed the 4Mb mark. The Nintendo DS only has 4Mb of RAM, and to be able to load a larger ROM introduces the complexity of having to deal with a file system layer (which uses the ROM itself as storage) to load resources, such as bitmaps, in the DS RAM. I did not wish to introduce this extra complexity, because I was certain that I could fit it in 4Mb.
Therefore, I started looking for places to trim some fat (because I didn't want to rely on the FAT... ). At that point, my sprite sheets had a lot of empty space, essentially because I had been lazy; I based the enemy sheets on the player sheet, but the enemy sheets required about 60% of the animations of the player sheet, but I hadn't shrunk the sheets. After getting rid of this excess, the PNG files only decreased in size by 3-5%, because of the RLE compression.
To my pleasant surprise, because the sheets end up stored as bitmaps, the size savings in the ROM were about 40% of the bitmap total size in the ROM. In total, I saved almost a whole megabyte, reducing the ROM size down to about 3.2Mb.
The main tool used for creating the graphics has been GIMP, with a bit of MS Paint and Paint.Net sprinkled on top.
Music details
As for the music, I tried my best to create a variety of tracks, despite not being musically trained. It took a moderate amount of time, but was very satisfying. Many different instruments are used, including piano, guitars, organ, tubular bells, etc.
I made sure to use the one I consider the catchiest ("Funky Fight") on the first level, since that will be heard the most. Also, I chose the most somber and ominous one ("Really Serious") for the last level, right after a heavy, overdriven guitar-based track.
The transition from the heavy guitar track to the organ-based track feels like a relief, because it happens during a transition in game play from a difficult enemy wave to a much easier one. It also makes the player feel that the conclusion is near, due to the choice of instruments.
The main tool used for creating the tracks has been OpenMPT, a free audio tracker.
Sound details
I've recorded the game sounds on my Motorola Razr phone, using a simple WAV recorder app. In order to isolate myself from the noise around my home when recording, I recorded inside a walk-in closet full of noise-dampening clothes. For fifteen minutes, I yelled, grunted and punched myself to get some good effects. The "knife hit" sound effects used for the knife-wielding enemies are two kitchen knives being struck together.
I needed an explosion sound effect; I first tried to record a bottle of 7Up being opened thinking that I could slow it down. Unfortunately, that plan failed, as the microphone was not sensitive enough to capture the hiss. The explosion simply ended up being created from a slowed-down punch effect with aggressive echo.
One difficulty I encountered was the fact that there is a vast difference between how an effect sounds via the built-in DS speakers and via plug-in headphones. Low-frequency sounds like a bass line is barely audible on the DS speakers, and high-frequencies seem louder than through headphones. A lot of time was spent simply transferring the ROM to the hardware so that I could hear it through the DS speakers as well.
Essentially, I aimed for an in-between approach, where I used as few low frequency sounds as possible, to get a good compromise between the speakers and the headphone experience.
A lot of time was spent of adjusting the volume of sounds relative to the background music, and relative to each other. DS speakers versus headphones had to be taken into account here as well. Deep grunts, such as the ones used for Meiss, had to play at a higher volume than, for example, high-pitched knife hits.
This also meant that I had to bring several high-pitched punching sounds down a few semitones for them to be in line with the other effects.
The most frequently heard sounds throughout the game are the punching sounds. From among the several that are in the game, I chose the ones that are less intense to be used for the player, since they will be played all the time.
The Power Strike special move is intentionally very loud when it hits a large group of enemies. I found Power Strike to be feel very fun as soon as I implemented it, so I wanted its sound to be over the top.
The main tools used for creating the sounds have been Audacity, and Windows XP's sndrec32.
Thanks for checking out Geoincursion, and I hope you have as much fun playing it as I did making it!