4.0 Skeleton Game Code

So now you want to know how to just get stuck in and start writing games. First you need to understand a couple of game programming design patterns and why they'll make your life easier; and then you'll want to fill in the gaps in a bit of skeleton code that we're going to discuss below.

4.1 Design Decisions

4.1.1 Static Game Class

How many instances of your game do you expect to be running in a JVM? One, and only one. Given this, you may feel the urge to implement the singleton pattern on your game, where your game class holds a single instance of itself and a method to retrieve that instance, something like:

public class Game {
   
    /** The single instance of the Game */
    private final Game game = new Game();

    /** Don't allow construction from anywhere else */
    private Game() {

    }

    /** @return the game instance */
    public static Game getGame() {
        return game;

    }

    /** Start a new game */
    public void newGame() {
        // etc
    }

    /** The main game loop */
    public void mainLoop() {
        // etc
    }

    // etc
}

However this does result in code all over your application calling Game.getGame().someMethod() - and to what end? It might look like object-oriented code but in reality you're just making things harder for yourself. It's far more intuitive to write your class purely statically, like this:

public class Game {

    /** Don't allow construction from anywhere else */
    private Game() {

    }

    /** Start a new game */
    public static void newGame() {
        // etc
    }

    /** The main game loop */
    public static void mainLoop() {
        // etc
    }

    // etc
}

Now you can just call your methods directly on the Game class. We're going to use this pattern in the forthcoming skeleton game code.

4.1.2 About Threads and Timeslicing

There is a tendency for Java programs to be heavily multithreaded, simply because you can in Java, whereas it is exceedingly difficult to do properly in C++. However, you are likely to fall into a number of pitfalls in game programming above and beyond those experienced in general multithreaded programming:

Firstly, threads were designed a long time ago for a specific purpose, and that purpose is keeping the processor busy whilst some slow I/O operation is being carried out. How many slow I/O operations are you going to do in your game? Apart from loading the graphics, sounds, and level data at the start - none (unless you're doing networking but that's a whole other issue).

What does this mean for you? Well, in normal Java, programmers will tend to set off a number of threads and expect them to more or less behave like time slices rather than threads. Time slices work in a rather different way to threads: each is executed, in turn, for a finite amount of time. You can more or less guarantee that a slice will execute for its allotted time, and that all slices will get a look-in.

Threads don't work like this. To put it bluntly, you can't accurately predict when a thread is going to execute (or if it will ever be executed), or even how long it will execute for. Therefore you should not be using threads to do stuff that you expect to occur in a timely fashion. This includes collision detection, animation, sound playback and device input, and the ultimate blow, event handling.

In the absence of threads to do this useful stuff, we have to resort to a single-threaded model where the game effectively runs in one, very busy, thread - the "main loop". This is quite convenient, however, because it highlights another problem with multithreaded game code that is not immediately obvious.

4.1.3 About Threads and Hardware

Your machine's hardware is only ever in one state at a time, unless it is some kind of quantum computer. The device drivers for the hardware - namely OpenGL and the like - keep track of this state in order to send the correct hardware setup commands to the device. Can you imagine what might happen if one thread is trying to do some drawing at the same time that another thread is trying to do some drawing? Yes, you get a mess. You will discover that in order to draw in a thread in OpenGL that you need to associate the current rendering thread with the current state of the hardware (the "context"). Suddenly you need to synchronize every method which can alter the hardware state. Suddenly your application looks incredibly complicated and runs like a dog! So much for threads.

So be aware of this next time you think it's a good idea to load your textures in a background thread, and remember that only one thread - the main thread - is allowed to execute any commands on your hardware devices.

It is for this reason that we use polling to read input devices as well. Not to mention the fact that we can't guarantee that an event loop will be executed every game frame, and so our input will be erratic.

4.1.4 Object-orientation

Object-orientation is good when it's done right. It makes your code much, much, easier to understand and work on. However you may be led astray by parrots who repeat mantras. There are some programmers that say everything should be private and you should expose your instance variables with getters and setters. The Hotspot virtual machine is even cleverly optimised to make this operation almost entirely free. But wait! Ask yourself why you're filling your classes up with getters and setters when a dot would do the same job without any need to maintain it.

