CS 4773 Object Oriented Systems
A Simple Animation

Code for the programs can be found in
/usr/local/courses/cs4773/spring2000/examples/set5


Previous Topic: Threads

Introduction
A Simple Animation
Adding a Scrollbar
Adding Buffering
An External Animation Thread
Animation Using PingPongThread
Applet Summary

Next Topic: Java Threads and Synchronization


Introduction

The idea of (sprite) animation is simple.
  1. Draw the object in a position.
  2. Wait a while.
  3. Draw the object in a new position.
  4. Go back to 2.

A Simple Animation

/* < Applet code = MoveIt
     width = 300 height = 300 >
   < /Applet >
*/

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

public class MoveIt extends Applet implements ActionListener, Runnable {

   Button startButton;
   int initX = 50;
   int initY = 50;
   int rectWidth = 50;
   int rectHeight = 20;
   int x;
   int y;
   int num = 100;
   Thread moveThread;
   

   public void init() {
      setBackground(Color.lightGray);
      setLayout(new BorderLayout());
      startButton = new Button("Start");
      add(BorderLayout.SOUTH,startButton);
      startButton.addActionListener(this);
      x = initX;
      y = initY;
   }

   public void paint(Graphics g) {
      g.setColor(Color.red);
      g.fillRect(x,y,rectWidth,rectHeight);
   }

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

   public void start() {
       if (moveThread != null)
          moveThread.resume();
   }

   public void run() {
      try {
         for(int i=0;i < num;i++) {
            x++;
            y++;
            repaint();
            Thread.sleep(100);
         }
      }  catch (InterruptedException e) {
         return;                 // end this thread
      }
   }

   public void actionPerformed(ActionEvent e) {
      if (e.getSource() == startButton) {
         x = initX;
         y = initY;
         moveThread = new Thread(this);
         moveThread.start();
      }
   }
}
Click Here to run MoveIt.


What do you think would happen if the button were pressed again while the rectangle is moving?

Adding a Scrollbar

Click Here for Scrollbar documentation.

/* < Applet code = MoveItSB
     width = 300 height = 300 >
   < /Applet >
*/

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

public class MoveItSB extends Applet implements ActionListener, 
                                     AdjustmentListener, Runnable {

   Button startButton;
   int initX = 50;
   int initY = 50;
   int rectWidth = 50;
   int rectHeight = 20;
   int x;
   int y;
   int num = 100;
   int delay = 100;
   Thread moveThread;
   Label delayLabel;
   Scrollbar delayValue;

   public void init() {
      Panel p = new Panel();
      Panel q = new Panel();
      p.setLayout(new GridLayout(2,1));
      q.setLayout(new GridLayout(1,2));
      setBackground(Color.lightGray);
      setLayout(new BorderLayout());
      startButton = new Button("Start");
      delayLabel = new Label("Delay "+delay);
      q.add(delayLabel);
      delayValue = new Scrollbar(Scrollbar.HORIZONTAL,delay,10,0,1010);
      delayValue.addAdjustmentListener(this);
      q.add(delayValue);
      p.add(q);
      p.add(startButton);
      add(BorderLayout.SOUTH,p);
      startButton.addActionListener(this);
      x = initX;
      y = initY;
   }

   public void paint(Graphics g) {
      g.setColor(Color.red);
      g.fillRect(x,y,rectWidth,rectHeight);
   }

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

   public void start() {
       if (moveThread != null)
          moveThread.resume();
   }

   public void run() {
      try {
         for(int i=0;i < num;i++) {
            x++;
            y++;
            repaint();
            Thread.sleep(delay);
         }
      }  catch (InterruptedException e) {
         return;                 // end this thread
      }
   }

   public void actionPerformed(ActionEvent e) {
      if (e.getSource() == startButton) {
         x = initX;
         y = initY;
         moveThread = new Thread(this);
         moveThread.start();
      }
   }

   public void adjustmentValueChanged(AdjustmentEvent e) {
      delay = delayValue.getValue();
      delayLabel.setText("Delay "+delay);
   }
}
Click Here to run MoveItSB.


If you change the scrollbar while this is running, will it have an immediate effect, or will it only affect the next run? Why?

Adding Buffering

If you look closely at the above animation, you will see a flickering as the screen is erased before the new rectangle is displayed. You can see it more easily if the window is large:

Click Here to run MoveItSB in a larger window.

You can eliminate this by using buffering. We override update so that the screen is not erased and we draw into an image which is then displayed. We set the backgound to yellow so that it will be more easily seen.

/* < Applet code = MoveItDB
     width = 300 height = 300 >
   < /Applet >
*/

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

