Java Game Programming Tricks:  The Basics

Mark Taylor


    Java has come along way since its introduction in the  90's.  Given the latest JRE and todays 2+ Ghz computers Java can be quiet fast and more than adequate to run most any type of 2D game you care to mention and 3D support has moved way beyond Sun's lackluster Java3D and is quiet formidable ( click here for more info).  In fact it is quiet an exciting time to be doing gamedev in Java.


    In this article I will concentrate on the basics.  I always tell people that Java + AWT is a 2d game programming API, all the elements are there for you to use, you just need to know how.  However, game programming in Java requires a few special considerations that may not be obvious to someone who has programmed a game in another language; threading, timing and managing the garbage collector to name a few.  In this article I'll go over these 'gotcha's' and show you all the tools you'll need to make a game by presenting a simple applet based demo.  I assume you are familiar with Java and object oriented programming  (for a more basic intro click here).


    We will stick to Java AWT classes to keep it simple.  You should know that the latest Java2D classes give you ALOT of functionality, like hardware acclerated blitting, a better timer, and full screen windowing on platforms that support it, to name a few.

Odds and Ends

    The latest JDK, 1.4.2 does not by default compile code that will run with outdated vms, namely the Microsoft vm that currently ships with Windows.  To get your applets/applications to run on machines without requiring the user to install the latest JRE you must compile with the switch "-target 1.1" and limit your class use to AWT classes.

    In the example applet you will notice the first few frames the animations tend to flicker.  This is the result of image caching.  There is probably a work around for this but I haven't found one.

The latest JIT compiling virtual machines can give erratic performance (start slow and become faster) until the vm has 'warmed up'.  This usually isn't noticable unless you actually track slowest and fastest frame rates.

Applets - How they work

    This wasn't obvious to me when I first learned Java as I had no experience with the 'event driven' paradigm, so here is a short tour:  Applets are event driven.  Applets are executed from a web browser (duh).  The browser gives the applet an area of the page to draw itself in and loads the applet.  There are four methods in Applet that the browser calls:  init();  start();  stop(); update(); and destroy();  So init() is where you put your init code, start() is where you put your start up code and stop() is where you put your cleanup code.  You'll never code destroy();

    How applets draw themselves is not terribly intuitive and bears some explanation.  The browser will call update() when the area of the screen the applet is in needs to be redrawn and pass a Graphics context to the applet to draw itself on.  The update method, which is a method of the Applets parent class Component, as well as the repaint() method will in turn call the paint() method.  So to get smooth, non flicker animation you should overide the Applets paint() method and call it when you need to redraw the applets area.  (NOTE:  if your confused where these methods come from download the JDK Documentation and look at awt.Component.  At times it seems learning Java is learning the classes, so its important to be familiar with them.)

    So far we have:

public class GameApplet extends applet  {
    init() {}

    start() {}

    stop() {}

    update(Graphics g)  {
        paint(g);
    }
    paint(Graphics g) {
        // your code here;
    }
}

Double Buffering

Double buffering is how you get smooth flicker free animation in computer graphics.  The idea is you draw your game on a non visible buffer then copy it to the viewable area.  If you try drawing directly on the applet you will notice 'tearing' and flickering.  So for a backbuffer we need an offscreen Image and a Graphics (think a Graphics as a Device Context in the win api) for that image:

  Graphics backbuffergc;
  Image backbuffer;

    CreateImage() is also a method that Applet inherits from its base class, Component.  Then we modify the init method like so:

init()  {
    backbuffer = createImage(width, height);
    backbuffergc = backbuffer.getGraphics();
}

paint(Graphics g)  {
    g.drawImage(backbuffer,0,0,width,height,this);
}

    To reiterate, the Graphics context g here is passed to the applet from the browser; this is your area of the screen to draw on.

Loading Graphics

    Java's AWT only supports Image formats of .gif or .jpg.  Loading images over the internet can take a bit of time so a good procedure to use is to put all of your gfx into one large Image and load it using a MediaTracker and then divide the big image into the smaller images you will use for tiles, animation frames etc using an ImageFilter.  Gifs are good to use because you get transparency.  Again, consult the Javadocs for documentation of these classes.
Image [] tiles = new Image [NUMTILES];

public void loadimages ()  {
    MediaTracker t = new MediaTracker (this);
    Image imagestrip = getImage(getDocumentBase(),"bigimage.gif");
    t.addImage(imagestrip,0);
    try  {
        t.waitForAll();
    }
    catch (InterruptedException e) {}
    if (t.isErrorAny())  {
        System.out.println("Error loading Images");
    }

    ImageProducer source = imagestrip.getSource();
    for(int i=0;i<NUMTILES;i++)  {
      ImageFilter getsubimage=new CropImageFilter(i*TILEWIDTH,0,TILEWIDTH,TILEHEIGHT);
            ImageProducer producer=new FilteredImageSource(source,getsubimage);
            tiles[i]=createImage(producer);
    }
}

    We'll put the call to this method in the init() method so our Images will begin loading first thing.

