Now we have images loaded and doing something the next topic
on the
agenda is flicker. Although simplistic animations or
slideshows run fairly
well, as more drawing is done to the
viewing window in advanced applets,
one can begin to see the
screen refresh causing a flicker.
I would say this is the bane of the animator or game developer
because
many solutions exist on a variety of platforms to speed
up rendering.
Either built into the hardware or programmed in
the software, the most
common solution is double buffering.
Double buffering is the process of storing a copy of the screen
in a
section of memory and doing all drawing to this canvas as
if it were the
screen. Since all drawing is being done off the
screen, the only drawing
onscreen is the actual copying of the
buffer to the screen which can be
timed to avoid the refresh.
Some systems or environments have automatic buffering which
means little
or no programming. My experience in low level
DOS has taught me this can
mean extensive coding for speedy
animation. Thankfully, in Java it is
really only a matter of
redirection from what we've doing so far.
These lessons are compressed to be building blocks for bigger
and better
applets. Otherwise, there might be ten versions of
Hello world (yikes!) and
I refuse to insult your intelligence
this way. At this juncture, I must
cram more information about
sprite images relevant to the example.
A good thing about gif files is that you can make transparent
gifs quite
easily using many paint programs and image utilities.
This fact allows gif
images which aren't rectangular to keep
their fine figure and blend in with
whatever background we like.
In Part
3, an image array was created to load in each sketch
or picture. This
is alright for a slideshow of variable images
but bad in the case of small
identical images. Bad because each
image requires an established HTTP
connection which can be slow.
A good solution is to store your images side
by side like so:
Only a single image connection is required for this image strip
which
can be drawn easily with a slight adjustment. Since the
previous tutorial
sections cover applet structure and graphics
techniques, I will just
elaborate on new or changed code.
Image offscreenImage; Graphics offscreenGraphics;
These two objects are added to the applet to implement double
buffering.
The first is the familiar image object which serves
as the image buffer
itself. The second is a graphics context
or handle for reference to the
image buffer.
Image imageBackground; Image spriteStrip;
These are just less important images for drawing. The first is
a
background image to make a backdrop for the sprite. The next
is the sprite
image strip. I've decided to omit explaining the
init method. Image loading
was discussed in Part 3.
offscreenImage = createImage(this.size().width, this.size().height); offscreenGraphics = offscreenImage.getGraphics();
This conditional initialization code is added to the paint method.
The
method createImage is used to create an offscreen buffer image
the size of
the applet window. The image method getGraphics is used
to get a graphics
context. This is excellent because we can draw to
the double buffer exactly
as if it were the screen.
spriteWidth = spriteStrip.getWidth(this) / spriteCount; spriteHeight = spriteStrip.getHeight(this);
Here, the variable spriteCount is used to calculate the
independent
image width, assuming each is the same width. The height is
the
same as the strip height because it is a row of images.
offscreenGraphics.drawImage(imageBackground,0,0,this);
This is the same method used in Part 3
but note how our own graphics
context is used. We could draw lines or
circles, etc. In this case,
our background image is copied into the
offscreen buffer. Redrawing
the background every frame is rather
inefficient but I'll go over a
better design later. For this purpose, it's
okay.
offscreenGraphics.clipRect(spriteX,spriteY, spriteWidth,spriteHeight);
This code above makes a clipping window in our offscreen buffer in
the
position of our sprite image. We want it to be the size of only
a single
image frame. This way, the picture we want is drawn and not
the entire
image strip.
offscreenGraphics.drawImage(spriteStrip, spriteX+(-spriteIndex * spriteWidth), spriteY,this);
Drawing the sprite is the same as drawing any image. Note above that
the
only difference is the x coordinate. We are shifting the strip of
images
left to get the image we want. If we didn't create a clipping
rectangle
first, the image would just move left. The image strip is
like a large
banner being carried back and forth behind a small window.
Considering the
image download speed increases, this is worthwhile.
g.drawImage(offscreenImage,0,0,this);
And finally, here is the only call to the screen graphics context
which
draws the updated offscreen buffer just like any normal image.
Here's the HTML parameters:
<APPLET CODE="DBuffer.class" WIDTH=104 HEIGHT=64>
<PARAM
NAME="SPRITEDELAY" VALUE="200">
<PARAM NAME="SPRITEX"
VALUE="45">
<PARAM NAME="SPRITEY" VALUE="20">
<PARAM
NAME="SPRITECOUNT" VALUE="4">
<PARAM NAME="SPRITENAME"
VALUE="guy">
<PARAM NAME="BACKGROUNDNAME"
VALUE="skullbg">
</APPLET>
The example applet parameters allow you to insert your own
background
image and animated character at an (x,y) position within the
applet
window. If you alter the applet, you can make your character
walk
around in front of a logo by changing spriteX and spriteY in
the
run method infinite loop. Below is the entire Java code. Enjoy!
// DBuffer.java // by Garry Morse import java.awt.*; import java.applet.*; public class DBuffer extends Applet implements Runnable { Thread spriteThread; MediaTracker tracker; Image offscreenImage; Graphics offscreenGraphics; Image imageBackground; Image spriteStrip; int spriteDelay=0, spriteCount=0, spriteIndex=0; int spriteWidth=0,spriteHeight=0; int spriteX=0,spriteY=0; String paramString; public void init() { // create a media tracker object for this applet tracker = new MediaTracker(this); // get parameter for wait spriteDelay between images paramString = getParameter("SPRITEDELAY"); spriteDelay = Integer.parseInt(paramString); // get parameter for number of separate images in strip paramString = getParameter("SPRITECOUNT"); spriteCount = Integer.parseInt(paramString); // get parameter for x position of sprite paramString = getParameter("SPRITEX"); spriteX = Integer.parseInt(paramString); // get parameter for y position of sprite paramString = getParameter("SPRITEY"); spriteY = Integer.parseInt(paramString); // get parameter for sprite image file and load strip paramString = getParameter("SPRITENAME"); spriteStrip = getImage(getCodeBase(),paramString + ".gif"); // get parameter for background image file and load image paramString = getParameter("BACKGROUNDNAME"); imageBackground = getImage(getCodeBase(),paramString + ".gif"); // add images to monitored images with IDs tracker.addImage(spriteStrip,0); tracker.addImage(imageBackground,1); // load images into memory tracker.checkID(0,true); tracker.checkID(1,true); repaint(); } public void start() { if(spriteThread==null) { spriteThread = new Thread(this); spriteThread.start(); } } public void stop() { if(spriteThread!=null) { spriteThread.stop(); spriteThread = null; } } public void update(Graphics g) { paint(g); } public void paint(Graphics g) { // if images not loaded yet, inform user if(!tracker.checkAll()) { g.drawString("loading images...", 20,this.size().height/2); } else { if(offscreenImage==null) { // set up double buffer offscreenImage = createImage(this.size().width, this.size().height); offscreenGraphics = offscreenImage.getGraphics(); } // get dimensions of sprite spriteWidth = spriteStrip.getWidth(this) / spriteCount; spriteHeight = spriteStrip.getHeight(this); // draw background and sprite in offscreen buffer offscreenGraphics.drawImage(imageBackground,0,0,this); offscreenGraphics.clipRect(spriteX,spriteY, spriteWidth,spriteHeight); offscreenGraphics.drawImage(spriteStrip, spriteX+(-spriteIndex * spriteWidth), spriteY,this); // copy offscreen buffer to screen g.drawImage(offscreenImage,0,0,this); } } public void run() { while(true) { repaint(); // reloop through list of images to display if(++spriteIndex >= spriteCount) spriteIndex=0; // try to shut down spriteThread for milliseconds try { spriteThread.sleep(spriteDelay+1); } catch(InterruptedException e) { } } } }
Click here to see the Double Buffer applet in action!
The Java Game Programming Tutorial and all tutorials within are created by Garry Morse, Copyright 1997