Man, this class is pretty!

Lecture Notes Three: Animating Sprites

What's that elfish?

frilled.cs.indiana.edu%pwd
/nfs/grouchy/home/user2/www/classes/a348-dger/t540/lectures/three
frilled.cs.indiana.edu%webster sprite
sprite \'spr[0xF5]^-t\ n
[ME sprit, fr. MF esprit, fr. L spiritus spirit -- more at SPIRIT]
(14c)
1a archaic: SOUL
1b: a disembodied spirit: GHOST
2a: ELF, FAIRY
2b: an elfish person 

frilled.cs.indiana.edu%
We start by defining a very general, abstract class for sprites.

frilled.cs.indiana.edu%pwd
/nfs/grouchy/home/user2/www/classes/a348-dger/t540/lectures/three
frilled.cs.indiana.edu%ls -ld Sprite.java
-rw-r--r--   1 dgerman       758 Nov  4 23:05 Sprite.java
frilled.cs.indiana.edu%cat Sprite.java
import java.awt.*;

abstract class Sprite {
    
    protected boolean visible;           // is sprite visible
    protected boolean active;            // is sprite updatable
    
    // abstract methods:
    abstract void paint (Graphics g);
    abstract void update();
    
    // accessor methods:
    public boolean isVisible() {
	return visible;
    }
    
    public void setVisible(boolean b) {
	visible = b;
    }
    
    public boolean isActive() {
	return active;
    }
    
    public void setActive(boolean b) {
	active = b;
    }
    
    // suspend the sprite
    public void suspend() {
	setVisible(false);
	setActive(false);
    }
    
    // restore the sprite
    public void restore() {
	setVisible(true);
	setActive(true);
    }
    
}

frilled.cs.indiana.edu%
We then describe a more specialized (two-dimensional) type of sprite.

frilled.cs.indiana.edu%pwd
/nfs/grouchy/home/user2/www/classes/a348-dger/t540/lectures/three
frilled.cs.indiana.edu%ls -ld Sprite2D.java
-rw-r--r--   1 dgerman       393 Nov  4 23:20 Sprite2D.java
frilled.cs.indiana.edu%cat Sprite2D.java
import java.awt.*; 

abstract class Sprite2D extends Sprite {
    
    protected int locx;   
    protected int locy;
    
    Color color;
    boolean fill; 
    
    public boolean getFill() {
	return fill;
    }
    
    public void setFill(boolean b) {
	fill = b;
    }
    
    public void setColor(Color c) {
	color = c;
    }
    
    public Color getColor() {
	return color;
    }

}

frilled.cs.indiana.edu%
And then we specialize this description even further:
frilled.cs.indiana.edu%pwd
/nfs/grouchy/home/user2/www/classes/a348-dger/t540/lectures/three
frilled.cs.indiana.edu%ls -ld RectSprite.java
-rw-r--r--   1 dgerman       728 Nov  4 23:45 RectSprite.java
frilled.cs.indiana.edu%cat RectSprite.java
import java.awt.*;

class RectSprite extends Sprite2D {
    
    protected int width, height;      // dimensions of rectangle
    
    public RectSprite(int x,int y,int w,int h,Color c) {
	locx = x;
	locy = y;
	width = w;
	height = h;
	color = c;
	fill = false;                 // default: don't fill
	restore();                    // restore the sprite
    }
    
    // provide implementation of abstract methods:
    
    public void update() {
	
	// does nothing
	
    }
    
    // check if sprite's visible before painting
    public void paint(Graphics g) {
	if (visible) {
	    g.setColor(color);
	    if (fill) {
		g.fillRect(locx,locy,width,height);
	    } else {
		g.drawRect(locx,locy,width,height);
	    }
	}
    }
}
frilled.cs.indiana.edu%
Finally, we describe what a bouncing rectangle is to us.

frilled.cs.indiana.edu%pwd
/nfs/grouchy/home/user2/www/classes/a348-dger/t540/lectures/three
frilled.cs.indiana.edu%ls -ld Bouncing*
-rw-r--r--   1 dgerman      1100 Nov  4 23:45 BouncingRect.java
frilled.cs.indiana.edu%cat Bouncing.java
import java.awt.*;

class BouncingRect extends RectSprite implements Moveable {
    
    // the coords at which 
    // the rectangle bounces  
    protected int max_width;
    protected int max_height;    
    
    // sprite velocity. used to implement Moveable interface
    protected int vx;
    protected int vy;
    
    public BouncingRect(int x,int y,int w,int h,Color c,
			int max_w,int max_h) {
	super(x,y,w,h,c);
	max_width = max_w;
	max_height = max_h;
    }
    