public class MoveItDB extends Applet implements ActionListener, 
                                     AdjustmentListener, Runnable {

   Button startButton;
   int initX = 50;
   int initY = 50;
   int rectWidth = 50;
   int rectHeight = 20;
   int x;
   int y;
   int num = 100;
   int delay = 100;
   Thread moveThread;
   Label delayLabel;
   Scrollbar delayValue;
   Image buffer;
   Graphics gc;
   Graphics gcback;
   int currentWidth;
   int currentHeight;

   public void init() {
      Panel p = new Panel();
      Panel q = new Panel();
      p.setLayout(new GridLayout(2,1));
      q.setLayout(new GridLayout(1,2));
      setBackground(Color.lightGray);
      setLayout(new BorderLayout());
      startButton = new Button("Start");
      delayLabel = new Label("Delay "+delay);
      q.add(delayLabel);
      delayValue = new Scrollbar(Scrollbar.HORIZONTAL,delay,10,0,1010);
      delayValue.addAdjustmentListener(this);
      q.add(delayValue);
      p.add(q);
      p.add(startButton);
      add(BorderLayout.SOUTH,p);
      startButton.addActionListener(this);
      x = initX;
      y = initY;
      currentWidth = getBounds().width;
      currentHeight = getBounds().height;
      buffer = createImage(currentWidth,currentHeight);
      gc = buffer.getGraphics();
      gcback = buffer.getGraphics();
      gc.setColor(Color.red);
      gcback.setColor(Color.yellow);
   }

   public void update(Graphics g) {
      paint(g);
   }

   public void paint(Graphics g) {
      gcback.fillRect(0,0,currentWidth,currentHeight);
      gc.fillRect(x,y,rectWidth,rectHeight);
      g.drawImage(buffer,0,0,this);
   }

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

   public void start() {
       if (moveThread != null)
          moveThread.resume();
   }

   public void run() {
      try {
         for(int i=0;i < num;i++) {
            x++;
            y++;
            repaint();
            Thread.sleep(delay);
         }
      }  catch (InterruptedException e) {
         return;                 // end this thread
      }
   }

   public void actionPerformed(ActionEvent e) {
      if (e.getSource() == startButton) {
         x = initX;
         y = initY;
         moveThread = new Thread(this);
         moveThread.start();
      }
   }

   public void adjustmentValueChanged(AdjustmentEvent e) {
      delay = delayValue.getValue();
      delayLabel.setText("Delay "+delay);
   }
}
Click Here to run MoveItDB.

An External Animation Thread

The following can be used as an external thread for animation.
import java.applet.*;
import java.awt.*;
import java.awt.event.*;

public class SimpleAnimationThread extends Thread {

   private int num;
   private int delay;
   private Applet ap;
   private Point pos;

   public SimpleAnimationThread(Point initPos, int num, int delay, 
                                Applet ap) {
      pos = new Point(initPos);
      this.num = num;
      this.delay = delay;
      this.ap = ap;
   }

   public Point getPosition() {
      return pos;
   }

   public void run() {
      try {
         for(int i=0;i < num;i++) {
            pos.x++;
            pos.y++;
            ap.repaint();
            Thread.sleep(delay);
         }
      }  catch (InterruptedException e) { }   
   }

}

Here is an applet that uses this external thread.
/* < Applet code = MoveItExt
     width = 300 height = 300 >
   < /Applet >
*/

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

public class MoveItExt extends Applet implements ActionListener, 
                                     AdjustmentListener {

   Button startButton;
   int initX = 50;
   int initY = 50;
   int rectWidth = 50;
   int rectHeight = 20;
   int num = 100;
   int delay = 100;
   SimpleAnimationThread moveThread;
   Label delayLabel;
   Scrollbar delayValue;
   Image buffer;
   Graphics gc;
   Graphics gcback;
   int currentWidth;
   int currentHeight;
   Point initPos;

   public void init() {
      Panel p = new Panel();
      Panel q = new Panel();
      p.setLayout(new GridLayout(2,1));
      q.setLayout(new GridLayout(1,2));
      setBackground(Color.lightGray);
      setLayout(new BorderLayout());
      startButton = new Button("Start");
      delayLabel = new Label("Delay "+delay);
      q.add(delayLabel);
      delayValue = new Scrollbar(Scrollbar.HORIZONTAL,delay,10,0,1010);
      delayValue.addAdjustmentListener(this);
      q.add(delayValue);
      p.add(q);
      p.add(startButton);
      add(BorderLayout.SOUTH,p);
      startButton.addActionListener(this);
      initPos = new Point(initX,initY);
      currentWidth = getBounds().width;
      currentHeight = getBounds().height;
      buffer = createImage(currentWidth,currentHeight);
      gc = buffer.getGraphics();
      gcback = buffer.getGraphics();
      gc.setColor(Color.red);
      gcback.setColor(Color.yellow);
   }

   public void update(Graphics g) {
      paint(g);
   }

   public void paint(Graphics g) {
      Point pt;
      gcback.fillRect(0,0,currentWidth,currentHeight);
      if (moveThread == null)
         pt = initPos;
      else
         pt = moveThread.getPosition();
      gc.fillRect(pt.x,pt.y,rectWidth,rectHeight);
      g.drawImage(buffer,0,0,this);
   }

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

   public void start() {
       if (moveThread != null)
          moveThread.resume();
   }

   public void actionPerformed(ActionEvent e) {
      if (e.getSource() == startButton) {
         moveThread = new SimpleAnimationThread(initPos, num, delay, this);
         moveThread.start();
      }
   }

   public void adjustmentValueChanged(AdjustmentEvent e) {
      delay = delayValue.getValue();
      delayLabel.setText("Delay "+delay);
   }
}
Click Here to run this applet.