You're writing a game: the source code is probably only going to be used once, or by a very few people. Most of your game code is so unique to your actual game that it is disposable code. So save yourself some effort and use a dot if you can. A critical mistake I have observed time and time again in object-oriented projects is to get so bogged down trying to design a perfect object-oriented model that the project takes absolutely ages to design. Before you know it there are hundreds of getters and setters to maintain, and interfaces all over the place to keep the reuse of your classes to a maximum - when all along the project design goal was not reuse but to get the bloody thing finished on time and under budget!

Your mileage may vary...

4.2 Show Me The Money

Ok, ok, without further ado, here is a skeleton listing which you can use to write a fullscreen Java game using LWJGL. Because we're games programmers, we don't want to do a Hello World as we'd probably rather shoot it. But before we can make bullets we must pay homage to the rotating square!

import org.lwjgl.*;
import org.lwjgl.opengl.*;
import org.lwjgl.input.*;


public final class Game {
    static {
        try {
            DisplayMode[] modes = Display.getAvailableDisplayModes();
            System.out.println("Available display modes:");
            for (int i = 0; i < modes.length; i ++)
                System.out.println(modes[i]);
            // For now let's just pick a mode we're certain to have
            Display.create(new DisplayMode(640, 480, 16, 60), true);
            System.out.println("Created display.");
        } catch (Exception e) {
            System.err.println("Failed to create display due to "+e);
            System.exit(1);
        }
    }

    public static final GL gl = new GL(16, 0, 16, 8);
    static {
        try {
            gl.create();
            System.out.println("Created OpenGL.");
        } catch (Exception e) {
            System.err.println("Failed to create OpenGL due to "+e);
            System.exit(1);
        }
    }
    public static final GLU glu = new GLU(gl);

    /** Is the game finished? */
    private static boolean finished;

    /** A rotating square! */
    private static float angle;

    /**
     * No construction allowed
     */
    private Game() {
    }

    public static void main(String[] arguments) {
        try {
            init();
            while (!finished) {
                Keyboard.poll();
                mainLoop();
                render();
                gl.swapBuffers();
            }
   
        } catch (Throwable t) {
            t.printStackTrace();
        } finally {
            cleanup();
        }
    }

    /**
     * All calculations are done in here
     */
    private static void mainLoop() {
        angle += 1f;
        if (angle > 360.0f)
            angle = 0.0f;

        Keyboard.poll();
        if (Keyboard.isKeyDown(Keyboard.KEY_ESCAPE))
            finished = true;
    }

    /**
     * All rendering is done in here
     */
    private static void render() {
        gl.clear(GL.COLOR_BUFFER_BIT);
        gl.pushMatrix();
        gl.translatef(Display.getWidth() / 2, Display.getHeight() / 2, 0.0f);
        gl.rotatef(angle, 0, 0, 1.0f);
        gl.begin(GL.QUADS);
        gl.vertex2i(-50, -50);
        gl.vertex2i(50, -50);
        gl.vertex2i(50, 50);
        gl.vertex2i(-50, 50);
        gl.end();
        gl.popMatrix();
    }

    /**
     * Initialize
     */
    private static void init() throws Exception {
        Keyboard.create();
        // Go into orthographic projection mode.
        gl.matrixMode(GL.PROJECTION);
        gl.loadIdentity();
        glu.ortho2D(0, Display.getWidth(), 0, Display.getHeight());
        gl.matrixMode(GL.MODELVIEW);
        gl.loadIdentity();
        gl.viewport(0, 0, Display.getWidth(), Display.getHeight());
        // Fix the refresh rate to the display frequency.
        gl.wglSwapIntervalEXT(1);
    }

    /**
     * Cleanup
     */
    private static void cleanup() {
        Keyboard.destroy();
   
          gl.destroy();
        Display.destroy();
    }
}