Chapter 25

Using Animation


CONTENTS


This chapter completes Part IV, "Window Programming," by showing you how to include animation sequences in your window programs. It identifies the basic elements of implementing an animation and then describes approaches to improving the quality of an animation's display by selectively repainting parts of a window and using the MediaTracker class to support the loading of the images used in an animation. When you finish this chapter, you'll be able to include animation in your window programs.

Animation Basics

While including animation sequences in your Java programs may at first appear to be complicated, it is, in fact, rather easy once you learn the basics. Animations are nothing more than the rapid display of still images such that the pattern of image display causes the appearance of movement for the objects contained in the image. To create an animation, you need to produce the sequence of objects that are to be displayed and then write a Java program that will display that sequence at a particular display rate.

For me, the hardest part of developing an animation is producing the images that are to be displayed. This part requires drawing skills and is completely separate from Java programming. Don't fret if you are unable to easily draw these animation sequences. Chances are that you're better at it than I am. The important point of this chapter is to learn how to display in the form of an animation the sequences that you do come up with.

Many animations display their image sequences in a looping fashion. A looping animation gives the appearance that it is much longer than it actually is, and it can run indefinitely. Looping animations also require fewer image frames. If your animation displays 10 to 20 image frames per second and you want it to run for a minute, then you will need 600 to 1200 images. That's a lot of work for a one-minute animation. It is much easier to develop a small but varied looping animation and have it loop several times during the course of a minute.

The major parameter of an animation, besides the type and quality of the images it displays, is the number of image frames that it displays per second. This is typically a fixed number between 5 and 25. The more frames per second that are displayed, the smoother the animation appears to be. The frames-per-second parameter translates into a frame delay parameter that is used to determine how long a program should wait before it displays the next image frame. This is typically measured in milliseconds. For example, frames-per-second rates of 5, 10, and 20 translate into frame delays of 200, 100, and 50 milliseconds.

A common approach to implementing an animation is to create a program thread that runs in an infinite loop and displays the frames of the animation sequence one at a time, waiting frame-delay milliseconds between each frame's display.

A Simple Animation

In order to get a better understanding of the basics of the animation process, you can develop a simple, character-based animation. The source code of the SimpleAnimationApp program is shown in Listing 25.1.


Listing 25.1. The source code of the SimpleAnimationApp program.

import java.awt.*;
import jdg.ch20.*;

public class SimpleAnimationApp extends Frame implements Runnable {
  Thread animation;
  int frameDelay = 100;
  String frames[] = {"*","**","***","****","*****","****","***","**","*"};
  int numFrames = frames.length;
  int currentFrame = 0;
  long lastDisplay = 0;
  String menuItems[][] = {{"File","Exit"}};
  MyMenuBar menuBar = new MyMenuBar(menuItems);
  int screenWidth = 200;
  int screenHeight = 200;
  public static void main(String args[]) {
    SimpleAnimationApp app = new SimpleAnimationApp();
  }
  public SimpleAnimationApp() {
    super("Simple Animation");
    setup();
    pack();
    resize(screenWidth,screenHeight);
    show();
    animation = new Thread(this);
    animation.start();
  }
  void setup() {
    setMenuBar(menuBar);
    setFont(new Font("default",Font.BOLD,18));
  }
  public void paint(Graphics g) {
    g.drawString(frames[currentFrame],60,60);
  }
  public void run() {
    do {
     long time = System.currentTimeMillis();
     if(time - lastDisplay > frameDelay) {
       repaint();
       try {
          Thread.sleep(frameDelay);
       }catch(InterruptedException ex){
       }
       ++currentFrame;
       currentFrame %= numFrames;
     lastDisplay = time;
     }
   } while (true);
  }
  public boolean handleEvent(Event event) {
   if(event.id==Event.WINDOW_DESTROY){
     System.exit(0);
     return true;
  }else if(event.id==Event.ACTION_EVENT){
     if(event.target instanceof MenuItem){
       String arg = (String) event.arg;
     if("Exit".equals(arg)) {
       System.exit(0);
       return true;
     }
    }
   }
   return false;
  }
 }


Compile and run SimpleAnimationApp. Your program's display should look like the one shown in Figure 25.1.

Figure 25.1 : A simple animation.

A string of asterisks is modulated to give the appearance of movement.

While this short animation is by no means in line for any awards, it does illustrate all the basic elements of more complex and entertaining animations.

The SimpleAnimationApp class declares the animation thread, the frameDelay variable, the array of frames[] used to implement the animation's display, the numFrames variable, the currentFrame variable, the time of the lastDisplay of a frame, and the standard menu bar and window size variables.

