CSCI A202 - Introduction to Programming (II)

Lecture 29: Animation and Applets

Suppose you have the following frames:

To produce an animation means to show these frames in rhytmic (and perhaps cyclic) succession.

Of course, getting the frames in the first place requires an artist.

Artists are expensive but they do great work.

These frames taken from Hooked on Java CD (Dec. 1995, van Hoff, Shaio, Starbuck).

Now that we have a nice set of frames let's look at animations.

The following program is due to Arthur van Hoff:

import java.awt.*;
import java.applet.Applet;
import java.awt.event.*;

public class AnimatorApplet extends    Applet 
                            implements Runnable, MouseListener { 
  int     frameNumber = -1; 
  int     delay; 
  Thread  animatorThread; 
  boolean frozen = false; 

  public void init() {
    String str; 
    int fps = 10; 
    // How many milliseconds between frames? 
    str = getParameter("fps"); 
    try {
      if (str != null) {
        fps = Integer.parseInt(str); 
      } 
    } catch (Exception e) {
      // nothing done
    } 
    delay = (fps > 0) ? (1000 / fps) : 100; 
    addMouseListener(this); 
  }

  public void start() {
    if (frozen) { 
      // Do nothing. The user has requested
      // that we stop changing the image. 
    } else { 
      // Start animating!
      if (animatorThread == null) { 
        animatorThread = new Thread(this); 
      } 
      animatorThread.start(); 
    } 
  }

  public void stop() {
    // Stop the animating thread. 
    animatorThread = null; 
  } 

  public void mousePressed(MouseEvent e) {
    if (frozen) {
      frozen = false; 
      start(); 
    } else {
      frozen = true; 
      stop(); 
    } 
  } 

  public void mouseReleased(MouseEvent e) { } 
  public void mouseClicked (MouseEvent e) { } 
  public void mouseEntered (MouseEvent e) { } 
  public void mouseExited  (MouseEvent e) { } 
  public void mouseDragged (MouseEvent e) { } 
  public void mouseMoved   (MouseEvent e) { } 

  public void run() {
    // Just to be nice, lower this thread's priority so
    // it can't interfere with other processing going on. 
    Thread.currentThread().setPriority(Thread.MIN_PRIORITY); 

    // Remember the starting time.
    long startTime = System.currentTimeMillis(); 

    // This is the animation loop. 
    while (Thread.currentThread() == animatorThread) {
      // Advance the animation frame. 
      frameNumber++; 
 
      // Display it. 
      repaint(); 

      try {
        startTime += delay; 
        Thread.sleep(Math.max(0, startTime - System.currentTimeMillis())); 
      } catch (InterruptedException e) {
        break; 
      } 
    }
  } 

  public void paint(Graphics g) {
    g.drawString("Frame " + frameNumber, 0, 30); 
  } 
}
Here it is in action.

Having the frames and an animation template let's put things together.

import java.awt.*;
import java.applet.Applet;
import java.awt.event.*;

public class SecondApplet   extends    Applet 
                            implements Runnable, MouseListener { 
  int     frameNumber = -1; 
  int     delay; 
  Thread  animatorThread; 
  boolean frozen = false; 
  String[] imagesToFetch; 
  Image[] fetchedImages;
  int fetched; 

  public void init() {
    String str; 
    int fps = 10; 
    // How many milliseconds between frames? 
    str = getParameter("fps"); 
    try {
      if (str != null) {
        fps = Integer.parseInt(str); 
      } 
    } catch (Exception e) {
      // nothing done
    } 
    delay = (fps > 0) ? (1000 / fps) : 100; 
    addMouseListener(this); 

    int i = 0; 
    while (getParameter("image" + i) != null) { i++; } 
 
    imagesToFetch = new String[i]; 
    fetchedImages = new Image[i]; 

    for (i=0; i < imagesToFetch.length; i++) 
      imagesToFetch[i] = getParameter("image" + i); 

    while (fetched < imagesToFetch.length) {
      fetchedImages[fetched] = getImage(getDocumentBase(), imagesToFetch[fetched]); 
      while (! prepareImage(fetchedImages[fetched], this)) { 
        
      } 
      fetched++; 
    } 

  }

  public void start() {
    if (frozen) { 
      // Do nothing. The user has requested
      // that we stop changing the image. 
    } else { 
      // Start animating!
      if (animatorThread == null) { 
        animatorThread = new Thread(this); 
      } 
      animatorThread.start(); 
    } 
  }

  public void stop() {
    // Stop the animating thread. 
    animatorThread = null; 
  } 

  public void mousePressed(MouseEvent e) {
    if (frozen) {
      frozen = false; 
      start(); 
    } else {
      frozen = true; 
      stop(); 
    } 
  } 

  public void mouseReleased(MouseEvent e) { } 
  public void mouseClicked (MouseEvent e) { } 
  public void mouseEntered (MouseEvent e) { } 
  public void mouseExited  (MouseEvent e) { } 
  public void mouseDragged (MouseEvent e) { } 
  public void mouseMoved   (MouseEvent e) { } 

  public void run() {
    // Just to be nice, lower this thread's priority so
    // it can't interfere with other processing going on. 
    Thread.currentThread().setPriority(Thread.MIN_PRIORITY); 

    // Remember the starting time.
    long startTime = System.currentTimeMillis(); 

    // This is the animation loop. 
    while (Thread.currentThread() == animatorThread) {
      // Advance the animation frame. 
      frameNumber++; 
 
      // Display it. 
      repaint(); 

      try {
        startTime += delay; 
        Thread.sleep(Math.max(0, startTime - System.currentTimeMillis())); 
      } catch (InterruptedException e) {
        break; 
      } 
    }
  } 

  public void paint(Graphics g) {
    int frames = imagesToFetch.length; 
    if (frames > 0) { frameNumber = frameNumber % frames; } 
    // g.drawString("Frame " + frameNumber, 0, 30); 
    // g.drawString(fetched + " F: " + imagesToFetch[frameNumber], 0, 30); 
    g.drawImage(fetchedImages[frameNumber], 0, 0, this); 
  } 
}
And the result should be a waving Duke.