Man, this class is pretty!

Lecture Notes Five: Creating Bitmap Loops

In the previous chapters you saw how to load, draw, and move a single bitmap. Now let's create animation by looping a sequence of bitmaps.


This requires loading several images, and we're going to use the MediaTracker class. The reason for using it is that the process of loading an Image happens asynchronously, ... and MediaTracker allows you to wait for the images to finish loading before proceeding with the animation. Let's see an example!

I was going to say... First, what images will we be loading?

How about a chef and some sushi? Alright!

Yes, but your spelling is all wrong! Hmmm...
frilled.cs.indiana.edu%webster alright
al-right [ME, fr. OE ealriht ]
:ALL RIGHT 
usage In now obsolete senses all right or alright was formed in
     Old English as ealriht. Variation in early scribal and printing
     practices and in spoken stress patterns has given us this and similar
     pairs in all ready, already and all together, altogether.
     Since the 19th century some have insisted that alright is wrong,
     but, though it is less frequent than all right, it remains in
     common use and appears in the work of reputable writers  <the first
     two years of medical school were alright --Gertrude Stein>   <it
     is doing a bit of alright --P. H. Dougherty, N.Y. Times>  

frilled.cs.indiana.edu%
Alright. Time to move on.

Let's write an applet. Here it is, and here's the code:
import java.applet.*;
import java.awt.*;

public class Track extends Applet {
    MediaTracker t;
    Image i,j; // two images
    
    public void init() {
	setBackground(Color.black); 
	t = new MediaTracker(this); // create a MediaTracker object 
	i = getImage(getCodeBase(),"sushi.gif"); /* now define the Image
						    location, then add it 
						    to the MediaTracker, 
						    as detailed below */ 
	t.addImage(i,0); /* the second argument is an ID number. Images 
			    of lower ID are loaded first; furthermore, the 
			    MediaTracker can wait for images of the same ID 
			    to finish loading together. */ 
	j = getImage(getCodeBase(),"chef.gif"); 
	t.addImage(j,0); 
	showStatus("loading");
		
	try { // now wait for all images to finish loading 
	    t.waitForAll();               
	}  catch (Exception e) { }
	
	if (t.isErrorAny()) { // check for errors 
	    showStatus("error");
	} else if (t.checkAll()) {
	    showStatus("successfully loaded");
	}
	
    }
    
    public void paint(Graphics g) {
	g.drawImage(i,13,17,this); // draw the first image 
	g.drawImage(j,203,107,this); // draw the second 
    }
}

That requires an .html file: Very good. When you run this, you will have to wait in the beginning for the images to load,
<html>
  <head><title>MediaTracker Demo</title></head>
  <body>
    <applet code=Track.class width=300 height=300>
    </applet>
  </body>
</html>
... but they're displayed completely once (or, when and only when) loading is done. So we're ready to go.

Let's write this applet now. OK, but let's try to understand it in stages.

The class extension mechanism in Java allows us to approach any modeling problem in stages. That's what I said.

We will create two new classes:
  • a BitmapLoop class,
  • and an UFOControl applet
We'll build on previously described concepts:
  • Sprite
  • BitmapSprite
  • and Moveable

Here's a diagram for BitmapLoop
... which covers UFOControl also.

Take a look at this, ... then ask me some questions.
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);
    }
    
}

OK, what is a Sprite?

A Sprite is a very generic thing. Sounds a little bit abstract to me.

And it's meant to be that way. "They're bouncy, trouncy, flouncy, pouncy... "

You're close. A Sprite is something that:
  • is visible or not
  • can tell you if it's visible or not
  • can make itself visible or invisible
  • is active or not
  • can tell you if it's active or not
  • can make itself active/inactive
A Sprite should also be able to:
  • paint and update themselves
  • suspend or restore themselves
How a Sprite paints or updates itself depends on the kind of Sprite we are talking about. But they will all share the same structure.

The wonderful thing about sprites is: ... that sprites are a wonderful thing!

Got that right anyway!

Now what's a BitmapSprite?

Take a look at the code:
import java.awt.*;
import java.applet.*; 

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);
	}
    }
}
Oh, that's easy.

A BitmapSprite is a Sprite with:
  • a location (locx, locy)
  • a size (width, height)
  • an Image to show
  • appears in an Applet context
Not only that but it also:
  • can resize itself
  • can update itself
  • can paint itself
update does nothing (as the image is always the same) while paint is very predictable (and draws the image, with a Graphics method).

Sounds like a BitmapSprite to me! Now, what's a BitmapLoop?

First, a BitmapLoop is a BitmapSprite that can (and should) update itself. It's an animation. Second, we also want it to be Moveable.

What does it mean to be Moveable? In very generic terms it is this:
interface Moveable {
    public abstract void setPosition(int c, int d);
    public abstract void setVelocity(int dx, int dy);
    public abstract void updatePosition();
}

A-ha! So if it's Moveable it can
  • set its position,
  • set its velocity, and
  • it can update its position (by velocity)