    public void setPosition(int x,int y) {
	locx = x;
	locy = y;
    }
    
    public void setVelocity(int x,int y) {
	vx = x;
	vy = y;
    }
    
    // update position according to velocity 
    public void updatePosition() {
	locx += vx;
	locy += vy;
    }
    
    // move and bounce rectangle if it hits borders
    public void update() {
	
	// flip x velocity if it hits left or right bound
	if ((locx + width > max_width) ||
	    locx < 0) {
	    vx = -vx;
	}
	
	// flip y velocity if it hits top or bottom bound
	if ((locy + height > max_height) ||
	    locy < 0) {
	    vy = -vy;
	}
	updatePosition();
    }
    
}

frilled.cs.indiana.edu%
A description of the moveable interface is needed.

frilled.cs.indiana.edu%pwd
/nfs/grouchy/home/user2/www/classes/a348-dger/t540/lectures/three
frilled.cs.indiana.edu%ls -ld Move*
-rw-r--r--   1 dgerman       170 Nov  4 23:38 Moveable.java
frilled.cs.indiana.edu%cat Move*.java
interface Moveable {
    public abstract void setPosition(int c, int d);
    public abstract void setVelocity(int x, int y);
    public abstract void updatePosition();
}
frilled.cs.indiana.edu%
So now we can write an applet.

frilled.cs.indiana.edu%pwd
/nfs/grouchy/home/user2/www/classes/a348-dger/t540/lectures/three
frilled.cs.indiana.edu%ls -ld Bounce.java Seven.html
-rw-r--r--   1 dgerman      2357 Nov  4 23:48 Bounce.java
-rw-r--r--   1 dgerman       247 Nov  4 23:49 Seven.html
frilled.cs.indiana.edu%cat Bounce.java
import java.applet.*;
import java.awt.*;

public class Bounce extends Applet implements Runnable {
    
    Thread animation;
    
    Graphics offscreen;
    Image image;
    
    static final int NUM_SPRITES = 3;       
    static final int REFRESH_RATE = 80;    // in ms 
    
    Sprite sprites[];                      // sprite array
    int width, height;                     // applet dimensions
    
    public void init() {
	System.out.println(">> init <<");
	setBackground(Color.black);        // applet background
	width = bounds().width;            // set applet dimensions
	height = bounds().height;
	initSprites();
	image = createImage(width,height); // make offscreen buffer
	offscreen = image.getGraphics();
    }
    
    public void initSprites() {
	
	sprites = new Sprite[NUM_SPRITES]; // init sprite array 
	
	// define sprite for border
	sprites[0] = new RectSprite(0,0,width-1,height-1,Color.green);
	
	sprites[1] = new BouncingRect(0,0,30,30,Color.yellow,
				      width-1,height-1);
	
	sprites[2] = new BouncingRect(17,17,13,13,Color.red,
				      width-1,height-1);
	
	((Moveable)sprites[1]).setVelocity(4,3);
	((Moveable)sprites[2]).setVelocity(1,2);
	((Sprite2D)sprites[2]).setFill(true);  // fill this sprite
	
    }
    
    public void start() {	
	System.out.println("Starting... ");
	animation = new Thread(this);
	if (animation != null) {
	    animation.start();
	}
    }
    
    // CALL EACH SPRITE'S update() METHOD
    // DYNAMIC METHOD BINDING OCCURS HERE!
    public void updateSprites() {
	for (int i=0; i<sprites.length; i++) {
	    sprites[i].update(); // call each sprite's update() method
	}
    }
    
    // override update so it doesn't erase screen
    public void update(Graphics g) {
	paint(g);
    }
    
    public void paint(Graphics g) {
	
	offscreen.setColor(Color.black);
	offscreen.fillRect(0,0,width,height);  // clear buffer
	
	for (int i=0; i<sprites.length; i++) {
	    sprites[i].paint(offscreen);       // paint each rectangle
	}
	
	g.drawImage(image,0,0,this);
    }
    
    public void run() {
	while (true) {
	    repaint();
	    updateSprites();
	    try {
		Thread.sleep (REFRESH_RATE);
	    } catch (Exception exc) { };
	}
    }
    
    public void stop() {
	
	System.out.println("Stopping... ");

	if (animation != null) {
	    animation.stop();
	    animation = null;
	}
    }
    
}

frilled.cs.indiana.edu%cat Seven.html
<html>
  <head><title>Bouncing... </title></head>
  <body bgcolor=white>

    Sprites bouncing... (see the notes) <p> 

    <applet code="Bounce.class" width=300 height=300>
    </applet>

    

