IMPORTANT: This is the last in a series of four applet examples for animation.Make sure you understand the first three -- HelloAnimThreadFirst?, HelloAnimThreadA?, and HelloAnimThreadB? -- before looking at this one.
Explanation of HelloAnimThreadC? applet
Number of classes: 1 (HelloAnimThreadC?.class)
What it does: Animates a small rectangle across the screen, bouncing when it hits the edges of the applet space. Uses double-buffering to smooth the animation. IMPORTANT: with such a simple, small, single-image animation, the improvement in the animation many not be obvious -- but if you add more or larger images, double-buffering GREATLY improves the look of the animation. If the cows in the round-up game were not double-buffered, you would see a flicker while each cow moved to a new location.
How to do animation in Java:
You have two choices for animation:
1) Display a series of different images (as you often do with gif animations)
2) Display just a single image, but keep updating that image's location on the screen
Of course, you can combine the two methods, as we did with the moving cows in the Roundup game... there are four different "looks" (different .gif files) for each cow, but the cows also move across the screen.
In this animation series of applets, we draw a rectangle on the screen using a method of the Graphics class, rather than drawing a .gif image.
Animation Overview for this applet:
In a permanent loop, update / change the x and y coordinates of a rectangle and then repaint it in the new location. To the user, this will appear as a moving rectangle. Paint the image to an offscreen "buffer" (a hidden Graphics object) then paint this newly-composed image to the screen in the paint method.
- Define instance variables for the rectangles size and x,y screen coordinates (which represent the top left corner of the rectangle), and the right and bottom of the applet (max width and max height). Also define instance variables for the rectangles x and y velocity. This determines how many pixels (and in which direction) the rectangle will move with each loop. Also define instance variables for double-buffering: a Graphics object (which is like the paper we'll draw onto) and another Image object, which will be the picture we'll draw onto the offscreen paper. It is the offscreen Image object that will ultimately be painted onto the screen.
- Get an offscreen Image by creating one with the same height and width as the applet.
- Get an offscreen Graphics object by calling getGraphics() on the offscreen Image object. This gives you a Graphics surface you can paint on, but it is NOT the same Graphics object you are given in your paint method. (so it is NOT the same Graphics object that will be displayed on the screen)
- Implement a run() method (did we mention that this applet implements the runnable interface? You'll see why in a minute...). This run method contains the infinite loop (while (true)) that drives the animation.
- In the run method, update the x and y instance variables so the rectangle will be drawn at a new location The x and y will also be checked for the rectangle hitting the edges of the applet. If the rectangle hits an edge, the velocity will be reversed for that dimension (i.e. if the rectangle hits the top, then the y velocity will be reversed so the rectangle will start traveling down again -- y will be incremented positively instead of negatively)
- At the end of the run method, call repaint() (a method the applet can respond to). Repaint will ultimately lead to the applet's paint() method being called. Don't call paint directly, unless you do it inside update(). Only update() will have the actual Graphics object the paint method needs.
- Override the update() method. Update is scheduled when you call repaint(). (You don't call update directly). Update normally just erases the background by filling it with the current background color. To prevent this erasing step, you can override update(), but you must still include the call to paint() which update() normally does. Otherwise, paint() won't be called when you call repaint(), since repaint() triggers update() which calls paint(). Since we have overriden update() to stop it from doing the erasing, you would expect to see a trail of rectangles (as you see in the previous applet, B) but we will take care of the erasing in the paint() method by using double-buffering.
- Override the paint() method, and give it something to do...
Be sure to create and start a Thread object at the end of the init method.
- First we have to do what the update() method usually does -- erase the background. But rather than doing this on the screen -- what the user would see -- we are doing it to the offscreen Graphics surface. The offscreen Graphics surface is what we're actually painting the full picture onto, so we have to erase this background.
- Next, we paint the picture to the offscreen Graphics object. In this applet, we are only painting one rectangle, but if there were more images in the animation, we would paint ALL Of the them to the offscreen graphics image before finally painting the fully-composed image to the screen.
- Finally, paint the completed image to the screen using the actual Graphics object passed to the paint() method (which we actually got from the update() method and passed it on to paint() when we overrode the update() method).
import java.awt.* ;
import java.applet.Applet ;
public class HelloAnimThreadC extends Applet implements Runnable
private int maxWidth , maxHeight ;
private int currX , currY ;
private int currXVelocity , currYVelocity ;
private int imageHeight , imageWidth ;
Graphics offscreen ;
Image imageForOffscreen ;
public void init()
setBackground( Color.yellow ) ;
maxWidth = 200 ;
maxHeight = 200 ;
imageHeight = 25 ;
imageWidth = 40 ;
currXVelocity = 3 ;
currYVelocity = 3 ;
currX = (int)( ( Math.random() * 80 ) + 1 );
currY = (int)( ( Math.random() * 80 ) + 10 );
imageForOffscreen = createImage( maxWidth , maxHeight );
offscreen = imageForOffscreen.getGraphics();
Thread animator = new Thread( this );
public void paint( Graphics g )
offscreen.setColor( getBackground() );
offscreen.fillRect( 0 ,0 ,maxWidth , maxHeight );
offscreen.setColor( Color.black );
offscreen.drawRect( currX , currY , imageWidth , imageHeight );
g.drawImage( imageForOffscreen , 0 , 0 , this );
public void update( Graphics g )
paint( g );
public void run()
while ( true )
Thread.sleep( 50 );
catch ( InterruptedException e )
currX = currX + currXVelocity ;
currY = currY + currYVelocity ;
if ( ( currX + imageWidth ) >= maxWidth )
currXVelocity = -3 ;
else if ( currX <= 0 )
currXVelocity = 3 ;
if ( ( currY + imageHeight ) >= maxHeight )
currYVelocity = -3 ;
else if ( currY <= 0 )
currYVelocity = 3 ;