The setup of the SimpleAnimationApp program is fairly standard, with the exception of the creation of the animation thread at the end of the class constructor and the invocation of the animation thread's start() method.

The paint() method contains a single statement that is used to display a string of asterisks on the console window.

The run() method implements the animation loop. It checks the current system time and the time of the last image display to see if it is time to display a new frame. It uses the currentTimeMillis() method of the System class to read the current time in milliseconds. If it is time to display another frame, the run() method invokes the repaint() method to display the current frame and then tries to sleep for frameDelay milliseconds. It updates the currentFrame using modular arithmetic and changes the time of lastDisplay.

The handleEvent() method performs the standard window event handling covered in Chapters 18 through 24.

A Graphics Animation

Because the SimpleAnimationApp program provides all the basic elements required of an animation, we can easily modify the animation to support graphics. Figures 25.2 through 25.5 provide four stick figures I drew using the Windows Paint program. These crude figures can be used to create an animation of a stick figure that attempts to fly.

Figure 25.2 : stickman1.gif.

Figure 25.3 : stickman2.gif.

Figure 25.4 : stickman3.gif.

Figure 25.5 : stickman4.gif.

You may easily substitute your own figures for the ones used in this example.

The source code of the GraphicAnimationApp program is shown in Listing 25.2.


Listing 25.2. The source code of the GraphicAnimationApp program.

import java.awt.*;
import jdg.ch20.*;

public class GraphicAnimationApp extends Frame implements Runnable {
  Thread animation;
  int frameDelay = 100;
  Image frames[];
  int numFrames;
  int currentFrame = 0;
  long lastDisplay = 0;
  String menuItems[][] = {{"File","Exit"}};
  MyMenuBar menuBar = new MyMenuBar(menuItems);
  int screenWidth = 400;
  int screenHeight = 400;
  public static void main(String args[]) {
    GraphicAnimationApp app = new GraphicAnimationApp();
  }
  public GraphicAnimationApp() {
   super("Graphic Animation");
   setup();
   pack();
   resize(screenWidth,screenHeight);
   show();
   animation = new Thread(this);
   animation.start();
 }
 void setup() {
   setMenuBar(menuBar);
   setFont(new Font("default",Font.BOLD,18));
   Toolkit toolkit = getToolkit();
   frames = new Image[4];
   frames[0] = toolkit.getImage("stickman1.gif");
   frames[1] = toolkit.getImage("stickman2.gif");
   frames[2] = toolkit.getImage("stickman3.gif");
   frames[3] = toolkit.getImage("stickman4.gif");
   numFrames = frames.length;
 }
 public void paint(Graphics g) {
   g.drawImage(frames[currentFrame],10,10,this);
 }
 public void run() {
  do {
    long time = System.currentTimeMillis();
    if(time - lastDisplay > frameDelay) {
      repaint();
      try {
       Thread.sleep(frameDelay);
      }catch(InterruptedException ex){
      }
      ++currentFrame;
      currentFrame %= numFrames;
      lastDisplay = time;
     }
   } while (true);
 }
 public boolean handleEvent(Event event) {
  if(event.id==Event.WINDOW_DESTROY){
    System.exit(0);
    return true;
  }else if(event.id==Event.ACTION_EVENT){
    if(event.target instanceof MenuItem){
      String arg = (String) event.arg;
    if("Exit".equals(arg)) {
      System.exit(0);
      return true;
     }
    }
   }
   return false;
  }
 }


When you run GraphicAnimationApp, your display should look like the one shown in Figure 25.6.

Figure 25.6 : The GraphicAnimationApp program display.

Unless you have a really fast computer and video card, your program display probably has some very noticeable flickering. Don't worry about that problem now. You'll learn ways to improve the quality of an animation's display in the following section. For now, just focus on how we modified the SimpleAnimationApp program to support graphics-based animation.

The GraphicAnimationApp program is very similar to the SimpleAnimationApp program. These are the differences between the two programs:

These simple changes were all that was needed to convert the program from a simple text-based animation to a graphics-based animation.

Improving Animation Display Qualities

The GraphicAnimationApp program has some serious deficiencies in the way that it displays the animation images. The first and probably the most noticeable problem is that it tries to start displaying the images before they are completely loaded. This is an easy problem to solve using the MediaTracker class.

The MediaTracker class provides the capability to manage the loading of image files. You use the addImage() method to add an image to the list of images being tracked. After adding an image to a MediaTracker object, you can then check on the image or all images managed by MediaTracker object using the access methods provided by the MediaTracker class.

The other major problem with the animation's display is that the entire screen is repainted with each new frame, which causes a significant amount of flickering. This image flickering can be mitigated by limiting the area of the window that is updated with each new image. The repaint() and update() methods of the component class provide this capability.

