Buy this Book
Print Book $44.95 Read it Now!
Print Book £31.95
Add to UK Cart
Reprint Licensing

Killer Game Programming in Java
Killer Game Programming in Java

By Andrew Davison
Price: $44.95 USD
£31.95 GBP

Cover | Table of Contents | Colophon


Table of Contents

Chapter 1: Why Java for Games Programming?
One of my assumptions is that the reader (that's you) has had an introductory knowledge of Java, the sort of stuff gleaned from a semester's course at college. Near the start of that course, you were probably regaled with Java's many advantages: an object-oriented paradigm, cross-platform support, code reuse, ease of development, tool availability, reliability and stability, good documentation, support from Sun Microsystems, low development costs, the ability to use legacy code (e.g., C, C++), and increased programmer productivity.
Rather than explain each of these again, I will take a different approach and discuss Java's suitability for games programming in terms of the typical misconceptions and complaints wheeled out by people who think that games must be implemented in C, C++, assembler, or whatever (just so long as it's not Java).
Here's the list of objections to Java:
  • Java is too slow for games programming.
  • Java has memory leaks.
  • Java is too high-level.
  • Java application installation is a nightmare.
  • Java isn't supported on games consoles.
  • No one uses Java to write real games.
  • Sun Microsystems isn't interested in supporting Java gaming.
