Game Development, Mortuus Est

The Code Behind Mortuus Est

Mortuus Est (subtitle pending) is growing at a rapid pace. With over 70 classes, 18,000 lines of code, and countless hours of toil, it’s really starting to stack up. The base library is libgdx – a powerful, feature-filled LWJGL-based library that supports iOS, Android, and Desktop deployment, and is Java based. It adopts OpenGL as it graphics engine, and there are a few extensions as well, such as Box2D to handle collisions and a custom Box2D fixture loader for .JSON defined body fixtures (check out that link – the guy is a legend). I have extended and repackaged the libraries into my own custom framework, extending it with a number of custom classes I have written to ease the design of many aspects of the game. I wanted to cover just a few of the systems that make the world tick the way that it does – the method in the madness, as it were. Let’s start with a few of the important principles, shall we?

Build 0.8.0.0 (18)

Basic Game Architecture

Every object derives from base world object, that determines its position, dimensions, box2d body (for collisions), render scale, render colour, type (as every object has multiple variations), texture, and other attributes common to all objects. With these simple parameters we can handle all objects, as well as render them, in the same manner.

More complicated objects employ their own controllers, unique to each instance. Zombies, for instance, contain a NavigationController and a damage controller for special kinds of attacks, the player a WeaponController, and interactive objects an InteractionController. As described below, the WorldController loops through all active objects in the WorldContainer and updates each object’s state. Where controllers are implemented, the controller has its own script that acts dynamically according to the conditions of the world – a state-based artificial intelligence.

From the top of the call heirarchy, a separate desktop and Android launcher act as the entry point to the main application. Either of these (depending on the platform from which the game is executed) instantiates the GameMain object which simply initializes the active screen with the GameScreen object. GameScreen, implementing the libgdx Screen and InputProcessor classes, handles all input and is the main thread of the application. It is of course possible to use multiple screens to accomodate different parts of the game (menu, options, in-game, et cetera) but we decided to do things differently and implement the menus within the game world itself. As such, only a single screen is needed, with functionality taken care of through scripts assigned to game objects that are executed when the player activates them.

GameScreen initializes three subsequent classes that form a variation of the  model-view-controller (MVC) design pattern implementation:

  • WorldController – handles all input, game-logic updates, and WorldContainer object management.
  • WorldContainer – using a factory pattern to handle the creation, storage, and destruction of all game-world objects.
  • WorldRendering – which loops through the WorldContainer objects and renders them to screen.

Once we are in the game, WorldController can access everything – it is the Alpha and the Omega in the architectural department. It controls all game logic updates.

Framework Hierarchy
Framework Hierarchy

The main thread of the application calls the GameScreen update method, which in turn calls first worldController.update(deltaTime), followed by worldRenderer.render(). The WorldContainer is populated by the WorldController using appropriate scripts.

Developer Variables

There are multiple static classes that store variables used in balancing the game. Each class contains variables that are referenced throughout the program structure. This means that all variables, from zombie AI, to weapon fire-rates, to background tile alignment, are defined here. It saves journeying through miles of code in order to tweak values.

Objects Within Classes

Because of the enormous number of updates that need to take place, WorldController implements multiple sub-controllers that loop through active instances in the game world and process anything that needs to be updated, such as input, audio, fonts, the player character, zombies, the interface, level loading, dynamic item creation, and so on.

The player object itself contains its own weapon controller in order to keep track of equipped weapons and their modifications, and a player inventory stores collected items and active boosters (that provide abilities such as increased fire rates, unlimited ammo, etc).

Classes Within Classes

Helping to keep things organised, WorldContainer defines many subclasses. Each subclass is named after a category of objects and can have further subclasses within it. At the lowest level, each object’s container class contains: the texture sheet, an array for all existing instances, and creation and destruction methods for individual instances. An example is the blood splatter container:

public class WorldContainer {

     World world;  //a Box2D physics world instance which we populate with bodies
     //...

     public Effects {
          public class BloodSplatter {
               Array<Blood> bloodArray = new Array<Blood>();
               Blood blood;
               public Texture bloodTextureSheet;

               //getters/setters
               //...

               public void createBlood(Zombie zombie) {}
               public void createBlood(Body body, boolean expanding) {}
               public void createBloodCritical(Body body, boolean expanding) {}
               public void removeBlood(int i) {}
          }
          public BloodSplatter blood = new BloodSplatter();
          //...
     }
     public Effects effects = new Effects();
     //...
}

Then from the WorldController or its subcontrollers, for logic updates such as a bullet colliding with a zombie, we can call:

worldContainer.effects.blood.createBlood(zombie); //creates blood splatter at the zombie's position