In this case velocity is really a very small variation in both x and y, so updating the position likely amounts to changing it by the current value of the velocity.

Let's look at how a BitmapLoop is written:
import java.awt.*;
import java.applet.*; 

class BitmapLoop extends BitmapSprite implements Moveable {

    protected Image images[];       // sequence of bitmaps 
    protected int currentImage;     // the current bitmap 
    protected boolean foreground;   // are there foreground images? 
    
    // Constructor assumes background image already loaded (use MediaTracker)
    public BitmapLoop(int x, int y, Image background, 
                                    Image fg[], Applet applet) {

	super(x, y, background, applet);

	width = image.getWidth(applet);  // get size of background 
	height = image.getHeight(applet);
	
	images = fg;                     // both are of type Image[] 
	currentImage = 0;            
	if (images.length == 0)     
	    foreground = false;          // nothing in images[]  
	else 
	    foreground = true;
        // using the boolean instead of the array's length, for convenience 
	
    }    
    
    // cycle currentImage if sprite is active and there are foreground images 
    public void update() { 

	if (active && foreground) 
	    currentImage = (currentImage + 1) % images.length;

	updatePosition(); // last method in class 

    }
        
    public void paint(Graphics g) {

	if (visible) { // draw background, then foreground images, if any 

	    g.drawImage(image, // where does this one come from? 
	                locx, locy, applet);
            // it comes from BitmapSprite and it's looked at as a background

	    if (foreground)    // something in images[] 
		g.drawImage(images[currentImage], locx, locy, applet);

	}
    }    
    
    // implement moveable interface 
    public void setPosition(int x, int y) {
	locx = x;
	locy = y;
    }

    protected int vx, vy; // both are displacements 

    public void setVelocity(int dx, int dy) {
	vx = dx;
	vy = dy;
    }
    
    // update position according to velocity 
    public void updatePosition() {
	locx += vx;
	locy += vy;
	vx = 0;
	vy = 0;
    }
    
}
Rings upon rings...

It kind of reminds me ... of the example we discussed in the first lab?
frilled.cs.indiana.edu%cat Calculator.java
class One {
    int add(int n, int m) {
	if (m == 0) return n;
	else return add(n+1, m-1);
    }
}

class Two extends One {
    int mul(int n, int m) {
	if (m == 1) return n; 
        else return add(n, mul(n, m-1)); 
    }
}

class Three extends Two {
    int pow(int n, int m) {
	if (m == 0) return 1; 
        else return mul(n, pow(n, m-1)); 
    }
}

class Calculator {
    public static void main(String[] args) {
	Three calc = new Three(); 
	int n = 3, m = 5; 
	System.out.println(n + " + " + m + " = " + calc.add(n, m)); 
	System.out.println(n + " * " + m + " = " + calc.mul(n, m)); 
	System.out.println(n + " ^ " + m + " = " + calc.pow(n, m)); 
    }
}
frilled.cs.indiana.edu%javac Calculator.java
frilled.cs.indiana.edu%java Calculator
3 + 5 = 8
3 * 5 = 15
3 ^ 5 = 243
frilled.cs.indiana.edu%
Yes. Note that we developed in stages. Each stage adds just one thing to the previous.

The final version is the most advanced. In the end they're all available.

Here's another example. This one for interfaces.
frilled.cs.indiana.edu%cat Example.java
interface Multiplier {
    int mul(int n, int m);
}

class Alpha implements Multiplier {
    public int mul(int n, int m) {
	return n * m; 
    }
}

class Beta implements Multiplier {
    public int mul(int n, int m) {
	int result = 0; 
	for (int i = 0; i < m; i++) 
	    result += n; 
	return result; 
    }
}

class Gamma implements Multiplier {
    public int mul(int n, int m) {
	if (m == 1) return n; 
        else return n + mul(n, m-1); 
    }
}

class Example {
    public static void main(String[] args) {
	Alpha a = new Alpha(); 
	Beta b = new Beta(); 
	Gamma g = new Gamma(); 
	int n = 5, m = 3; 
	System.out.println(n + " * " + m + " = " + a.mul(n,m) + " (by Alpha)");
	System.out.println(n + " * " + m + " = " + b.mul(n,m) + " (by Beta )");
	System.out.println(n + " * " + m + " = " + g.mul(n,m) + " (by Gamma)");
    }
}
frilled.cs.indiana.edu%javac Example.java
frilled.cs.indiana.edu%java Example
5 * 3 = 15 (by Alpha)
5 * 3 = 15 (by Beta )
5 * 3 = 15 (by Gamma)
frilled.cs.indiana.edu%

Indeed. What other names could we have given to the to the three Multipliers? How about:
  • Primitive (for Alpha)
  • Iterative (for Beta,) and
  • Recursive (for Gamma)

That's exactly what I had in mind. Very good, let's move on.

Here's UFOControl. Let me see it.
import java.applet.*;
import java.awt.*;

public class UFOControl extends Applet implements Runnable {
    
    Thread animation; 
    