Threads - You need at least one

    This was another confusing concept for me.  In event driven programming your applet code only executes when it recieves an event from the browser (ie a mouse move or click in the applets area).  So to get our applet to run continuously we have to provide a separate thread of execution.  Space Invaders would not be much fun if the aliens only moved when you did, would it?  A detailed discussion of threads is beyond the scope of this article,  but basically, this is where your main loop will go.

    To use a thread in our applet we will implement the Runnable interface.  Runnable lets us set up a thread to execute seemingly continuously.  We only have to provide a run() method which in our case will be the actual main loop

public class gameapplet extends Applet implements Runnable ...

Thread mainloop = null;

public void start ()  {
    mainloop = new Thread (this);
    if (mainloop != null)  {
        mainloop.start();
    }
}

public void run ()    {
    while (true)  {
        // update the game logic
         repaint ();  // calls paint() - draw the applet
            Thread.currentThread().yield();  // This plays nice with WinME
           }
}

public void stop ()  {
    if (mainloop != null)  {
        mainloop.stop();
        mainloop=null;
    }
}

    So our main loop thread begins when the browser calls the applets start() method and is stopped when the browser calls stop().

Timing - Accurate timing is not so easy

    One of the big 'gotcha's' with Java is getting accurate timing.  There are basically two ways to time a game as you know:  1. Pause the game every time thru the main loop using currentThread.sleep(int) or 2. Using System.currentTimeMillis() to keep track of the change in time.  The problem with both of these is the fact the Java timer on most platforms is very inaccurate (as bad as + - 50 millisecs on WinME).  Win 9x/ME is the worst, Win XP and Mac are better and Linux is the best from my anecdotal experience.  On all platforms but Linux using the Java timer will give you erratic, jumpy animation.

    There are multiple solutions to this problem.  The GAGE2D (link: http://java.dnsalias.com/) library uses Java Native Interface (JNI) to access the OS timer thru a .dll under Windows.  This of course makes your app unportable and is not usable for an applet anyway.  The Java3D api includes a high resolution timer class, but not many people have Java3D installed on their machines.  One of the simplest and most straight forward solutions is to simply use a self correcting timer class that has been posted in the forums at javagaming.org and should be on the wiki (links http://community.java.net/games/).  Using this class is straight forward;

HighResTimer timer;

//in the init method:
timer=new HighResTimer();
timer.setDelay(REFRESH_RATE);
timer.startTimer();
timer.setAutoCorrection(true, 50);

//in the run method:
synchronized(timer) {
    try {
        timer.wait();
    }
    catch(Exception e) {}
}

    The class as published gives you access to the tick count which you can use to get framerate independant animation, but simply waiting is usable for our purposes.

Garbage Collection - How to manage the gc

    One of the most often discussed features of Java with regard to games / real time applications is the garbage collector.  The gc is a simple thing to manage however and poses no real problems.  Its true that when the gc kicks in during your game it creates a noticable lag.  This can be seen simply by coding a System.gc() in your application.  This method forces the gc to execute and causes 100 - 150 millisec pause. 

    The most simple solution to preventing these lags is to simply not let any objects go out of reference in your main loop.  You do this by simply not creating or destroying new objects during your game.  Creating new objects during, for example, level loading is fine.  But creating a whole slew of particles every time the player fires his weapon is wasteful and invites the gc to step in.  A common solution is to create a pool of objects to draw from and then simply reuse them; using the particle system example, pre create an array of 100 particles with a member boolean variale to indicate if its in use and then when you need one simply iterate thru the array.

    Again there is more than one way to approach this problem.  For example the GAGE2D library provides a garbagecan class to hold unused objects to prevent them from going out of reference.

Input and The Demo

    At this point we have a basic framework in the form of an applet that gives us the ability to load graphics, utilize a continuously running thread of execution, perform double buffering, and use relatively stable timing under any platform. The final element we need is the ability to read the players input.

    As always, there is more than one way to do input.  The Component class has the methods processKeyEvent() and processMouseEvent() which you can use in the actual applet.

    My personal preference is to separate out the actually game into a gamemanager object where all of the actual game logic and rendering occurs and register this class as a KeyListener to the applet.  Most simply this class would consist of public methods init(), update() and paint().  To do input with this type of setup simply add your gamemanager object as a keyListener to the applet;

// in the applets init method
this.addKeyListener(gamemanager);

Then you must code for the KeyListener interface in your gamemanager object;

public class GameManager implements KeyListener ...

// in the game application
public void keyTyped (KeyEvent e)  {
    if(e.getKeyCode() == KeyEvent.VK_UP) {
        //perform some action
    }
    ...
    // process other keys
}
public void keyPressed (KeyEvent e) {...}
public void keyReleased (KeyEvent e) {...}

    So now you have all the tools to make a game.  The example applet consists of a basic walkaround demo for a console style rpg game.  It demonstrates a scrolling tile map and an animated player sprite using the classes GameManager, Map, and Player.  The example is intended to be just that, an example.  An actual game would require a great deal more abstraction and quiet different organization.  The source is commented so feel free to browse it, copy it, condemn it, whatever.
Demo Source Code
Demo Applet

Hosted by www.Geocities.ws

1