This sort of encapsulation and using a variation of the factory pattern makes the act of creating instances simple. We can create multiple different methods (as above for different kinds of blood spatter) without cluttering the interface and upon creation they are automatically added to their respective array and are accessible from the WorldRenderer and WorldController updates.

Garbage Collection and Instance Pooling

The Java platform features an automatic garbage collector, which can be a performance constraint due to its periodic call that can cause hitches in frames per second. This is especially prevalent on mobile devices! The way around this is to use instance pooling as much as possible – a technique that reuses instances as opposed to constantly instantiating new ones. Textures and audio produce the most significant performance hit due to them consuming a lot of memory. Instead of assigning a dedicated Texture to each instance, we create a single Texture from a texture atlas in the WorldContainer and then assign a TextureRegion to each instance – a subset of the texture, based on its type or current state. In this manner, we can create an infinite number of objects but the resource load is constant. The same applies for audio.

In the same manner, any variables are handled the same way. Although the performance hit is minimal in comparison, the reuse of complex variables saves 2 or 3 milliseconds per garbage collection call. This is accomplished by creating a single instance of each variable used in each class, and this variable is reassigned when another variable of the same type is required.

Façade Patterns and Composite Objects

Certain interfaces can become overly complicated and cumbersome, and a particular example is this:

Say that our player can equip a weapon from their inventory – standard practice in many shoot-‘em-up games. Although there may be 5, 10, or 30 different weapons available to the player, there is only ever one currently equipped, and further, all weapons have the same properties: shoot, reload, ammunition count, and a texture. So how do we handle this?

The composition of the Player class is as follows: a PlayerInventory object that tracks all items and statistics, weapons, and active power-ups. The Weapons object stores all currently owned weapons, and within each weapon object are WeaponModification objects that record exactly how that specific weapon is modified. This sort of composite class simplifies the management of many objects and their behavior.

Separate to the PlayerInventory is the WeaponController object that manages the weapons – from modifying ammunition when firing, keeping track of the currently equipped weapon, and time between shots. The call hierarchy starts from the GameScreen, where the input is given to shoot a weapon. This call is passed to the WorldController, which in turn passes it to the Player instance. The Player instance passes it to its WeaponController using a simple shoot() function. The WeaponController utilises a façade pattern to get the currently equipped weapon to fire a projectile of the correct type, damage, size, and velocity, and then delay any firing for a specific time (in order to control the fire rate). No information is given to the WeaponController besides a simple shoot command, and the correct projectile is produced based on the player’s current loadout.

Inheritance and Polymorphism

Now there’s a slightly scary word – but it’s not as bad as it sounds, I promise. Inheritance (or extending, in Java) should be a concept known by any object-oriented programmer: deriving one class (the sub/derived/specialised class) from another (the super/base/generic class). A good example of this is interactive objects:

Objects that can be interacted with require an InteractionController instance (to detect when the player is interacting and what it should do), and an Animator (for any animations during interaction). This is common across all objects in our game that can be poked and prodded, and so a generic base class is designed with these two (fairly detailed) classes. This InteractiveObject class is then ‘extended’ with further functionality specific to that specialised version, such as specific animations and behaviour.

Polymorphism is based on the concept of inheritance and is a fancy way of saying that a subclass can be passed as a superclass instance – an unbelievably helpful technique. A great example is this: we wanted multiple zombie types, but as always they all have health, the same navigation controller design, and an animator. Thus we define a superclass called Zombie that contains this, and then extend it to create ZombieSpider, ZombieFarmer, ZombieSevered … Of course, each instance must be stored in an array for managing and rendering. Here’s the plan:

We define a superclass array:

Array zombieArray = new Array();

And now we create a subclass zombie and add it to the array:

zombieArray.add(zombieSpider);

We aren’t done here, however. The ZombieSpider type has now been cast to a Zombie type, and we can no longer access any of the ZombieSpider members or functions because it is using the Zombie interface. When we need to access class-specific functionality, we need to re-cast the object to its original type:

for (int i = 0 ; i < wc.zombies.getZombieArray().size ; i++) {
     zombie = wc.zombies.getZombieArray().get(i); //ZombieSpider returned as Zombie

     if (zombie instanceof ZombieSpider) {      //check type
          zombieSpider = (ZombieSpider)zombie;  //cast back to ZombieSpider
          // Do stuff
     }

Wrap-Up and Out

That about covers the core concepts of the game – it’s amazing how simple OOP principles are, and how effective they can be when used properly. When I first started Mortuus Est, the Ludum Dare beginnings caused me to completely ignore OOP conventions – no patterns, no inheritance, no nothing. It soon became an unmanageable beast that was far heavier (metaphorically speaking) than it should have been. This write-up serves as a reminder to myself of how simple good practices are, and how they can ease the burden of a complicated project.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s