Dedicated to Michael Jordan. <p> </body> </html> frilled.cs.indiana.edu%

And here's how the applet runs.

This is a diagram of the program so far.

We now change it, as follows. You will soon see why.

Classes already there have been downplayed with a pink shade of lightgrey.

So we can better focus on the new addition, which we present below.

We therefore need to present only three new classes and an HTML file.

An image will be used:

A sound file will be used as well.

And here's the applet.

Let's now post the code. There are three new classes in all:

frilled.cs.indiana.edu%pwd
/nfs/grouchy/home/user2/www/classes/a348-dger/t540/lectures/three
frilled.cs.indiana.edu%ls -ld *.java
-rw-r--r--   1 dgerman       696 Nov  5 12:33 BitmapSprite.java
-rw-r--r--   1 dgerman      2357 Nov  4 23:48 Bounce.java
-rw-r--r--   1 dgerman      1116 Nov  5 13:08 BouncingBitmap.java
-rw-r--r--   1 dgerman      1100 Nov  4 23:45 BouncingRect.java
-rw-r--r--   1 dgerman      2914 Nov  5 17:12 BouncingSushi.java
-rw-r--r--   1 dgerman       170 Nov  4 23:38 Moveable.java
-rw-r--r--   1 dgerman       728 Nov  4 23:45 RectSprite.java
-rw-r--r--   1 dgerman       758 Nov  4 23:05 Sprite.java
-rw-r--r--   1 dgerman       393 Nov  4 23:20 Sprite2D.java
frilled.cs.indiana.edu%cat BouncingSushi.java
import java.applet.*;
import java.awt.*;

public class BouncingSushi extends Applet implements Runnable {
    
    Thread animation;
    
    Graphics offscreen;
    Image image;
    
    AudioClip a;
    
    static final int NUM_SPRITES = 6;       
    static final int REFRESH_RATE = 80;    // in ms
    
    Sprite sprites[];                      // sprite array
    int width, height;                     // applet dimensions
    
    public void init() {

	System.out.println("Initializing... ");

	width = bounds().width;            // set applet dimensions
	height = bounds().height;
	initSprites();
	image = createImage(width,height); // make offscreen buffer
	offscreen = image.getGraphics();
    }
    
    public void initSprites() {
	
	sprites = new Sprite[NUM_SPRITES]; // init sprite array
	
	// define sprite for border
	sprites[5] = new RectSprite(0,0,width-1,height-1,Color.green);
	
	sprites[1] = new BouncingRect(0,0,30,30,Color.yellow,
				      width-1,height-1);
	
	sprites[2] = new BouncingRect(17,17,13,13,Color.red,
				      width-1,height-1);
	
	// border of the smaller box
	sprites[3] = new RectSprite(0,0,114,114,Color.green);
	
	// this rect bounces in a smaller box!
	sprites[4] = new BouncingRect(13,13,17,17,Color.green,
				      114,114);

	sprites[0] = new BouncingBitmap(37,37,
					getImage(getCodeBase(), 
						 "MM.jpg"),
					this,
					width-1,height-1);
	
	((Moveable)sprites[1]).setVelocity(7,5);
	((Moveable)sprites[2]).setVelocity(1,2);
	((Moveable)sprites[4]).setVelocity(3,4);
	((Sprite2D)sprites[4]).setFill(true);  // fill this sprite
	((Moveable)sprites[0]).setVelocity(4,3);
	((BitmapSprite)sprites[0]).setSize(134,168);
	
    }
    
    public void start() {
	System.out.println("Starting... ");
	animation = new Thread(this);
	if (animation != null) {
	    animation.start();
	}
	a = getAudioClip(getCodeBase(), "Sound.au");
	a.loop() ; // loop the sound
	
    }
    
    // CALL EACH SPRITE'S update() METHOD
    // DYNAMIC METHOD BINDING OCCURS HERE!
    public void updateSprites() {
	for (int i=0; i<sprites.length; i++) {
	    sprites[i].update(); 
            // call each sprite's update() method
	}
    }
        
    // override update so it doesn't erase screen
    public void update(Graphics g) {
	paint(g);
    }
    
    public void paint(Graphics g) {
	
	offscreen.setColor(Color.black);
	offscreen.fillRect(0,0,width,height); // clear buffer
	
	for (int i=0; i<sprites.length; i++) {
	    sprites[i].paint(offscreen);      // paint each rectangle
	}
	
	g.drawImage(image,0,0,this);
    }
    