You are already familiar with limited screen repainting from using the repaint() method in Chapter 23, "The Canvas." The update() method provides the capability to update a Graphics object without first clearing the current image. This allows successive images to be displayed as marginal increments to the currently displayed image.

Another option to improving an animation's display quality is to change the frame delay. By decreasing the number of frames per second being displayed, you are able to lower the rate at which flickering occurs. However, you do this at the expense of the overall quality of your animation because higher frame-display rates tend to smooth out any gaps between successive images.

An Updated Graphics Animation

The GraphicUpdateApp program shows how to use the MediaTracker class, together with limited repainting and frame-delay adjustments, to improve the quality of the GraphicAnimationApp program. Its source code is shown in Listing 25.3.


Listing 25.3. The source code of the GraphicUpdateApp program.

import java.awt.*;
import jdg.ch20.*;

public class GraphicUpdateApp extends Frame implements Runnable {
  Thread animation;
  int frameDelay = 200;
  Image frames[];
  int numFrames;
  int currentFrame = 0;
  long lastDisplay = 0;
  boolean fullDisplay = false;
  MediaTracker tracker;
  String menuItems[][] = {{"File","Exit"}};
  MyMenuBar menuBar = new MyMenuBar(menuItems);
  int screenWidth = 400;
  int screenHeight = 400;
  public static void main(String args[]) {
    GraphicUpdateApp app = new GraphicUpdateApp();
  }
  public GraphicUpdateApp() {
    super("Updated Graphic Animation");
    setup();
    pack();
    resize(screenWidth,screenHeight);
    show();
    animation = new Thread(this);
    animation.start();
  }
  void setup() {
    setMenuBar(menuBar);
    setFont(new Font("default",Font.BOLD,18));
    Toolkit toolkit = getToolkit();
    frames = new Image[4];
    frames[0] = toolkit.getImage("stickman1.gif");
    frames[1] = toolkit.getImage("stickman2.gif");
    frames[2] = toolkit.getImage("stickman3.gif");
    frames[3] = toolkit.getImage("stickman4.gif");
    numFrames = frames.length;
    tracker = new MediaTracker(this);
    for(int i=0;i<numFrames;++i) tracker.addImage(frames[i],i);
  }
  public void paint(Graphics g) {
   if(allLoaded())
      g.drawImage(frames[currentFrame],10,10,this);
   else{
     String stars = "*";
     for(int i=0;i<currentFrame;++i) stars += "*";
     g.drawString(stars,60,60);
   }
  }
  boolean allLoaded() {
    for(int i=0;i<numFrames;++i) {
     if(tracker.statusID(i,true) != MediaTracker.COMPLETE) return false;
    }
    return true;
  }
  public void run() {
    do {
     long time = System.currentTimeMillis();
     if(time - lastDisplay > frameDelay) {
      if(allLoaded()) {
       if(fullDisplay) repaint(10,90,160,78);
        else{
          fullDisplay = true;
          repaint();
        }
      }else repaint();
      try {
       Thread.sleep(frameDelay);
      }catch(InterruptedException ex){
      }
      ++currentFrame;
      currentFrame %= numFrames;
      lastDisplay = time;
     }
   } while (true);
  }
  public boolean handleEvent(Event event) {
   if(event.id==Event.WINDOW_DESTROY){
     System.exit(0);
     return true;
  }else if(event.id==Event.ACTION_EVENT){
    if(event.target instanceof MenuItem){
     String arg = (String) event.arg;
      if("Exit".equals(arg)) {
       System.exit(0);
       return true;
      }
     }
    }
    return false;
   }
  }


When you run GraphicUpdateApp, it displays an animated string of asterisks while the image files are being loaded. After that, it will immediately display the image animation. This reduces the unsightly flickering caused when an image is displayed while it is being loaded.

Notice how GraphicUpdateApp implements the limited-area repainting. You can run your mouse over the image display to determine the boundaries of the repaint area.

You should also notice that GraphicUpdateApp displays images at a slower rate. The frame- delay rate was increased from 100 microseconds to 200 microseconds, decreasing the frame display rate by a factor of 2.

The changes made to GraphicAnimationApp by GraphicUpdateApp consist of the declaration of the fullDisplay and tracker variables and modifications to the setup(), paint(), and run() methods. In addition, the allLoaded() method was created:

Summary

This chapter shows how to include animation sequences in your window programs. It identifies the basic elements of implementing an animation and describes approaches to improving the quality of an animation's display. It shows you how to selectively repaint parts of a window and how to use the MediaTracker class to support the loading of the images used in an animation. Chapter 26, "Client Programs," begins Part V, "Network Programming."