What would happen with we pushed the start button again while the rectangle is moving?
Would the applet behave differently if the line in SimpleAnimationThread
pos = new Point(initPos);
were replaced by
pos = initPos?

Click Here to run the applet with this change.

How would these classes have to be changed to allow two rectangles to move independently?


Animation Using PingPongThread

A case was made in the Threads discussion that PingPongThread was a general purpose thread which had nothing to do with ping pong, but just did the operations of "sleep" and "do something" in a loop. This is exactly what is needed to do animation.

A few minor changes to MoveItExt.java will convert it into an applet which uses PingPongThread instead of SimpleAnaimationThread. Here is a list of the changes needed:

Here is the code for MoveItExtDI:
/* < Applet code = MoveItExtDI
     width = 300 height = 300 >
   < /Applet >
*/

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

public class MoveItExtDI extends Applet implements ActionListener, 
                                     AdjustmentListener, DisplayInfo {

   Button startButton;
   int initX = 50;
   int initY = 50;
   int rectWidth = 50;
   int rectHeight = 20;
   int num = 100;
   int delay = 100;
   PingPongThread moveThread;
   Label delayLabel;
   Scrollbar delayValue;
   Image buffer;
   Graphics gc;
   Graphics gcback;
   int currentWidth;
   int currentHeight;
   int x;
   int y;

   public void init() {
      Panel p = new Panel();
      Panel q = new Panel();
      p.setLayout(new GridLayout(2,1));
      q.setLayout(new GridLayout(1,2));
      setBackground(Color.lightGray);
      setLayout(new BorderLayout());
      startButton = new Button("Start");
      delayLabel = new Label("Delay "+delay);
      q.add(delayLabel);
      delayValue = new Scrollbar(Scrollbar.HORIZONTAL,delay,10,0,1010);
      delayValue.addAdjustmentListener(this);
      q.add(delayValue);
      p.add(q);
      p.add(startButton);
      add(BorderLayout.SOUTH,p);
      startButton.addActionListener(this);
      currentWidth = getBounds().width;
      currentHeight = getBounds().height;
      buffer = createImage(currentWidth,currentHeight);
      gc = buffer.getGraphics();
      gcback = buffer.getGraphics();
      gc.setColor(Color.red);
      gcback.setColor(Color.yellow);
      x = initX;
      y = initY;
   }

   public void update(Graphics g) {
      paint(g);
   }

   public void paint(Graphics g) {
      gcback.fillRect(0,0,currentWidth,currentHeight);
      gc.fillRect(x,y,rectWidth,rectHeight);
      g.drawImage(buffer,0,0,this);
   }

   public void stop() {
      if (moveThread != null)
         moveThread.setSuspend(true);
   }

   public void start() {
       if (moveThread != null)
          moveThread.setSuspend(false);
   }

   public void actionPerformed(ActionEvent e) {
      if (e.getSource() == startButton) {
         moveThread = new PingPongThread(1, "unused", num, delay, this);
         x = initX;
         y = initY;
         moveThread.start();
      }
   }

   public void adjustmentValueChanged(AdjustmentEvent e) {
      delay = delayValue.getValue();
      delayLabel.setText("Delay "+delay);
   }

   public void showString(int id, String str) {
      x++;
      y++;
      repaint();
   }

}

Click Here to run the applet with this change.


Applet Summary

MoveIt is a simple thread which moves a rectangle along a diagonal line with a 100 ms delay per frame.

MoveItSB add a scrollbar for determining the delay between frames.

MoveItDB adds buffering to avoid flicker.

MoveItExt is a version that uses an external thread.

MoveItExt1 is a slight change which shows why it it necessary to be careful when passing objects to methods.

MoveItExtDI a version using the PingPongThread


Next topic: Java Threads and Synchronization