It's worth saying that I think almost all of these objections are substantially wrong. Java is roughly the same speed as C++. Memory leaks can be avoided with good programming and techniques like profiling. Yes, Java is high-level, but it offers more direct access to graphics hardware and external devices. Installation isn't a nightmare if you use decent installation software. There's a growing number of excellent, fun Java games, and an enormous amount of support available from Sun and Sun-sponsored sites.
If you're keeping count, I haven't disagreed with the lack of a games consoles port, which is a tad embarrassing for a "write once, run anywhere" language. Things may be changing in this category, as I'll explain later.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Java Is Too Slow for Games Programming
This is better rephrased as "Java is slow compared to C and C++, the dominant languages for games programming." This argument was valid when Java first appeared (around 1996) but has become increasingly ridiculous with each new release. Some figures put JDK 1.0, that first version of the language, at 20 to 40 times slower than C++. However, J2SE 5.0, the current release, is typically only 1.1 times slower.
These numbers depend greatly on the coding style used. Java programmers must be good programmers to utilize Java efficiently, but that's true of any language. Jack Shirazi's Java Performance Tuning site (http://www.javaperformancetuning.com/) is a good source for performance tips, with links to tools and other resources. A recent benchmarking of Java vs. C++ by Keith Lea caused quite a stir (http://www.theserverside.com/news/thread.tss?thread_id=26634). He found that Java may sometimes be faster than C++. The response from the C++ crowd was typically vitriolic.
The speed-up in Java is mostly due to improvements in compiler design. The Hotspot technology introduced in J2SE 1.3 enables the runtime system to identify crucial areas of code that are utilized many times, and these are aggressively compiled. Hotspot technology is relatively new, and it's quite likely that future versions of Java will yield further speed-ups. For example, J2SE 5.0 is reportedly 1.2 to 1.5 times faster than its predecessor (Version 1.4).
Hotspot technology has the unfortunate side effect that program execution is often slow at the beginning until the code has been analyzed and compiled.
Swing often comes under attack for being slow. Swing GUI components are created and controlled from Java, with little OS support; this increases their portability and makes them more controllable from within a Java program. Speed is supposedly compromised because Java imposes an extra layer of processing above the OS. This is one reason why some games applications still utilize the original Abstract Windowing Toolkit (AWT) since it's mostly simple wrapper methods around OS calls.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Java Has Memory Leaks
When C/C++ programmers refer to memory leaks in Java, they probably don't understand how Java works. Java doesn't offer pointer arithmetic; typical C-style memory leaks, such as out-of-bounds array accesses, are caught by the Java compiler.
However, these programmers may mean that objects that are no longer needed by the program are not being garbage collected. This becomes an issue if the program keeps creating new objects and requiring more memory, and eventually crashes when the maximum memory allocation is exceeded.
This kind of problem is a consequence of bad programming style, since the garbage collector can only do its job when an object is completely dereferenced, meaning the program no longer refers to the object. A good profiling tool, such as JProfiler (http://www.ej-technologies.com/products/jprofiler/overview.html), can help identify code using excessive amounts of memory.
JProfiler is a commercial product; many open source profilers are listed at http://java-source.net/.
Another memory-related complaint is that the Java garbage collector is executing at poorly timed intervals, causing the application to halt for seconds as the collector sweeps and cleans. The Java Virtual Machine (JVM) comes with several different garbage collectors, which collect in various ways and can be selected and fine-tuned from the command line. Information on the performance of the chosen collector can be gathered and analyzed. A good hands-on explanation of this topic, centered around the JTune visualization tool, can be found at http://www-106.ibm.com/developerworks/java/library/j-perf06304/. Another possibility is GC Portal (http://java.sun.com/developer/technicalArticles/Programming/GCPortal/).
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Java Is Too High-level
This complaint is the age-old one of abstraction versus speed and control. The details of the argument often include the following statements:
  1. Java's use of classes, objects, and inheritance add too much overhead without enough coding benefit.
  2. Java's machine independence means that low-level, fast operations, e.g., direct Video RAM I/O are impossible.
Statement 1 ignores the obvious benefits of reusing and extending Java's large class library, which includes high-speed I/O, advanced 2D and 3D graphics, and many networking techniques, from lowly sockets to distributed agents. Also forgotten are the advantages of object-oriented design, typified by UML, which makes complex, large, real-world systems more manageable during development, implementation, and maintenance.
Statement 2 impacts gaming when we consider high-speed graphics, but it's been addressed in recent versions of Java. J2SE 1.4 introduced a full-screen exclusive mode (FSEM), which suspends the normal windowing environment and allows an application to access the underlying graphics hardware more directly. It permits techniques, e.g., page flipping, and provides control over the screen's resolution and image depth. The principal aim of FSEM is to speed up graphics-intensive applications, such as games.
Statement 2 comes into play for game peripherals, e.g., joysticks and game pads; machine independence seems to suggest that nonstandard I/O devices won't be useable. Java games requiring these types of devices can utilize the Java Native Interface (JNI) to link to C or C++ and, therefore, to the hardware. There's also JInput, a new game controller API.
An interesting historical observation is that the gaming community used to think that C and C++ were too high-level for fast, efficient games programming when compared to assembly language. Opinions started to change only after the obvious success of games written in C, such as Doom and Dungeon Master, in the mid-1980s. Also important was the appearance of cross-platform development tools that supported C, such as RenderWare.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Java Application Installation Is a Nightmare
The naysayers claim that the user needs to be a Java expert to install and execute a Java application, whereas most game players want to point and click on a few dialog boxes to get a game up and running. More specific comments include the following:
  1. Java (specifically, the JRE) has to be on the machine before the application will run.
  2. Code bloat since even small programs require a 15 MB JRE. Downloading this can be slow.
  3. Frequently changing JVMs make it hard to write code that will work for every possible version of Java.
  4. Nonstandard components are often required—e.g., Java 3D, causing even more installation problems.
  5. It's impossible to compile the application for a specific platform.
  6. The .jar extension is commonly hijacked by other software (e.g., by compression programs) at execution time, meaning that the user can't double-click on a JAR to get it to start.
  7. The JRE is slower to start up compared to a native compiled application.
All these problems, aside from perhaps 2 and 7, can be solved by using good installation software. I have two appendixes dedicated to installation: Appendix A is about install4j, a cross-platform tool for creating native installers for Java applications, and Appendix B is about Java Web Start (JWS), a web-enabled installer.
The code bloat comment is increasingly irrelevant, with many games weighing in at over 100 MB and many graphics and sound card drivers being made larger than 15 MB. Network speeds are a problem, especially overseas, but broadband usage is growing rapidly.
Sun Microsystems estimates that more than 50 percent of all new PCs come with a pre-installed JRE, though a game installer must still cater to the other 50 percent.
There's some truth to point 7, but the slow startup time is fairly negligible compared to the total running time of an average game.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Java Isn't Supported on Games Consoles
Unfortunately, this criticism has some justification. Video gaming is a multi-billion-dollar industry, with estimates placing revenues at $29 billion by 2007 with the market catering to over 235 million gamers. PCs and game consoles account for almost all the income, but only about 10-20 percent of it is from PCs, the majority coming from three consoles: Sony's PlayStation 2 (PS2), Microsoft's Xbox, and Nintendo's GameCube. Sony is the dominant console maker, having nearly twice as many units in homes compared to Microsoft and Nintendo combined. Microsoft accounts for about 95 percent of the desktop PC market. Arguably, two important games platforms exist, the PS2 and Windows, and Java isn't available on the PlayStation.
This problem has long been recognized by Sun. Back at the JavaOne conference in 2001, Sony and Sun announced their intention to port the JVM to the PS2. Nothing has been released, but there are persistent rumors about a JVM on the PlayStation 3, earmarked to appear in 2006.
In the future, Java may have a better chance of acceptance into the closed world of console makers because of two trends: consoles mutating into home media devices and the meteoric rise of online gaming. Both trends require consoles to offer complex networking and server support, strong areas for Java and Sun.
The Phantom console from Infinium Labs was announced at JavaOne in 2004 (http://www.phantom.net/index.php). It's essentially a PC running an embedded Windows XP installation, with an nVidia graphics card, a hard drive, and a broadband connection. Most importantly for Java gaming, the Phantom will come with a complete JRE. It was demoed during Electronic Entertainment Exposition (E3) in 2004, where it was shown running Law and Order: Dead on the Money (which uses Java 3D).
Die-hard programmers may point out that it's possible to get Java running on a PS2. One approach is to install Kaffe, an open source, non-Sun JVM, on top of PlayStation Linux. Kaffe can be obtained from http://www.kaffe.org/
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
No One Uses Java to Write Real Games
The word "real" here probably means commercial games. The number of commercial Java games is small compared to ones coded in C or C++, but the number is growing and many have garnered awards and become bestsellers:
Puzzle Pirates by Three Rings (http://www.puzzlepirates.com/)
This is a multiplayer pirate game that includes Tetris-like or Columns-like puzzles at various points. The client and server are written in Java. It won several awards during 2004, including the Technical Excellence and Audience Choice prizes at the Game Developers Conference.
Chrome by Techland (http://www.chromethegame.com/en/show.php)
Chrome is a futuristic multiplayer FPS (first person shooter) made up of 14 different missions, in an amazing variety of landscapes. It received a Duke's Choice Award from Sun Microsystems in 2004 for the most innovative product using Java technology.
Law and Order II by Legacy Interactive. (http://www.lawandordergame.com/index2.htm)
This is a detective game written in Java, Java 3D, and QuickTime for Java. The first Law and Order sold over 100,000 units.
Kingdom of Wars by Abandoned Castle Studios (http://www.abandonedcastle.com/)
This is a fantasy game set in the world of Jairon.
Alien Flux by Puppy Games (http://www.puppygames.net/info.php?game=Alien_Flux)
Alien Flux is an exciting arcade shoot-em-up.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Sun Microsystems Isn't Interested in Supporting Java Gaming
The games market isn't a traditional one for Sun, and it'll probably never have the depth of knowledge of a Sony or Nintendo. However, the last few years have demonstrated Sun's increasing commitment to gaming.
J2SE has strengthened its games support through successive versions: Version 1.3 improved its graphics and audio capabilities, and Version 1.4 introduced full-screen mode and page flipping in hardware. Faster I/O, memory mapping, and support for nonblock sockets, which is especially useful in client/server multiplayer games, also appeared first in 1.4. Version 5.0 has a decent nanosecond timer at last. Java extension libraries, such as Java 3D, the Java Media Framework (JMF), the Java Communications API, Jini, and JAXP (Java's peer-to-peer API) offer something to games programmers.
Sun started showing an interest in gaming back in 2001, with its announcement of the Java Game Profile, a collaboration with several other companies, including Sega and Sony, to develop a Java gaming API. The profile was perhaps too ambitious, and was abandoned at the end of 2003. However, it did produce three game-focused technologies: a Java binding for OpenGL called JOGL, a binding for OpenAL (a 3D audio library) called JOAL, and JInput.
Part of the 2001 initiative was the creation of the JavaGaming.org web site (http://www.javagaming.org), initially manned by volunteers. In 2003, the Game Technology Group was formed, and JavaGaming.org received a substantial makeover as part of the creation of the new java.net portal (http://www.java.net) aimed at the technical promotion of Java. Java.net hosts many discussion forums, user groups, projects, communities, and news. The communities include: Java Desktop, Java Education and Learning, Java Enterprise, and Java Games.
The Java Games community pages can be accessed through http://www.javagaming.org or http://community.java.net/games/. The site includes Java games forums, projects, news, weblogs, a wiki (http://wiki.java.net/bin/view/Games/WebHome
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 2: An Animation Framework
A core technology for a good game is an animation algorithm that produces reliably fast game play across various operating systems (e.g., flavors of Windows, Linux, and Macintosh), and in different kinds of Java programs (e.g., applets, windowed, and full-screen applications).
I distinguish between windowed and full-screen applications because J2SE 1.4 introduced full-screen exclusive mode(FSEM). It suspends the normal windowing environment and allows an application to access the underlying graphics hardware more directly. FSEM permits techniques such as page flipping and provides control over the screen's resolution and image depth. The principal aim of FSEM is to accelerate graphics-intensive applications, such as games.
The common ground between windowed and full-screen application is the game's animation algorithm, which is the subject of this chapter.
The algorithm is embedded in a JPanel subclass (called GamePanel), which acts as a canvas for drawing 2D graphics (e.g., lines, circles, text, images). The animation is managed by a thread, which ensures that it progresses at a consistent rate, as independent of the vagaries of the hardware and OS as possible. The rate is measured in terms of frames per second (FPS), where a frame corresponds to a single rendering of the application to the canvas.
GamePanel is gradually refined and expanded through the chapter, introducing the following notions:
  • The {update, render, sleep} animation loop
  • Starting and terminating an animation
  • Double buffering
  • User interaction
  • Active rendering
  • Animation control based on a user's requested FPS
  • The management of inaccuracies in the timer and sleep operations
  • Combining FPS and game state updates per second (UPS)
  • Game pausing and resumption
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Animation as a Threaded Canvas
A JPanel is employed as a drawing surface, and an animation loop is embedded inside a thread local to the panel. The loop consists of three stages: game update, rendering, and a short sleep.
The code in Example 2-1 shows the main elements of GamePanel, including the run() method containing the animation loop. As the chapter progresses, additional methods and global variables will be added to GamePanel, and some of the existing methods (especially run()) will be changed and extended.
Example 2-1. The GamePanel class (initial version)
public class GamePanel extends JPanel implements Runnable
{
  private static final int PWIDTH = 500;   // size of panel
  private static final int PHEIGHT = 400;

  private Thread animator;            // for the animation
  private volatile boolean running = false;    // stops the animation

  private volatile boolean gameOver = false;   // for game termination

  // more variables, explained later
  //       :

  public GamePanel()
  {    setBackground(Color.white);    // white background
    setPreferredSize( new Dimension(PWIDTH, PHEIGHT));

    // create game components
    // ...
  }  // end of GamePanel()


  public void addNotify()
  /* Wait for the JPanel to be added to the
     JFrame/JApplet before starting. */
  {
    super.addNotify();   // creates the peer
    startGame();         // start the thread
  }


  private void startGame()
  // initialise and start the thread
  {
    if (animator == null || !running) {
      animator = new Thread(this);
      animator.start();
    }
  } // end of startGame()

  public void stopGame()
  // called by the user to stop execution
  {  running = false;   }


  public void run()
  /* Repeatedly update, render, sleep */
  {
    running = true;
    while(running) {
      gameUpdate();   // game state is updated
      gameRender();   // render to a buffer
      repaint();      // paint with the buffer

      try {
        Thread.sleep(20);  // sleep a bit
      }
      catch(InterruptedException ex){}
    }
    System.exit(0);   // so enclosing JFrame/JApplet exits
  } // end of run()


  private void gameUpdate()
  { if (!gameOver)      // update game state ...
  }

  // more methods, explained later...

}  // end of GamePanel class
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Converting to Active Rendering
The current painting strategy is to call repaint() in run()'s animation loop:
    while(running) {
      gameUpdate();   // game state is updated
      gameRender();   // render to a buffer
      repaint();      // paint with the buffer

      try {
        Thread.sleep(20);  // sleep a bit
      }
      catch(InterruptedException ex){}
    }
Since a call to repaint() is only a request, it's difficult to know when the repaint has been completed. This means that the sleep time in the animation loop is little more than a guess; if the specified delay is too long, then the animation speed is impaired for no reason. If the delay is too short, then repaint requests may be queued by the JVM and skipped if the load becomes too large.
In fact, no single sleep time is satisfactory since the time taken to update and render a frame will vary depending on the activity taking place in the game. The sleep time must be calculated afresh each time round the loop after measuring the iteration's update and rendering periods. Unfortunately, the repaint() part of the rendering is done by the JVM and cannot be easily measured.
As a first step to dealing with these issues, I switch to active rendering, shown below as modifications to run():
    public void run()
    /* Repeatedly update, render, sleep */
    {
      running = true;
      while(running) {
        gameUpdate();   // game state is updated
        gameRender();   // render to a buffer
        paintScreen();  // draw buffer to screen

        try {
          Thread.sleep(20);  // sleep a bit
        }
        catch(InterruptedException ex){}
      }
      System.exit(0);
    } // end of run()


    private void paintScreen()
    // actively render the buffer image to the screen
    {
      Graphics g;
      try {
        g = this.getGraphics();  // get the panel's graphic context
        if ((g != null) && (dbImage != null))
          g.drawImage(dbImage, 0, 0, null);
        Toolkit.getDefaultToolkit().sync();  // sync the display on some systems
        g.dispose();
      }
      catch (Exception e)
      { System.out.println("Graphics context error: " + e);  }
    } // end of paintScreen()
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
FPS and Sleeping for Varying Times
A weakness of the animation loop is that its execution speed is unconstrained. On a slow machine, it may loop 20 times per second; the same code on a fast machine may loop 80 times, making the game progress four times faster and perhaps making it unplayable. The loop's execution speed should be about the same on all platforms.
A popular measure of how fast an animation progresses is frames per second (FPS). For GamePanel, a frame corresponds to a single pass through the update-render-sleep loop inside run(). Therefore, the desired 100 FPS imply that each iteration of the loop should take 1000/100 == 10 ms. This iteration time is stored in the period variable in GamePanel.
The use of active rendering makes it possible to time the update and render stages of each iteration. Subtracting this value from period gives the sleep time required to maintain the desired FPS. For instance, 100 FPS mean a period of 10 ms, and if the update/render steps take 6 ms, then sleep() should be called for 4 ms. Of course, this is different on each platform, so must be calculated at runtime.
The following modified run() method includes timing code and the sleep time calculation:
    public void run()
    /* Repeatedly: update, render, sleep so loop takes close
       to period ms */
    {
      long beforeTime, timeDiff, sleepTime;

      beforeTime = System.currentTimeMillis();

      running = true;
      while(running) {
        gameUpdate();
        gameRender();
        paintScreen();

        timeDiff = System.currentTimeMillis() - beforeTime;
        sleepTime = period - timeDiff;   // time left in this loop

        if (sleepTime <= 0)  // update/render took longer than period
          sleepTime = 5;    // sleep a bit anyway

        try {
          Thread.sleep(sleepTime);  // in ms
        }
        catch(InterruptedException ex){}

        beforeTime = System.currentTimeMillis();
      }

      System.exit(0);
    } // end of run()
timeDiff holds the execution time for the update and render steps, which becomes part of the sleep time calculation.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Sleeping Better
The animation loop in run() depends on a good timer and the accuracy of the sleep() call. The previous major section dealt with alternatives to currentTimeMillis(). In this section, I consider ways of improving the sleep() code in run(), so the required frame rate is consistently achieved.
The SleepAcc class measures sleep accuracy. Example 2-4 calls sleep() with increasingly small values and measures the actual sleep time using the Java 3D timer.
Example 2-4. Measuring sleep() accuracy
import java.text.DecimalFormat;
import com.sun.j3d.utils.timer.J3DTimer;

public class SleepAcc
{
  private static DecimalFormat df;

  public static void main(String args[])
  {
    df = new DecimalFormat("0.##");  // 2 dp

    // test various sleep values
    sleepTest(1000);
    sleepTest(500);
    sleepTest(200);
    sleepTest(100);
    sleepTest(50);
    sleepTest(20);
    sleepTest(10);
    sleepTest(5);
    sleepTest(1);
  } // end of main()


  private static void sleepTest(int delay)
  {
    long timeStart = J3DTimer.getValue();

    try {
      Thread.sleep(delay);
    }
    catch(InterruptedException e) {}

    double timeDiff =
       ((double)(J3DTimer.getValue() - timeStart))/(1000000L);
    double err = ((delay - timeDiff)/timeDiff) * 100;

    System.out.println("Slept: " + delay + " ms  J3D: " +
                          df.format(timeDiff) + " ms  err: " +
                          df.format(err) + " %" );
  }  // end of sleepTest()

} // end of SleepAcc class
The difference between the requested and actual sleep delay is negligible for times of 50 ms or more and gradually increases to a +/-10 to 20 percent error at 1 ms. A typical run is:
    D>java SleepAcc
    Slept: 1000 ms  J3D: 999.81 ms  err: 0.02 %
    Slept: 500 ms  J3D: 499.54 ms  err: 0.09 %
    Slept: 200 ms  J3D: 199.5 ms  err: 0.25 %
    Slept: 100 ms  J3D: 99.56 ms  err: 0.44 %
    Slept: 50 ms  J3D: 49.59 ms  err: 0.82 %
    Slept: 20 ms  J3D: 20.53 ms  err: -2.59 %
    Slept: 10 ms  J3D: 10.52 ms  err: -4.91 %
    Slept: 5 ms  J3D: 5.42 ms  err: -7.78 %
    Slept: 1 ms  J3D: 1.15 ms  err: -13.34 %
      :  // more lines until ctrl-C is typed
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
FPS and UPS
Apart from FPS, there is another useful measure of animation speed: UPS. The current animation loop carries out one update and one render in each iteration, but this correspondence isn't necessary. The loop could carry out two updates per each rendering, as illustrated by the following code fragment:
    public void run()
    // Repeatedly update, render, sleep
    { ...
      running = true;
      while(running) {
        gameUpdate();   // game state is updated
        gameUpdate();   // game state is updated again

        gameRender();   // render to a buffer
        paintScreen();  // paint with the buffer

        // sleep a bit
      }
      System.exit(0);
    } // end of run()
If the game offers 50 FPS (i.e., 50 iterations of the animation loop per second), then it is doing 100 updates per second.
This coding style causes the game to advance more quickly since the game state is changing twice as fast but at the cost of skipping the rendering of those extra states. However, this may not be noticeable, especially if the FPS value is 20 or higher.
One limitation on high FPS rates is the amount of time that the update and render steps require. Satisfying a period of 5 ms (1000/5 == 200 FPS) is impossible if these steps take more than 5 ms to accomplish. Most of this execution time is usually consumed by the rendering stage.
In this situation, the way to increase game speed is to increase the number of UPS. In programming terms, this translates into calling gameUpdate() more than once during each iteration. However, too many additional calls will cause the game to flicker, as too many successive states are not rendered. Each update adds to the execution time, which will further reduce the maximum achievable FPS value.
The new run() is:
    private static int MAX_FRAME_SKIPS = 5;
      // no. of frames that can be skipped in any one animation loop
      // i.e the games state is updated but not rendered


    public void run()
    /* Repeatedly update, render, sleep so loop takes close
       to period nsecs. Sleep inaccuracies are handled.
       The timing calculation use the Java 3D timer.

       Overruns in update/renders will cause extra updates
       to be carried out so UPS tild== requested FPS
    */
    {
      long beforeTime, afterTime, timeDiff, sleepTime;
      long overSleepTime = 0L;
      int noDelays = 0;
      
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Pausing and Resuming
Even with the most exciting game, there comes a time when the user wants to pause it (and resume later).
One largely discredited coding approach is to use Thread.suspend() and resume(). These methods are deprecated for a similar reason to Thread.stop();suspend() can cause an applet/application to suspend at any point in its execution. This can easily lead to deadlock if the thread is holding a resource since it will not be released until the thread resumes.
Instead, the Java documentation for the Thread class recommends using wait() and notify() to implement pause and resume functionality. The idea is to suspend the animation thread, but the event dispatcher thread will still respond to GUI activity. To implement this approach, I introduce an isPaused Boolean, which is set to true via pauseGame():
    // global variable
    private volatile boolean isPaused = false;

    public void pauseGame()
    { isPaused = true;   }


    public void run()
    // Repeatedly (possibly pause) update, render, sleep
    // This is not a good approach, and is shown for illustration only.
    { ...
      running = true;
      while(running) {
        try {
          if (isPaused) {
            synchronized(this) {
              while (isPaused && running)
                wait();
            }
          }
        } // of try block
        catch (InterruptedException e){}

        gameUpdate();   // game state is updated
        gameRender();   // render to a buffer
        paintScreen();  // paint with the buffer
   

        // sleep a bit
      }
      System.exit(0);
    } // end of run()
The isPaused flag is detected in run() and triggers a wait() call to suspend the animation thread. The flag must be volatile so run() is sure to see the change made by pauseGame() (otherwise the variable may be cached locally).
The thread is resumed by resumeGame() or stopGame(), both of which call notify(). These methods must be synchronized so the animation thread doesn't miss the notification and remain suspended indefinitely:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Other Animation Approaches
This chapter has been concerned with developing a threaded animation loop inside a JPanel. But other ways of implementing animation in Java exist, and I'll briefly consider two of them:
  • Using the Swing timer
  • Using the utility timer from java.util.timer
Both of them use a timer to trigger method calls at regular intervals. However, I'll present timing figures that show that the Swing timer doesn't have the necessary accuracy for my needs, while the utility timer is a possible alternative.
The Swing timer (in javax.swing.Timer) is used as the basis of animation examples in many Java textbooks.
The essential coding technique is to set a Timer object to "tick" every few milliseconds. Each tick sends an event to a specified ActionEvent listener, triggering a call to actionPerformed(). actionPerformed() calls repaint() to send a repaint request to the JVM. Eventually, repainting reaches the paintComponent() method for the JPanel, which redraws the animation canvas. These stages are shown in Figure 2-1, which represents the test code in SwingTimerTest.java.
Figure 2-1: Swing timer animation
The SwingTimerTest class uses the Swing timer to draw the current average FPS values repeatedly into a JPanel. The period for the timer is obtained from the requested FPS given on the command line. The average FPS are calculated every second, based on FPS values collected over the previous 10 seconds.
main() reads in the user's required FPS and converts them to a period. It creates a JFrame and puts the SwingTimerPanel inside it.
The SwingTimerTest() constructor creates the timer and sends its "ticks" to itself:
   new Timer(period, this).start();
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 3: Worms in Windows and Applets
In this chapter, I test the threaded animation loop of Chapter 2 inside a windowed application and an applet. To simplify comparisons between the approaches, the programs are all variants of the same WormChase game. In Chapter 4, I will continue the comparisons, concentrating on several kinds of full-screen applications.
Figure 3-1 shows the windowed WormChase application on the left and the applet version on the right.
Figure 3-1: WormChase in a JFrame and JApplet
The aim of the game is to click the cursor on the red head of the rapidly moving worm. If the player misses the worm's head, then a blue box is added to the canvas (unless the worm's black body was clicked upon).
The worm must go around the boxes in its path, so the boxes may make the worm easier to catch. When the worm moves off the top edge of the window it appears at the bottom, and vice versa. When it travels past the left or right edge, it appears at the opposite side. The worm gradually gets longer until it reaches a maximum length, which it maintains for the rest of the game.
When the game finishes, a score is displayed in the center of the window, calculated from the number of boxes used and the time taken to catch the worm. Fewer boxes and less time will produce a higher score. The current time and the number of boxes are displayed below the game canvas in two text fields.
This chapter and the next are concerned with several variants of WormChase, and a few issues apply to all the versions which need to be considered before we begin.
The main drawback of the animation loop in Chapter 2 is the need to install Java 3D so its timer is available. Consequently, two versions of the windowed WormChase application are investigated here: one using the Java 3D timer and the other using the System timer. A comparison of the two will show when the Java 3D timer is beneficial.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Preliminary Considerations
This chapter and the next are concerned with several variants of WormChase, and a few issues apply to all the versions which need to be considered before we begin.
The main drawback of the animation loop in Chapter 2 is the need to install Java 3D so its timer is available. Consequently, two versions of the windowed WormChase application are investigated here: one using the Java 3D timer and the other using the System timer. A comparison of the two will show when the Java 3D timer is beneficial.
As mentioned in the last chapter, programmers using J2SE 5.0 may choose to do a global search and replace on the Java 3D timer version of WormChase, changing every J3DTimer.getValue() call to System.nanoTime().
All the WormChase versions in this chapter and the next use the same game-specific classes (i.e., Worm and Obstacles, shown throughout this chapter). They employ a similar WormPanel class, which corresponds to the GamePanel animation class in Chapter 2.
The main differences between the programs lie in their top-level classes. For example, in this chapter, the windowed application uses a subclass of JFrame while the applet utilizes JApplet. This requires changes to how game pausing and resumption are triggered, and the way of specifying the required FPS.
Testing is done via the gathering of statistics using a version of the reportStats() method detailed in the section "Swing Timer Animation" in Chapter 2. The main change to that method is that the average UPS are calculated alongside the average FPS. The overall aim of the testing is to see if the animation loop can deliver 80 to 85 FPS. Failing this, the programs should produce 80 to 85 updates per second without an excessive number of frames being skipped.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Class Diagrams for the WormChase Application
Figure 3-2 shows the class diagrams for the WormChase application. The class names and public methods are shown.
Figure 3-2: Class diagrams for the WormChase application
The code for this version of WormChase is in the directory Worm/WormP/.
WormChase is the top-level JFrame, managing the GUI, and processing window events. WormPanel is the game panel holding the threaded animation loop.
The Worm class maintains the data structures and methods for the on-screen worm. The Obstacles class handles the blue boxes. Worm and Obstacles have their own draw() method, which is called by WormPanel to render the worm and boxes.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
The Worm-Chasing Application
Figure 3-3 shows a class diagram for WormChase, including all its variables and methods.
Figure 3-3: WormChase in detail
The main() function in WormChase reads the requested FPS from the command line, converting it to a delay in nanoseconds, which is passed to the WormChase() constructor:
    public static void main(String args[])
    {
      int fps = DEFAULT_FPS;
      if (args.length != 0)
        fps = Integer.parseInt(args[0]);

      long period = (long) 1000.0/fps;
      System.out.println("fps: " + fps + "; period: " +period+ " ms");

      new WormChase(period*1000000L);    // ms --> nanosecs
    }
The WormChase constructor creates the WormPanel canvas, as well as two text fields for displaying the number of boxes added to the scene (jtfBox) and the current time (jtfTime). These text fields can be updated via two public methods:
    public void setBoxNumber(int no)
    {  jtfBox.setText("Boxes used: " + no);  }

    public void setTimeSpent(long t)
    {  jtfTime.setText("Time Spent: " + t + " secs"); }
setBoxNumber() is called from the Obstacles object when a new box (obstacle) is created. setTimeSpent() is called from WormPanel.
The pausing, resumption, and termination of the game are managed through window listener methods (WormChase implements WindowListener). Pausing is triggered by window deactivation or iconification; the application resumes when the window is activated or de-iconified, and the clicking of the window close box causes termination:
    public void windowActivated(WindowEvent e)
    { wp.resumeGame();  }

    public void windowDeactivated(WindowEvent e)
    {  wp.pauseGame();  }

    public void windowDeiconified(WindowEvent e)
    {  wp.resumeGame();  }

    public void windowIconified(WindowEvent e)
    {  wp.pauseGame(); }

    public void windowClosing(WindowEvent e)
   {  wp.stopGame();  }
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
The Game Panel
The WormPanel class is similar to the GamePanel class developed in Chapter 2, with some additional methods for drawing the game scene. WormPanel contains an extended version of the reportStats() method used for timing the Swing and utility timers in Chapter 2, called printStats(). Its principal extension is to report the average UPS (updates per second) in addition to the average FPS.
A class diagram showing all the WormPanel methods is given in Figure 3-4.
The WormPanel constructor sets up the game components and initializes timing elements:
    public WormPanel(WormChase wc, long period)
    {
      wcTop = wc;
      this.period = period;

      setBackground(Color.white);
      setPreferredSize( new Dimension(PWIDTH, PHEIGHT));

      setFocusable(true);
      requestFocus();    // now has focus, so receives key events
      readyForTermination();

      // create game components
      obs = new Obstacles(wcTop);
      fred = new Worm(PWIDTH, PHEIGHT, obs);

      addMouseListener( new MouseAdapter() {
        public void mousePressed(MouseEvent e)
Figure 3-4: WormPanel methods in detail
        { testPress(e.getX(), e.getY()); }
      });

      // set up message font
      font = new Font("SansSerif", Font.BOLD, 24);
      metrics = this.getFontMetrics(font);

      // initialise timing elements
      fpsStore = new double[NUM_FPS];
      upsStore = new double[NUM_FPS];
      for (int i=0; i < NUM_FPS; i++) {
        fpsStore[i] = 0.0;
        upsStore[i] = 0.0;
      }
    }  // end of WormPanel()
The time period intended for each frame (in nanoseconds) is passed to WormPanel from WormChase and stored in a global variable. readyForTermination() is the same as in Chapter 2: a KeyListener monitors the input for termination characters (e.g., Ctrl-C), then sets the running Boolean to false.
The message font is used to report the score when the game ends. fpsStore[] and
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Storing Worm Information
The Worm class stores coordinate information about the worm in a circular buffer. It includes testing methods for checking if the player has clicked near the worm's head or body and includes methods for moving and drawing the worm.
The issues which make things more complicated include:
  • Having the worm grow in length up to a maximum size
  • Regulating the worm's movements to be semi-random so that it mostly moves in a forward direction
  • Getting the worm to go around obstacles in its path
The worm is grown by storing a series of Point objects in a cells[] array. Each point represents the location of one of the black circles of the worm's body (and the red circle for its head). As the worm grows, more points are added to the array until it is full; the worm's maximum extent is equivalent to the array's size.
Movement of the full-size worm is achieved by creating a new head circle at its front and removing the tail circle (if necessary). This removal frees up a space in the cells[] array where the point for the new head can be stored.
The growing and movement phases are illustrated by Figure 3-6, which shows how the cells[] array is gradually filled and then reused. The two indices, headPosn and tailPosn, make it simple to modify the head and tail of the worm, and nPoints records the length of the worm.
Figure 3-6: Worm data structures during growth and movement
The numbered black dots (and red dot) represent the Point objects which store the (x, y) coordinates of the worm's parts. The numbers are included in the figure to indicate the order in which the array is filled and over-written; they are not part of the actual data structure, which is defined like so:
    private static final int MAXPOINTS = 40;

    private Point cells[];
    private int nPoints;
    private int tailPosn, headPosn;   // tail and head of buffer
    // additional variables already defined

    cells = new Point[MAXPOINTS];   // initialise buffer
    nPoints = 0;
    headPosn = -1;  tailPosn = -1;
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Worm Obstacles
The Obstacles object maintains an array of Rectangle objects called boxes. Each object contains the top-left hand coordinate of a box and the length of its square sides.
The public methods in the Obstacles class are synchronized since the event thread of the game could add a box to the obstacles list (via a call to add()) while the animation thread is examining or drawing the list.
add() is defined as
    synchronized public void add(int x, int y)
    {
      boxes.add( new Rectangle(x,y, BOX_LENGTH, BOX_LENGTH));
      wcTop.setBoxNumber( boxes.size() );   // report new no. of boxes
    }
The method updates the boxes text field at the top-level of the game by calling setBoxNumber().
WormPanel delegates the task of drawing the obstacles to the Obstacles object, by calling draw():
    synchronized public void draw(Graphics g)
    // draw a series of blue boxes
    {
      Rectangle box;
      g.setColor(Color.blue);
      for(int i=0; i < boxes.size(); i++) {
        box = (Rectangle) boxes.get(i);
        g.fillRect( box.x, box.y, box.width, box.height);
      }
    }  // end of draw()
Worm communicates with Obstacles to determine if its new head (a Point object, p) intersects with any of the boxes:
    synchronized public boolean hits(Point p, int size)
    {
      Rectangle r = new Rectangle(p.x, p.y, size, size);
      Rectangle box;
      for(int i=0; i < boxes.size(); i++) {
        box = (Rectangle) boxes.get(i);
        if (box.intersects(r))
          return true;
      }
      return false;
    }  // end of hits()
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Application Timing Results
This version of WormChase is a windowed application, with an animation loop driven by the Java 3D timer. Can it support frame rates of 80 to 85 FPS?
I consider the average UPS, which gives an indication of the speed of the game. Table 3-1 shows the FPS and UPS figures for different requested FPS amounts, on different versions of Windows.
Table 3-1: Average FPS/UPSs for the windowed WormChase using the Java 3D timer
Requested FPS
20
50
80
100
Windows 98
20/20
48/50
81/83
96/100
Windows 2000
20/20
43/50
59/83
58/100
Windows XP
20/20
50/50
83/83
100/100
Each test was run three times on a lightly loaded machine, executing for a few minutes.
The numbers are for the machines hosting Windows 98 and XP, but the frame rates on the Windows 2000 machine plateaus at about 60. This behavior is probably due to the extreme age of the machine: a Pentium 2 with a paltry 64 MB of RAM. On a more modern CPU, the frame rates are similar to the XP row of Table 3-1.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
WormChase as an Applet
Content preview·