Part 4: What About Double Buffering?
Java Game Programming Tutorial
Getting Started
To program in the Java language, you will need to install the free Java Development Kit (JDK) on your system.
You can find the latest version of the JDK, documentation, and online tutorials here at the JDK Download Site.
You will also require a Java-enabled web browser to view the tutorial examples.
My aim is not to teach every aspect of Java programming but to help those with a bit of programming knowledge with graphics and game programming issues in Java relating to web pages and the internet. I hope you find it helpful.
Download javaGameProgrammingTutorialClasses.zip to receive the following classes:
- DBuffer.class
- Flicker.class
- FXCycle.class
- FXImage.class
- Hello.class
- Lines.class
Part 4: What About Double Buffering?
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) { }
}
}
}
|