    public void run() {
	while (true) {
	    repaint();
	    updateSprites();
	    try {
		Thread.sleep (REFRESH_RATE);
	    } catch (Exception exc) { };
	}
    }
    
    public void stop() {
	System.out.println("Stopping... ");
	a.stop(); // stop the sound
	if (animation != null) {
	    animation.stop();
	    animation = null;
	}
    }
    
}

frilled.cs.indiana.edu%cat BitmapSprite.java
import java.applet.*; 
import java.awt.*; 

class BitmapSprite extends Sprite {
    
    protected int locx;   
    protected int locy;
    
    // image dimensions
    protected int width,height;
    
    protected Image image;                   // the bitmap
    protected Applet applet;                 // the parent applet
    
    public BitmapSprite(int x,int y,Image i,Applet a) {
	locx = x;
	locy = y;
	image = i;
	applet = a;
	restore();
    }
    
    public void setSize(int w,int h) {
	width = w;
	height = h;
    }
    
    public void update() {
	
	// do nothing
	
    }
    
    public void paint(Graphics g) {
	if (visible) {
	    g.drawImage(image,locx,locy,applet);
	}
    }
}

frilled.cs.indiana.edu%cat BouncingBitmap.java
import java.applet.*;
import java.awt.*; 

class BouncingBitmap extends BitmapSprite implements Moveable {
    
    // the coords at which the bitmap bounces  
    protected int max_width;
    protected int max_height;    
    
    // sprite velocity. used to implement Moveable interface 
    protected int vx;
    protected int vy;
    
    public BouncingBitmap(int x,int y,Image i,Applet a, 
			  int max_w,int max_h) {
	super(x,y,i,a);
	max_width = max_w;
	max_height = max_h;
    }
    
    public void setPosition(int x,int y) {
	locx = x;
	locy = y;
    }
    
    public void setVelocity(int x,int y) {
	vx = x;
	vy = y;
    }
    
    // update position according to velocity
    public void updatePosition() {
	locx += vx;
	locy += vy;
    }
    
    // move and bounce rectangle if it hits borders
    public void update() {
	
	// flip x velocity if it hits left or right bound
	if ((locx + width > max_width) ||
	    locx < 0) {
	    vx = -vx;
	}
	
	// flip y velocity if it hits top or bottom bound
	if ((locy + height > max_height) ||
	    locy < 0) {
	    vy = -vy;
	}
	updatePosition();
    }
    
}
frilled.cs.indiana.edu%javac BouncingSushi.java
Note: BouncingSushi.java uses or overrides a deprecated API.
Note: Recompile with -deprecation for details.
frilled.cs.indiana.edu%ls -ld *.class
-rw-r--r--   1 dgerman       857 Nov  5 17:09 BitmapSprite.class
-rw-r--r--   1 dgerman      2633 Nov  5 17:09 Bounce.class
-rw-r--r--   1 dgerman       901 Nov  5 17:12 BouncingBitmap.class
-rw-r--r--   1 dgerman       875 Nov  5 17:12 BouncingRect.class
-rw-r--r--   1 dgerman      3265 Nov  5 17:31 BouncingSushi.class
-rw-r--r--   1 dgerman       180 Nov  5 17:09 Moveable.class
-rw-r--r--   1 dgerman       804 Nov  5 17:09 RectSprite.class
-rw-r--r--   1 dgerman       700 Nov  5 17:09 Sprite.class
-rw-r--r--   1 dgerman       568 Nov  5 17:09 Sprite2D.class
frilled.cs.indiana.edu%ls -ld *.jpg *.au
-rw-r--r--   1 dgerman      4550 Nov  5 14:24 MM.jpg
-rw-r--r--   1 dgerman     28106 Nov  5 12:21 Sound.au
frilled.cs.indiana.edu%
The HTML file for this applet is here:
<html>
  <head><title>A Weird Applet...</title></head>
  <body bgcolor=white>
    This <em>may</em> look a bit weird, but that's OK, 
    because it <em>is</em> weird... 
    <p> 
    <applet code="BouncingSushi.class" width=300 height=300>
    </applet>
    <p> 
    Dedicated to Andy Kauffman. 
  </body>
</html>
In this chapter you've learned about abstract classes and interfaces and how they permit the clean, modular design of applets. In particular you've created Sprite classes that you'll use in your first Java game (which is coming up really soon!). And you now know how to use bitmaps, which are important in creating customized looks for your graphics applets. You also saw how you can include sounds in your applets. Next you will see how to take control of your sprites.


Last updated: Nov 4, 2001 by Adrian German for A348/A548