2011-01-03

Technobabel Technicalities - Overview

In this article, I will give an overview of the internals of the game engine I'm building for Technobabel, a 2D Android shoot 'em up. I want to document this process because it helps me avoid getting lost in the details and provides a nice future reference. Furthermore, this inside look might be useful to someone else who stumbles on this blog. Someone might know a better way of implementing a certain feature and they'd be kind enough to leave a comment.


About Technobabel
Technobabel is a horizontal side scrolling shoot 'em up game. You control a character who flies through various levels along with some sidekicks in pursuit of an alien scientist who is responsible for a robot war against humanity. The unique thing about Technobabel is that it aims to be highly unpredictable; to defy the conventions of the genre and provide a really fun and unique experience. We have complex bosses, levels where you're not limited to horizontal scrolling, and various game mechanics.

To this end, I wanted the Technobabel engine to be flexible and customizable. This is a bad excuse for the unwarranted complexity in certain parts of the engine; favoring flexibility over getting the game done means that I spent more time laying down a framework that might be an overkill for a game of this kind. Game developer people will recognize this in the overuse of design patterns and the reliance on XML files for everything. I'm always torn between making visible progress and not going for the most straightforward implementation of a certain feature, because I know that I will have to write the complex code eventually. On the other hand, there's the YAGNI principle (You Ain't Gonna Need It) and all the design guidelines favoring simplicity and quick delivery. Generally, I strive to have a balance between these two conflicting goals.

Technobabel is an Android game. I love the Android SDK, although I haven't had to deal with many platforms yet. I use the Android emulator and a used Samsung Galaxy Spica phone to test the game. I picked the Spica because it was really cheap and one of the weaker phones on the market. We want to target as many phones as possible and the Spica is a good example of the bad phones we might target. I plan to buy a Nexus or HTC phone eventually, but for now the emulator/Spica setup suffices.

High Level Overview
Technobabel classes are divided into several Java packages according to their functionality. The code of Replica Island was a great inspiration and I incorporate some of it (such as fixed size arrays and object pools) in my design. However, Technobabel code is not based on Replica Island's and the designs are fairly different. The packages are:
  • Graphics
    • Canvas Graphics
    • OpenGL Graphics
  • Input
  • Audio
  • Logic
  • Object
  • Utility
The packages are kinda arbitrary and I mostly use them because I don't like seeing a long list of classes in Eclipse. Not everything fits neatly in one package or the other, and some classes are in the root project package.

The graphics class is solely responsible for drawing stuff on the screen. There is no logic here, just drawing commands, sprites, textures, and the graphics subsystem. Both Canvas graphics and OpenGL graphics are supported. The main reason behind that is because I wanted to get things drawn on the screen as soon as possible, so I abstracted the graphics system and provided two implementations. So far the I've only worked on the Canvas implementation, and the performance is good enough. I'm trying to keep the game playable in the emulator because that's where I do most of my testing. The design of the graphics subsystem is loosely based on the design of a game I was working on with my friend ramirez, which was to support both an SDL backend as well as an OpenGL backend. Should Technobabel be ported to another platform in the future, it is trivial to provide a new implementation of the graphics primitives. The last thing I want to say about the graphics subsystem for now is that only the low level commands are abstracted, higher level concepts like renderers for sprites and backgrounds as well as draw commands only need to be written once, although you can still subclass them if there is a certain performance advantage to doing so.

So far the input subsystem has been fairly basic. This is because I only support key input at the moment (the Spica has directional keys). The design tries to separate the events understood by the game (such as move left or shoot) from the actual events that Android sends (screen touch, key press, trackball, etc.) The input subsystem queues the hardware events and dispatches them to interested listeners. Objects that want to listen to input events normally have input components that register themselves with the input subsystem and define methods such as move or shoot. Like I said, the system is still basic and I will probably need to make big changes when I actually have to support more than one input method.

I still didn't implement audio in the game, but the audio package is where the classes for playing music and sound effects will end up. There will be one audio object that has methods to play or stop music and to trigger sound effects. Logic is just a generic package for anything logic related. In fact, I think the Object package should be under the logic package. Here you find classes that deal with game levels, collision, components used by objects, and sprite updating logic.

The object package represents the core of the object system. There are two types of objects so far: bullets and game objects. They both share some common properties such as having a position and a velocity, but bullets are much more lightweight and use shared components. Game objects use the component pattern where a game object is simply a list of game components that are updated when the object is updated. Each component provide a certain capability: there are components for sprites, collision detection, input, life, and AI movement. The only difference between an enemy and a player character is the type of components attached. You can define objects in XML files by defining the components used and their properties. This makes defining enemies and side kicks fairly easy (e.g. an enemy that explodes when it gets near the player might have a component that triggers an explosion on proximity to certain objects). The object package also contains the game and bullet factories responsible for creating these objects. There is also a dedicated bullet manager to manage the lifetime and recycling of bullets. Pools of objects and bullets are used to avoid garbage collection by reusing the object when its no longer used.

Finally, the utility package is for useful utility classes that don't belong anywhere else (for now). It contains some data structures such as fixed size array, queue and hash table. It also includes SAX handler classes to parse the files that define sprites, backgrounds, levels and objects. It includes a resource manager responsible for loading resources and caching their identifiers, a class that tracks the number of frames since the game started and provides useful methods to query the current time and to check if enough time has passed since an event, as well as a debug log class taken from replica island's code.

Outside the packages there are two classes that are quite important. There is an activity class responsible for presenting the android user interface, and a game class that represents the game thread and which has methods to start, update, pause, resume, and stop a game. I will talk about the multi-threaded design in the next post, but for now it's suffice to say that the game class updates all the other components such as level, bullet manager, input and collision detection subsystems, and the objects themselves. The class also acts as a global instance which allows communication between the various subsystems. Need to get a resource? Get the resource manager reference from the game class. Need to create an object? The object factory instance is also held by the game class. It's not exactly global though, there are no singletons used, but many objects are passed a Game instance in their constructors in case they need to access the Android context or any other subsystems.

No comments:

Post a Comment