CS 4773 Object Oriented Systems
A Simple Animation

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


Previous Topic: Threads

Introduction
A Simple Animation
Adding a Scrollbar
Adding Buffering
An External Animation Thread
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 init_x = 50;
   int init_y = 50;
   int rect_width = 50;
   int rect_height = 20;
   int x;
   int y;
   int num = 100;
   Thread move_thread;
   

   public void init() {
      setBackground(Color.lightGray);
      setLayout(new BorderLayout());
      StartButton = new Button("Start");
      add("South",StartButton);
      StartButton.addActionListener(this);
      x = init_x;
      y = init_y;
   }

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

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

   public void start() {
       if (move_thread != null)
          move_thread.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 = init_x;
         y = init_y;
         move_thread = new Thread(this);
         move_thread.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 init_x = 50;
   int init_y = 50;
   int rect_width = 50;
   int rect_height = 20;
   int x;
   int y;
   int num = 100;
   int delay = 100;
   Thread move_thread;
   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("South",p);
      StartButton.addActionListener(this);
      x = init_x;
      y = init_y;
   }

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

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

   public void start() {
       if (move_thread != null)
          move_thread.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 = init_x;
         y = init_y;
         move_thread = new Thread(this);
         move_thread.start();
      }
   }

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


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.

/* & lt 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 init_x = 50;
   int init_y = 50;
   int rect_width = 50;
   int rect_height = 20;
   int x;
   int y;
   int num = 100;
   int delay = 100;
   Thread move_thread;
   Label DelayLabel;
   Scrollbar DelayValue;
   Image buffer;
   Graphics GC;
   Graphics GCback;
   int width;
   int height;

   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("South",p);
      StartButton.addActionListener(this);
      x = init_x;
      y = init_y;
      width = getBounds().width;
      height = getBounds().height;
      buffer = createImage(width,height);
      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,width,height);
      GC.fillRect(x,y,rect_width,rect_height);
      g.drawImage(buffer,0,0,this);
   }

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

   public void start() {
       if (move_thread != null)
          move_thread.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 = init_x;
         y = init_y;
         move_thread = new Thread(this);
         move_thread.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 init_pos, int num, int delay, 
                                Applet ap) {
      pos = new Point(init_pos);
      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 init_x = 50;
   int init_y = 50;
   int rect_width = 50;
   int rect_height = 20;
   int num = 100;
   int delay = 100;
   SimpleAnimationThread move_thread;
   Label DelayLabel;
   Scrollbar DelayValue;
   Image buffer;
   Graphics GC;
   Graphics GCback;
   int width;
   int height;
   Point init_pos;

   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("South",p);
      StartButton.addActionListener(this);
      init_pos = new Point(init_x,init_y);
      width = getBounds().width;
      height = getBounds().height;
      buffer = createImage(width,height);
      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,width,height);
      if (move_thread == null)
         pt = init_pos;
      else
         pt = move_thread.GetPosition();
      GC.fillRect(pt.x,pt.y,rect_width,rect_height);
      g.drawImage(buffer,0,0,this);
   }

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

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

   public void actionPerformed(ActionEvent e) {
      if (e.getSource() == StartButton) {
         move_thread = new SimpleAnimationThread(init_pos, num, delay, this);
         move_thread.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(init_pos);
were replaced by
pos = init_pos?

Click Here to run the applet with this change.

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


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 passes objects to methods.


Next topic: Java Threads and Synchronization