    Graphics offscreen; // offscreen graphics context for double-buffering
    Image image;        // the actual image used for double-buffering
    
    static final int NUM_SPRITES = 1;      // just one, for Serling 
    static final int REFRESH_RATE = 80;    // in milliseconds
    
    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();                     // defined below 

	image = createImage(width,height); // make offscreen buffer
	offscreen = image.getGraphics();

    }
    
    public void initSprites() {

	sprites = new Sprite[NUM_SPRITES]; // just one, remember? 

	Image backImage;                   // background Image
	Image foreImage[] =  new Image[6]; // 6 foreground Images
	
	MediaTracker t = new MediaTracker(this);

	backImage = getImage(getCodeBase(),"image/back.gif"); // blue moon 

	t.addImage(backImage, 0); // 0 is just an ID, as discussed, ... 

	for (int i=0; i<6; i++) {
	    foreImage[i] = getImage(getCodeBase(),
				    "image/fore" + i + ".gif");
	    t.addImage(foreImage[i],0); /* ..., and all images 
                                           are equally important
	}
	
	System.out.println("loading Images");
	
	try { // wait for all images to finish loading 
	    t.waitForAll();               
	}  catch (Exception e) { }
	
	// check for errors 
	if (t.isErrorAny()) {
	    System.out.println("error");
	} else if (t.checkAll()) {
	    System.out.println("successfully loaded");
	}

	// initialize the BitmapLoop
	sprites[0] = new BitmapLoop(13, 17, // location first 
				    backImage, foreImage, this);
	
    }

    // Move UFO depending on Arrow Keys
    public boolean keyDown(Event e,int key) {

	// there's only one sprite, with index 0         
	switch (key) {
	case Event.RIGHT: // x is increasing 
	    ((Moveable)sprites[0]).setVelocity(3,0);
	    break;
	case Event.LEFT:  // x is decreasing
	    ((Moveable)sprites[0]).setVelocity(-3,0);
	    break;
	case Event.UP:    // y is decreasing 
	    ((Moveable)sprites[0]).setVelocity(0,-3);
	    break;
	case Event.DOWN:  // y is increasing 
	    ((Moveable)sprites[0]).setVelocity(0,3);
	    break;
	default:
	    break;
	}
	return true;

    }
    
    public void start() {
	
	System.out.println(" ***(start)*** ");

	animation = new Thread(this);

	if (animation != null) 
	    animation.start();
	
    }
    
    public void updateSprites() {
	for (int i=0; i<sprites.length; i++) {
	    sprites[i].update(); /* dynamic binding occurs here as we
				    call each sprite's update() method */ 

	    /* so, do you remember what BitmapLoops do at update()? 

               public void update() {
      	           if (active && foreground) 
	               currentImage = (currentImage + 1) % images.length;
	                   updatePosition();
               }

               exactly! - they simply keep looping their images/frames 

	    */ 

	}
    }
    
    /* override update so it doesn't erase screen. (this eliminates 
       the flicker but requires a more involved, more determined paint())
    */ 
    public void update(Graphics g) {
	paint(g);
    }    
    
    /* paint uses double buffering as announced above */ 
    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 sprite off screen 
	}

	// when done, update the applet's graphical context 
	g.drawImage(image,0,0,this); // ask g to show the image 
    }
    
    public void run() {

	while (true) {       // keep looping

	    repaint();       // this calls update that we've redefined above 

	    updateSprites(); // this is our method, defined above 

	    try {            // this simply waits a bit, between the frames 
		Thread.sleep (REFRESH_RATE); 
	    } catch (Exception exc) { };

	}

    }
        
    public void stop() {
	
	System.out.println(" ***(stop)*** ");
	if (animation != null) {
	    animation.stop();
	    animation = null;
	}

    }
    
}
Basically, and frankly: quite understandable.

Yeah, and frankly: quite understandable, too! ... although I still think it's understandable.

I see no reason to argue over that. Time to move on...

We need a bit of HTML,
<html>
  <head><title>Animating Bitmaps</title></head>
  <body>
    Dedicated to Rod Serling. 

    <applet code=UFOControl.class width=300 height=300>
    </applet>

    Use the arrow keys to move the Alien. 
</body>
</html>
... and we're ready.

Oh, and the pictures!
frilled.cs.indiana.edu%du -a image
2	image/back.gif
1	image/fore0.gif
1	image/fore1.gif
1	image/fore2.gif
1	image/fore3.gif
1	image/fore4.gif
1	image/fore5.gif
9	image

Let's summarize. In this lab, you have learned the intricacies of handling mouse and keyboard events in applets, which you will use in all future games.

In addition, you have seen how to display text in an applet, which is necessary to relay information to the player. Another important thing that you have learned is the use of MediaTracker to load images synchronously.

Finally, you have learned to create BitmapLoop sprites, which are sprites that, ... are little animations in themselves. Now you are ready for your first Java video game!


Last updated: Jan 21, 2002 by Adrian German for T540 NGD