Java Programming, Lecture Notes #1464
Preface
Having fun with Java
This is one of the lessons in a miniseries that will concentrate on
having fun while programming in Java.
This miniseries will include a variety of Java programming topics that
fall in the category of fun programming. This particular lesson
is the eighth in of a group of lessons that will teach you how to write
animation programs in Java.
Animation programs
The first lesson in the group was entitled
Fun
with Java: Sprite Animation, Part 1. That lesson, plus the six
lessons following that one, provided an in-depth explanation of the use
of Java for doing sprite animation.
The previous lesson, (and the last in the series on sprite animation),
was entitled Fun with Java: Sprite Animation, Part
7.
Frame animation
In this lesson, I will move forward and teach you how to also do frame
animation using Java. In fact, I will combine sprite animation
with frame animation in this lesson.
What is the difference?
What is the difference between sprite animation and frame animation?
This is how I view the difference between the two.
With sprite animation, an image moves around on the computer screen,
but the look of that image doesn’t change over time.
With frame animation, the look of an image can change over time, whether
it moves or not.
Spherical sea creatures
In this program, I will combine the two such that spherical sea creatures
will swim around in a fish tank. Each creature will change the way
it looks over time. In particular, each creature will change its
color as it swims.
Viewing tip
You may find it useful to open another copy of this lesson in a separate
browser window. That will make it easier for you to scroll back and
forth among the different figures and listings while you are reading about
them.
Supplementary material
I recommend that you also study the other lessons in my extensive collection
of online Java tutorials. You will find those lessons published at
Gamelan.com.
However, as of the date of this writing, Gamelan doesn’t maintain a consolidated
index of my Java tutorial lessons, and sometimes they are difficult to
locate there. You will find a consolidated index at
Baldwin’s
Java Programming Tutorials.
Preview
Animation in Java
This is one of a group of lessons that will teach
you how to write animation programs in Java. These lessons will teach
you how to write sprite animation, frame animation, and a combination of
the two.
Spherical sea creatures
The first program, which was discussed in the
previous seven lessons, showed you how to use sprite animation to cause
a group of colored spherical sea creatures to swim around in a fish tank.
A screen shot of the output produced by that program is shown in Figure
1.
Figure 1. Animated spherical sea creatures
in a fish tank.
Changing color
Many sea creatures have the ability to change
their color in very impressive ways. The second program, which I
will cover in its entirety in this lesson, will simulate that process using
a combination of sprite and frame animation. (This will really
be an upgrade to the previous program, and much of the code from the previous
program will be used.)
Because a screen shot cannot represent changes
over time, the screen shot shown in Figure 1 also represents the output
from the program being discussed in this lesson.
Sea worms
The third program, to be discussed in the next
lesson, will use a combination of sprite animation, frame animation, and
some other techniques to cause a group of multi-colored sea worms to slither
around in the fish tank. In addition to slithering, the sea worms
will also change the color of different parts of their body, much like
real sea creatures.
A screen shot of the output from the third program
is shown in Figure 2.
Figure 2. Animated sea worms in a fish tank.
Figure 3 shows the GIF image files that you will need to run these three
programs.
|
Figure 3. GIF image files that you will need.
Getting the images
You should be able to capture the images by right-clicking on them individually,
and then saving them into files on your local disk. Having done that,
you will need to rename the files to match the names that are hard-coded
into the programs.
Discussion
and Sample Program
Very similar to the previous program
Although this program is quite long, most of the program is identical
to the program named Animate01, which was discussed in the previous
series of lessons.
Only discuss new material
In this lesson, I will discuss only those parts of the program that
are new or different from the previous program. However, I have provided
a copy of the entire program in Listing 8 near the end of the lesson.
You can copy it into a source file on your local disk, compile it, run
it, and see the results.
Because I will be discussing only that code that is new or different,
it may be necessary for you to go back and study the previous program named
Animate01
in order to understand this program.
The makeSprite method
This program differs from the previous program in only two areas:
- The Sprite class from which new sprites are instantiated.
-
The makeSprite method of the controlling class, which is used to
create new sprites.
Listing 1 shows an abbreviated version of the controlling class with all
of the code other than the new method named makeSprite removed for
brevity.
public class Animate02 extends Frame implements Runnable{ //All the methods in this class are // identical to those in Animate01 // except for the method named // makeSprite. //Code deleted for brevity //---------------------------------// private Sprite makeSprite( Point position, int imageIndex){ return new Sprite( this, gifImages, imageIndex, 1, rand.nextInt() % 20, position, new Point(rand.nextInt() % 5, rand.nextInt() % 5)); }//end makeSprite() //---------------------------------// //Code deleted for brevity }//end class Animate02 Listing 1 |
The primary differences
The primary difference between this program and the previous program
named Animate01 can be summarized as follows:
Each Sprite had only one image
Each Sprite object in the previous program had an instance variable
of type Image. The image whose reference was stored in the
instance variable was used to provide the visual manifestation of the sprite
on the screen. Because a given sprite owned a reference to only a
single image, that sprite always looked the same on the screen.
Each Sprite has an array of images
In this program, each Sprite object has an instance variable,
which is a reference to an array of references to Image objects.
The images stored in the Image objects are used to provide the
visual manifestation of the sprites when they appear on the screen.
However, because each sprite now owns references to more than one image,
the sprite is able to change how it looks over time by using different
images as its visual manifestation.
What is an animation frame?
Each of the images in the array is typically referred to as a frame.
The process of causing a sprite to cycle through the frames, changing the
way that it looks in the process, is often referred to as frame animation.
(Note that the use of the word frame in this context has nothing
to do with the Java class named Frame.)
The substantive changes
All of the substantive changes in this program were made in the class
named Sprite. However, because the argument list for the constructor
of the Sprite class changed (to accommodate more incoming information),
it was also necessary to modify the method named makeSprite shown
in Listing 1.
The changes that were made had to do with changes to the parameter list
for the Sprite constructor. The new parameter list includes
a reference to an array of references to Image objects
(gifImages)
along
with some other new parameters that deal with the manner in which the sprite
cycles through the frames.
The Sprite class
As before, the Sprite class is the workhorse of this animation
program.
Each of the sprites swimming around in the fish tank is an object of
the class named Sprite. As is the typical objective in object-oriented
programming, a sprite knows how to take care of itself.
Where are you?
For example, an object of the Sprite class knows how to tell
other objects about the space that it occupies in the fish tank.
What is your speed and direction?
It knows how to tell other objects about its motion vector, which determines
the speed and direction of its motion.
It knows how to use its motion vector in conjunction with a random number
generator to incrementally advance its position to the next location in
its movement through the water. In so doing, it knows how to protect
itself from excessive speed.
Oops, just hit a wall!
It knows what to do if it runs into one of the walls of the fish tank.
Basically, it bounces off the wall much like a pool ball bounces off the
cushions on a pool table. When this happens, it modifies its motion
vector accordingly.
Please draw a picture of yourself
When requested to do so, it knows how to draw itself onto a graphics
context that it receives along with the request.
Did we just collide?
When requested to do so, it can determine if it has collided with another
sprite whose reference it receives along with the request.
Change your appearance
Finally, this new and improved sprite knows how to use an array of images
to change how it looks over time.
This is a very key class insofar as this program is concerned.
The behavior of the methods in this class determines the overall behavior
of the animation process.
Discuss in fragments
As usual, I will discuss the program in fragments.
Most of the code in this revised Sprite class is identical to
the code in the Sprite class used in the previous program named
Animate01.
In keeping with the spirit of this lesson, I will not discuss the code
that I discussed in the previous lessons. Rather, for the most part,
I will discuss only that code that is new or different. When you
see //… in the code fragments, that means that code was omitted
for brevity.
The beginning of the Sprite class
Listing 2 shows the beginning of the Sprite class along with
some new or different declarations for instance variables.
class Sprite { private Image[] image; private int frame; private int frameDisplayIncrement; private int frameDisplayDuration; private int frameDurationCounter; //... Listing 2 |
An array of Image references
In the earlier version, the reference variable named image was
of type Image. However, in this version, it is of type Image[].
In other words, it previously referred to a single object of type Image.
Now it refers to an array of references to Image objects.
The constructor
Listing 3 shows the signature for the constructor. The significant
thing about Listing 3 is the makeup of the formal argument list, which
I will discuss below.
public Sprite(//constructor Component component, Image[] image, int startingFrame, int frameDisplayIncrement, int frameDisplayDuration, Point position, Point motionVector){ Listing 3 |
More parameters required
The constructor for the new Sprite class takes seven parameters,
as opposed to only four parameters for the previous version.
As before, the first parameter is a reference to a Component
object. In fact, this parameter is assumed to be a reference to the
Frame
object in which this animation is run.
This parameter is used to determine the size of the Frame.
It is also used as a required
ImageObserver in some of the methods
in the class. (I discussed image observers in an earlier lesson in this
series.)
An array of Image references
Whereas previously, the second parameter was a reference to an object
of type Image, the second parameter in the new version is a reference
to an array containing references of objects of type Image.
The images in the array are used to provide a visual manifestation for
the sprite, and that visual manifestation changes over time.
Cycling through the animation frames
The third, fourth, and fifth parameters are new to this version of the
Sprite
class. These parameters are used to determine how the sprite cycles
through the set of images in the array.
The startingFrame parameter
The parameter named startingFrame specifies the first frame to
use at the beginning of the frame animation process. In effect, this
determines the color of a sprite when it first appears on the screen.
If you examine the code in Listing 8 near the end of this lesson, you will
see that different values are used for the fifteen sprites that populate
the fish tank.
The frameDisplayIncrement parameter
The parameter named frameDisplayIncrement allows for skipping
some of the images in the array of images.
In this program, this value was set to 1 for all sprites. Thus,
when cycling through the images, each sprite uses every image as its visual
manifestation.
The frameDisplayDuration parameter
The parameter named frameDisplayDuration is used to determine
how long a given image will be used as the visual manifestation for a sprite.
In this program, this value was specified as a random number with a
maximum value of 20 for each new sprite. Thus, each sprite will cycle
through the six images in the array at a different rate.
Same as previous parameters
The sixth and seventh parameters for the new constructor are the same
as the third and fourth parameters of the constructor in the previous program.
The sixth parameter named position specifies the initial position
of the sprite.
The seventh parameter named motionVector is a motion vector,
which determines the initial speed and direction of motion for a new Sprite,
object.
Some new code
Listing 4 shows some of the code in the constructor that is new to this
version of the program.
//... frame = startingFrame; this.frameDisplayIncrement = frameDisplayIncrement; frameDisplayDuration = Math.abs(frameDisplayDuration); if(frameDisplayDuration < 5) frameDisplayDuration = 5; this.frameDisplayDuration = frameDisplayDuration; frameDurationCounter = this.frameDisplayDuration; //... Listing 4 |
The first two statements in Listing 4 simply save the incoming values
for startingFrame and frameDisplayIncrement in the appropriate
instance variables.
Duration must be positive
The code having to do with the frameDisplayDuration does two
things. First it uses the abs method of the Math class
to guarantee that the duration is a positive value.
Minimum duration is five animation cycles
Second, it guarantees that the value is at least 5. This means
that each sprite object will use an Image object as its visual manifestation
for at least five animation cycles (or about one-half second at twelve
frames per second).
In this program, this means that each sprite will remain the same color
for at least five animation cycles.
The frameDurationCounter
As you may have surmised earlier, the instance variable named frameDurationCounter
is used to determine when a sprite chooses a new Image object as
its visual manifestation (when it changes color in this program). The
value of the frameDurationCounter is initialized in the code of
Listing 4.
All of the remain code in the constructor is essentially the same as
in the previous version of the program, so I won’t discuss it here.
The incFrame method
That brings us to a method named incFrame, which is completely
new to this version of the program. The entire method is shown in
Listing 5.
private void incFrame() { if ((frameDisplayDuration > 0) && (--frameDurationCounter <= 0)){ // Reset the frame trigger frameDurationCounter = frameDisplayDuration; // Increment the frame frame += frameDisplayIncrement; if (frame >= image.length) frame = 0; else if (frame < 0) frame = image.length - 1; }//end if }//end incFrame Listing 5 |
Later we will see that this method is invoked each time the sprite is
requested to update its location on the screen (once during each animation
cycle).
Decrement the frameDurationCounter
The code in this method is fairly ugly, particularly in this narrow
publication format.
When you wade through it, you discover that it decrements the frameDurationCounter,
(which was initialized to the value of the frameDisplayDuration) each
time the incFrame method is invoked.
Reset the counter and increase the frame variable
When the counter hits zero,
- The counter is reset back to the value of the frameDisplayDuration
-
The value of the variable named frame is increased by the value
of the frameDisplayIncrement
Choose an image
Later we will see that the value of frame is used to choose an
image from the array of images as the visual manifestation of the sprite.
Bracket the value of the frame variable
Some testing is done to ensure that the value for frame is not
greater than the number of images in the array of images and is not less
than zero. If so, the value of frame is corrected appropriately.
Frame control code
This is essentially the control code that determines how the sprite
cycles through the array of images, selecting an image for its visual manifestation
during each animation cycle. There are only about two more statements
in the entire Sprite class that have anything to do with frame animation.
The first of those appears in Listing 6.
Revised updatePosition method
The code in Listing 6 shows the beginning of the revised updatePosition
method of the Sprite class.
You may recall from the previous lessons that this is the method that
the SpriteManager invokes, once during each animation cycle, to
cause each individual sprite to draw itself on the screen in its new position.
public void updatePosition() { incFrame();//increment the frame //... Listing 6 |
Incrementing the frame variable
As you can see, the first statement in this new version of the method
invokes the incFrame method discussed above to cause the value of
the variable named frame to change when appropriate.
As you can also see, a great deal of code that is essentially the same
as in the previous program was deleted for brevity, taking us all the way
down to the revised method named drawSpriteImage shown in Listing
7.
The revised drawSpriteImage method
As you may recall from the previous lessons, the drawSpriteImage
method is the method that the SpriteManager invokes on each Sprite
object to cause the sprite to draw itself onto the offscreen graphics context
once during each animation cycle
public void drawSpriteImage( Graphics g){ g.drawImage(image[frame], spaceOccupied.x, spaceOccupied.y, component); }//end drawSpriteImage() Listing 7 |
What is the difference?
The difference between the new version of the drawSpriteImage
method and the previous version is so important that I have provided the
previous version in a different color in Listing 8 below for comparison.
public void drawSpriteImage( Graphics g){ g.drawImage(image, spaceOccupied.x, spaceOccupied.y, component); }//end drawSpriteImage() //--NOTE: THIS IS CODE FROM AN EARLIER PROGRAM-- Listing 8 |
A reference to an Image object
The thing to note in these two listings is the invocation of the drawImage
method. In both versions, the first parameter to the drawImage
method is a reference to the Image object that will be rendered
onto the screen as the visual manifestation of the sprite.
One Image object available
However, in the previous version of the program, this is a reference
to a single Image object passed into the Sprite constructor
when the Sprite object was instantiated.
A choice of Image objects
In the new version, this is a reference extracted from an array of references
using the frame variable as an index into the array.
The reference to the array of Image objects was received as an
incoming parameter to the constructor when the Sprite object was
instantiated. In the new version, the incFrame method discussed
earlier is used to establish the value stored in frame, which identifies
the Image object that is used to provide the visual manifestation
for the sprite during each animation cycle.
Six colored spheres
In this program, the array contains references to images of six small
spheres of different colors. As the animation progresses and the
incFrame
method controls the value of the variable named frame, different
colored spheres are selected as the visual manifestation of a sprite, and
the sprites appear to change color over time.
The remaining code in the Sprite class was discussed in earlier
lessons and therefore won’t be discussed further here.
Summary
In this lesson, I have taught you how to achieve frame animation in Java.
I accomplished this by showing you how to upgrade the sprite animation
program from the earlier lessons into a new program that provides both
sprite animation and frame animation.
What’s Next?
In the next lesson in this series, I will provide an additional upgrade
to convert the spherical sea creatures into sea worms that have the ability
to slither around in the fish tank and to change the colors of different
parts of their bodies in a random fashion as they slither.
Complete Program Listing
A complete listing of the program is provided in Listing
8.
/*File Animate02.java Copyright 2001, R.G.Baldwin This program displays several animated colored spherical sea creatures swimming around in an aquarium. Each creature maintains generally the same course until it collides with another creature or with a wall. However, the creatures have the ability to change course based on the addition or subtraction of random values from the components of their motion vector about once in every ten updates. In addition, each creature uses frame animation to change colors in the order red, green blue, yellow, purple, orange. Each creature switches among the colors on a periodic rate, but the period may be different for each creature. Thus, each creature cycles through the same colors, but remains a particular color for a different amount of time than the other creatures. The amount of time that a creature remains a particular color is based on a random number between 5 and 20 updates. The primary changes in this program relative to the program named Animate01 were made in the Sprite class, and in calls to the constructor for the Sprite class in the makeSprite() method. **************************************/ import java.awt.*; import java.awt.event.*; import java.util.*; public class Animate02 extends Frame implements Runnable{ //All the methods in this class are // identical to those in Animate01 // except for the methodnamed // makeSprite. private Image offScreenImage; private Image backGroundImage; private Image[] gifImages = new Image[6]; //offscreen graphics context private Graphics offScreenGraphicsCtx; private Thread animationThread; private MediaTracker mediaTracker; private SpriteManager spriteManager; //Animation display rate, 12fps private int animationDelay = 83; private Random rand = new Random(System. currentTimeMillis()); public static void main( String[] args){ new Animate02(); }//end main //---------------------------------// Animate02() {//constructor // Load and track the images mediaTracker = new MediaTracker(this); //Get and track the background // image backGroundImage = Toolkit.getDefaultToolkit(). getImage("background02.gif"); mediaTracker.addImage( backGroundImage, 0); //Get and track 6 images to use // for sprites gifImages[0] = Toolkit.getDefaultToolkit(). getImage("redball.gif"); mediaTracker.addImage( gifImages[0], 0); gifImages[1] = Toolkit.getDefaultToolkit(). getImage("greenball.gif"); mediaTracker.addImage( gifImages[1], 0); gifImages[2] = Toolkit.getDefaultToolkit(). getImage("blueball.gif"); mediaTracker.addImage( gifImages[2], 0); gifImages[3] = Toolkit.getDefaultToolkit(). getImage("yellowball.gif"); mediaTracker.addImage( gifImages[3], 0); gifImages[4] = Toolkit.getDefaultToolkit(). getImage("purpleball.gif"); mediaTracker.addImage( gifImages[4], 0); gifImages[5] = Toolkit.getDefaultToolkit(). getImage("orangeball.gif"); mediaTracker.addImage( gifImages[5], 0); //Block and wait for all images to // be loaded try { mediaTracker.waitForID(0); }catch (InterruptedException e) { System.out.println(e); }//end catch //Base the Frame size on the size // of the background image. //These getter methods return -1 if // the size is not yet known. //Insets will be used later to // limit the graphics area to the // client area of the Frame. int width = backGroundImage.getWidth(this); int height = backGroundImage.getHeight(this); //While not likely, it may be // possible that the size isn't // known yet. Do the following // just in case. //Wait until size is known while(width == -1 || height == -1){ System.out.println( "Waiting for image"); width = backGroundImage. getWidth(this); height = backGroundImage. getHeight(this); }//end while loop //Display the frame setSize(width,height); setVisible(true); setTitle( "Copyright 2001, R.G.Baldwin"); //Create and start animation thread animationThread = new Thread(this); animationThread.start(); //Anonymous inner class window // listener to terminate the // program. this.addWindowListener( new WindowAdapter(){ public void windowClosing( WindowEvent e){ System.exit(0);}}); }//end constructor //---------------------------------// public void run() { //Create and add sprites to the // sprite manager spriteManager = new SpriteManager( new BackgroundImage( this, backGroundImage)); //Create 15 sprites from 6 gif // files. for (int cnt = 0; cnt < 15; cnt++){ Point position = spriteManager. getEmptyPosition(new Dimension( gifImages[0].getWidth(this), gifImages[0]. getHeight(this))); spriteManager.addSprite( makeSprite(position, cnt % 6)); }//end for loop //Loop, sleep, and update sprite // positions once each 83 // milliseconds long time = System.currentTimeMillis(); while (true) {//infinite loop spriteManager.update(); repaint(); try { time += animationDelay; Thread.sleep(Math.max(0,time - System.currentTimeMillis())); }catch (InterruptedException e) { System.out.println(e); }//end catch }//end while loop }//end run method //---------------------------------// private Sprite makeSprite( Point position, int imageIndex){ return new Sprite( this, gifImages, imageIndex, 1, rand.nextInt() % 20, position, new Point(rand.nextInt() % 5, rand.nextInt() % 5)); }//end makeSprite() //---------------------------------// //Overridden graphics update method // on the Frame public void update(Graphics g) { //Create the offscreen graphics // context if (offScreenGraphicsCtx == null) { offScreenImage = createImage(getSize().width, getSize().height); offScreenGraphicsCtx = offScreenImage.getGraphics(); }//end if // Draw the sprites offscreen spriteManager.drawScene( offScreenGraphicsCtx); // Draw the scene onto the screen if(offScreenImage != null){ g.drawImage( offScreenImage, 0, 0, this); }//end if }//end overridden update method //---------------------------------// //Overridden paint method on the // Frame public void paint(Graphics g) { //Nothing required here. All // drawing is done in the update // method above. }//end overridden paint method }//end class Animate02 //===================================// class BackgroundImage{ //This class is identical to that // used in Animate01 private Image image; private Component component; private Dimension size; public BackgroundImage( Component component, Image image) { this.component = component; size = component.getSize(); this.image = image; }//end construtor public Dimension getSize(){ return size; }//end getSize() public Image getImage(){ return image; }//end getImage() public void setImage(Image image){ this.image = image; }//end setImage() public void drawBackgroundImage( Graphics g) { g.drawImage( image, 0, 0, component); }//end drawBackgroundImage() }//end class BackgroundImage //===================================// class SpriteManager extends Vector { //This class is identical to that // used in Animate01 private BackgroundImage backgroundImage; public SpriteManager( BackgroundImage backgroundImage) { this.backgroundImage = backgroundImage; }//end constructor //---------------------------------// public Point getEmptyPosition( Dimension spriteSize){ Rectangle trialSpaceOccupied = new Rectangle(0, 0, spriteSize.width, spriteSize.height); Random rand = new Random( System.currentTimeMillis()); boolean empty = false; int numTries = 0; // Search for an empty position while (!empty && numTries++ < 100){ // Get a trial position trialSpaceOccupied.x = Math.abs(rand.nextInt() % backgroundImage. getSize().width); trialSpaceOccupied.y = Math.abs(rand.nextInt() % backgroundImage. getSize().height); // Iterate through existing // sprites, checking if position // is empty boolean collision = false; for(int cnt = 0;cnt < size(); cnt++){ Rectangle testSpaceOccupied = ((Sprite)elementAt(cnt)). getSpaceOccupied(); if (trialSpaceOccupied. intersects( testSpaceOccupied)){ collision = true; }//end if }//end for loop empty = !collision; }//end while loop return new Point( trialSpaceOccupied.x, trialSpaceOccupied.y); }//end getEmptyPosition() //---------------------------------// public void update() { Sprite sprite; //Iterate through sprite list for (int cnt = 0;cnt < size(); cnt++){ sprite = (Sprite)elementAt(cnt); //Update a sprite's position sprite.updatePosition(); //Test for collision. Positive // result indicates a collision int hitIndex = testForCollision(sprite); if (hitIndex >= 0){ //a collision has occurred bounceOffSprite(cnt,hitIndex); }//end if }//end for loop }//end update //---------------------------------// private int testForCollision( Sprite testSprite) { //Check for collision with other // sprites Sprite sprite; for (int cnt = 0;cnt < size(); cnt++){ sprite = (Sprite)elementAt(cnt); if (sprite == testSprite) //don't check self continue; //Invoke testCollision method // of Sprite class to perform // the actual test. if (testSprite.testCollision( sprite)) //Return index of colliding // sprite return cnt; }//end for loop return -1;//No collision detected }//end testForCollision() //---------------------------------// private void bounceOffSprite( int oneHitIndex, int otherHitIndex){ //Swap motion vectors for // bounce algorithm Sprite oneSprite = (Sprite)elementAt(oneHitIndex); Sprite otherSprite = (Sprite)elementAt(otherHitIndex); Point swap = oneSprite.getMotionVector(); oneSprite.setMotionVector( otherSprite.getMotionVector()); otherSprite.setMotionVector(swap); }//end bounceOffSprite() //---------------------------------// public void drawScene(Graphics g){ //Draw the background and erase // sprites from graphics area //Disable the following statement // for an interesting effect. backgroundImage. drawBackgroundImage(g); //Iterate through sprites, drawing // each sprite for (int cnt = 0;cnt < size(); cnt++) ((Sprite)elementAt(cnt)). drawSpriteImage(g); }//end drawScene() //---------------------------------// public void addSprite(Sprite sprite){ add(sprite); }//end addSprite() }//end class SpriteManager //===================================// class Sprite { private Component component; private Image[] image; private Rectangle spaceOccupied; private Point motionVector; private Rectangle bounds; private Random rand; private int frame; private int frameDisplayIncrement; private int frameDisplayDuration; private int frameDurationCounter; public Sprite(//constructor Component component, Image[] image, int startingFrame, int frameDisplayIncrement, int frameDisplayDuration, Point position, Point motionVector){ //Seed a random number generator // for this sprite with the sprite // position. rand = new Random(position.x); frame = startingFrame; this.frameDisplayIncrement = frameDisplayIncrement; frameDisplayDuration = Math.abs(frameDisplayDuration); if(frameDisplayDuration < 5) frameDisplayDuration = 5; this.frameDisplayDuration = frameDisplayDuration; frameDurationCounter = this.frameDisplayDuration; this.component = component; this.image = image; setSpaceOccupied(new Rectangle( position.x, position.y, image[0].getWidth(component), image[0].getHeight(component))); this.motionVector = motionVector; //Compute edges of usable graphics // area int topBanner = ( (Container)component). getInsets().top; int bottomBorder = ( (Container)component). getInsets().bottom; int leftBorder = ( (Container)component). getInsets().left; int rightBorder = ( (Container)component). getInsets().right; bounds = new Rectangle( 0 + leftBorder, 0 + topBanner, component.getSize().width - (leftBorder + rightBorder), component.getSize().height - (topBanner + bottomBorder)); }//end constructor //---------------------------------// public Rectangle getSpaceOccupied(){ return spaceOccupied; }//end getSpaceOccupied() //---------------------------------// void setSpaceOccupied( Rectangle spaceOccupied){ this.spaceOccupied = spaceOccupied; }//setSpaceOccupied() //---------------------------------// public void setSpaceOccupied( Point position){ spaceOccupied.setLocation( position.x, position.y); }//setSpaceOccupied() //---------------------------------// public Point getMotionVector(){ return motionVector; }//end getMotionVector() //---------------------------------// public void setMotionVector( Point motionVector){ this.motionVector = motionVector; }//end setMotionVector() //---------------------------------// public void setBounds( Rectangle bounds){ this.bounds = bounds; }//end setBounds() //---------------------------------// private void incFrame() { if ((frameDisplayDuration > 0) && (--frameDurationCounter <= 0)){ // Reset the frame trigger frameDurationCounter = frameDisplayDuration; // Increment the frame frame += frameDisplayIncrement; if (frame >= image.length) frame = 0; else if (frame < 0) frame = image.length - 1; }//end if }//end incFrame //---------------------------------// public void updatePosition() { incFrame();//increment the frame Point position = new Point( spaceOccupied.x, spaceOccupied.y); //Insert random behavior. During // each update, a sprite has about // one chance in 10 of making a // small random change to its // motionVector. When a change // occurs, the motionVector // coordinate values are forced to // fall between -7 and 7. if(rand.nextInt() % 10 == 0){ Point randomOffset = new Point(rand.nextInt() % 3, rand.nextInt() % 3); motionVector.x += randomOffset.x; if(motionVector.x >= 7) motionVector.x -= 7; if(motionVector.x <= -7) motionVector.x += 7; motionVector.y += randomOffset.y; if(motionVector.y >= 7) motionVector.y -= 7; if(motionVector.y <= -7) motionVector.y += 7; }//end if //Make the move position.translate( motionVector.x, motionVector.y); //Bounce off the walls boolean bounceRequired = false; Point tempMotionVector = new Point(motionVector.x, motionVector.y); //Handle walls in x-dimension if (position.x < bounds.x) { bounceRequired = true; position.x = bounds.x; //reverse direction in x tempMotionVector.x = -tempMotionVector.x; }else if((position.x + spaceOccupied.width) > (bounds.x + bounds.width)){ bounceRequired = true; position.x = bounds.x + bounds.width - spaceOccupied.width; //reverse direction tempMotionVector.x = -tempMotionVector.x; }//end else if //Handle walls in y-dimension if (position.y < bounds.y){ bounceRequired = true; position.y = bounds.y; tempMotionVector.y = -tempMotionVector.y; }else if ((position.y + spaceOccupied.height) > (bounds.y + bounds.height)){ bounceRequired = true; position.y = bounds.y + bounds.height - spaceOccupied.height; tempMotionVector.y = -tempMotionVector.y; }//end else if //save new motionVector if (bounceRequired) setMotionVector(tempMotionVector); //update spaceOccupied setSpaceOccupied(position); }//end updatePosition() //---------------------------------// public void drawSpriteImage( Graphics g){ g.drawImage(image[frame], spaceOccupied.x, spaceOccupied.y, component); }//end drawSpriteImage() //---------------------------------// public boolean testCollision( Sprite testSprite){ // Check for collision with another // sprite if (testSprite != this){ return spaceOccupied.intersects( testSprite.getSpaceOccupied()); }//end if return false; }//end testCollision }//end Sprite class Listing 8 |
About the author
Richard Baldwin
is a college professor (at Austin Community College in Austin, TX) and
private consultant whose primary focus is a combination of Java and XML.
In addition to the many platform-independent benefits of Java applications,
he believes that a combination of Java and XML will become the primary
driving force in the delivery of structured information on the Web.
Richard has participated in numerous consulting projects involving
Java, XML, or a combination of the two. He frequently provides onsite
Java and/or XML training at the high-tech companies located in and around
Austin, Texas. He is the author of Baldwin’s Java Programming Tutorials,
which has gained a worldwide following among experienced and aspiring Java
programmers. He has also published articles on Java Programming in Java
Pro magazine.
Richard holds an MSEE degree from Southern Methodist University and
has many years of experience in the application of computer technology
to real-world problems.