Previous | Next | Trail Map | Creating a User Interface | Working with Graphics


Creating the Animation Loop

Every program that performs animation by drawing at regular intervals needs an animation loop. Generally, this loop should be in its own thread. It should never be in the paint() or update() method, since that would take over the main AWT thread, which is in charge of all drawing and event handling.

This page provides two templates for performing animation, one for applets and another for applications. The applet version is running just below. You can click on it to stop the animation. Click again to restart it.


Your browser can't run 1.0 Java applets, so here is a snapshot of what you'd see:


The animation the template performs is a bit boring: it simply displays the current frame number, using a default rate of 10 frames per second. The next few lessons build on this example, showing you how to animate primitive graphics and images.

Here is the code for the applet animation template. Here is the code for the equivalent application animation template. The rest of this page explains the templates' code. Here is a summary of what both templates do:

public class AnimatorClass extends AComponentClass implements Runnable {

    //In initialization code: 
        //From user-specified frames-per-second value, determine
	//how long to delay between frames.

    //In a method that does nothing but start the animation:
        //Create and start the animating thread.

    //In a method that does nothing but stop the animation:
        //Stop the animating thread.

    public boolean mouseDown(Event e, int x, int y) {
        if (/* animation is currently frozen */) {
            //Call the method that starts the animation.
        } else {
            //Call the method that stops the animation.
        }
    }

    public void run() {
        //Lower this thread's priority so it can't interfere
	//with other processing going on.

        //Remember the starting time.

        //Here's the animation loop:
        while (/* animation thread is still running */) {
            //Advance the animation frame.
            //Display it.
            //Delay depending on how far we are behind.
        }
    }

    public void paint(Graphics g) {
        //Draw the current frame of animation.
    }
}

Initializing Instance Variables

The animation templates use four instance variables. The first instance variable (frameNumber) represents the current frame. It's initialized to -1, even though the first frame number is 0. The reason: the frame number is incremented at the start of the animation loop, before any frames are painted. Thus, the first frame to be painted is frame 0.

The second instance variable (delay) is the number of milliseconds between frames. It's initialized using a frames per second number provided by the user. If the user provides no valid number, then the templates default to 10 frames per second. The following code converts frames per second into the number of milliseconds between frames:

delay = (fps > 0) ? (1000 / fps) : 100;

The ? : notation in the previous code snippet is shorthand for if else. If the user provides a number of frames per second greater than 0, then the delay is 1000 milliseconds divided by the number of frames per second. Otherwise, the delay between frames is 100 milliseconds.

The third instance variable (animatorThread) is a Thread object, representing the thread in which the animation loop runs. If you're not familiar with threads, see the Threads of Control(in the Writing Java Programs trail) lesson for an overview of their use.

The fourth instance variable (frozen) is a boolean value that's initialized to false. The templates set it to true to indicate that the user has requested that the animation stop. You'll see more about this later in this section.

The Animation Loop

The animation loop (the while loop in the animation thread) does the following, over and over again:
  1. Advances the frame number.
  2. Calls the repaint() method to request that the current frame of animation be drawn.
  3. Sleeps for up to delay milliseconds (more on this later).

Here's the code that performs these tasks:

while (/* animation thread is still running */) {
    //Advance the animation frame.
    frameNumber++;

    //Display it.
    repaint();

    ...//Delay depending on how far we are behind.
}

Ensuring a Constant Frame Rate

The most obvious way to implement the sleep in the animation loop is to sleep for delay milliseconds. This can cause the thread to sleep too long, however, since you lose a certain amount of time just by executing the animation loop.

The solution to this problem is to remember when the animation loop starts, add delay milliseconds to arrive at a wakeup time, and then sleep until the wakeup time. Here's the code that implements this:

long startTime = System.currentTimeMillis();
while (/* animation thread is still running */) {
    ...//Advance the animation frame and display it.
    try {
        startTime += delay;
        Thread.sleep(Math.max(0,
                              startTime-System.currentTimeMillis()));
    } catch (InterruptedException e) {
        break;
    }
}

Behaving Politely

Two more features of the animation templates belong in the category of polite behavior.

The first feature is allowing the user to explicitly stop (and restart) the animation, while the applet or application is still visible. Animation can be quite distracting, and it's a good idea to give the user the power to stop the animation so that the user can concentrate on something else. This feature is implemented by overriding the mouseDown() method so that it stops or starts the animation thread, depending on the thread's current state. Here's the code that implements this:

...//In initialization code:
boolean frozen = false;

...//In the method that starts the animation thread:
    if (frozen) {
        //Do nothing.  The user has requested that we
        //stop changing the image.
    } else {
        //Start animating!
       ...//Create and start the animating thread.
    }
}

. . .

public boolean mouseDown(Event e, int x, int y) {
    if (frozen) {
        frozen = false;
        //Call the method that stops the animation.
    } else {
        frozen = true;
        //Call the method that stops the animation.
    }
    return true;
}

The second feature is suspending the animation whenever the applet or application is known not to be visible. For the applet animation template, this is achieved by implementing the Applet stop() and start() methods. For the application animation template, this is achieved by implementing an event handler for the WINDOW_ICONIFY and WINDOW_DEICONIFY events. In both templates, if the user hasn't frozen the animation, then when the program detects that the animation isn't visible, it tells the animation thread to stop. When the user revisits the animation, the program restarts the animation thread, unless the user has explicitly requested that the animation be stopped.

You might be wondering why the frame number is incremented at the beginning of the loop, rather than at the end. The reason has to do with what happens when the user freezes the animation, then leaves it, and then revisits it. When the user freezes the animation, the animation loop completes before exiting. If the frame number were incremented at the bottom of the loop, instead of at the top of the loop, then the frame number when the loop exits would be one more than the number of the frame that's being displayed. When the user revisited the animation, the animation would be frozen on a different frame than the one the user left. This could be disconcerting and, if the user stopped the animation to look at a particular frame, annoying.


Previous | Next | Trail Map | Creating a User Interface | Working with Graphics