Defining the Bat


The Ball Sprite

I begin with a textual specification of what a ball sprite should do, then I will translate this into a statechart, and then manually convert it into BallSprite, a subclass of Sprite.

Textual Specification

In the following discussion, I'll refer to the ant sprite as the bat, since that's its function: the ant is used to knock away balls, preventing them from reaching the floor.

The ball drops from the top of the panel at varying speeds and angles of trajectory. It'll bounce off a wall if it hits one, reversing its x-axis direction. If the ball hits the bat, it will rebound and disappear off the top of the panel. If the ball passes the bat, it will disappear through the panel's base.

Remember, bat refers to the antwhich could be drawn as anything, but always functions as a bat.


After leaving the panel, the ball is reused: it's placed back at the top of the panel and put into motion again. The image associated with the ball is changed.

The number of returned balls is incremented when the ball bounces off the bat (numRebounds is incremented). When the ball drops off the bottom, numRebounds is decremented. If numRebounds reaches MAX_BALLS_RETURNED, the game is over. If numRebounds reaches 0, the game also terminates.

Sound effects are played when the ball hits the walls and the bat.

Statechart Specification

The statechart in Figure 11-5 specifies the actions of the ball sprite.

Figure 11-5. The BallSprite statechart


The statechart uses an update superstate, which is a state that encapsulates the states that modify the sprite's location, step sizes, and other values before the ball is moved and drawn. The update superstate highlights the sprite's update/draw cycle, which is driven by the method calls updateSprite( ) and drawSprite( ), originating from BugPanel's animation loop.

A do/ activity inside a state is carried out as the sprite occupies that state.

The "do" is what the state "does" when execution is in that state.


The examining environment state deals with the unusual situations that may occur as the ball descends through the JPanel. The transitions leaving examining environment are triggered by tests on the sprite's current location. Hitting the left or right walls causes a change in direction along the x-axis. Moving past the top or bottom edges of the panel places the ball in a finishing state. This may result in the sprite notifying the BugPanel object (bp) that the game is over or the ball may be reused. When none of the special environmental conditions apply, the sprite is moved and redrawn.

The speaker icons next to the [hit bat], [hit right wall], and [hit left wall] conditional transitions indicate that a sound effect will be played when the condition evaluates to true.

Translating the Statechart

The initialize state is covered by BallSprite's constructor:

     // images used for the balls     private static final String[] ballNames =                 {"rock1", "orangeRock", "computer", "ball"};     // reach this number of balls to end the game     private static final int MAX_BALLS_RETURNED = 16;     // globals     private ClipsLoader clipsLoader;     private BugPanel bp;     private BatSprite bat;     private int numRebounds;     public BallSprite(int w, int h, ImagesLoader imsLd, ClipsLoader cl,                                               BugPanel bp, BatSprite b)     { super( w/2, 0, w, h, imsLd, ballNames[0]);         // the ball is positioned in the middle at the top of the panel       clipsLoader = cl;       this.bp = bp;       bat = b;       nameIndex = 0;       numRebounds = MAX_BALLS_RETURNED/2;           // the no. of returned balls starts half way to the maximum       initPosition( );     }

The names of four ball images are fixed in ballNames[]; they are the names of four GIF files stored in the Images/ subdirectory.

BallSprite stores a reference to the BugPanel so it can notify the panel when the game is over. The BatSprite reference is used for collision detection (carried out by the [hit bat] condition for the examining environment). The ClipsLoader reference enables the sprite to play sounds when it hits the bat or rebounds from the walls.

The numRebounds variable performs the same task as the variable in the statechart. It begins with a value halfway between 0 and the maximum number of returns required for winning the game.

initPosition( ) initializes the ball's image, position and step values:

     private void initPosition( )     {       setImage( ballNames[nameIndex]);       nameIndex = (nameIndex+1)%ballNames.length;       setPosition( (int)(getPWidth( ) * Math.random( )), 0);                                     // somewhere along the top       int step = STEP + getRandRange(STEP_OFFSET);       int xStep = ((Math.random( ) < 0.5) ? -step : step);                                       // move left or right       setStep(xStep, STEP + getRandRange(STEP_OFFSET));   // move down     }     private int getRandRange(int x)     // random number generator between -x and x     {   return ((int)(2 * x * Math.random( ))) - x;  }

setImage( ), setPosition( ), and setStep( ) are all methods inherited from Sprite.

Updating the sprite

The update superstate is represented by an overridden updateSprite( ):

     public void updateSprite( )     {       hasHitBat( );       goneOffScreen( );       hasHitWall( );       super.updateSprite( );     }

The calls to hasHitBat( ), goneOffScreen( ), and hasHitWall( ) roughly correspond to the examining environment, finishing, and reinitialize states and transitions, while the call to Sprite's updateSprite( ) implements the move state.

The looping behavior of the examining environment has been simplified to a sequential series of tests. This is possible since the special conditions (e.g., hit a wall, hit the bat) don't occur more than once during a single update and are independent of each other. However, such optimizations should be done carefully to avoid changing the sprite's intended behavior.

hasHitBat( ) implements the [hit bat] conditional transition:

     private void hasHitBat( )     {       Rectangle rect = getMyRectangle( );       if (rect.intersects( bat.getMyRectangle( ) )) {  // bat collision?         clipsLoader.play("hitBat", false);         Rectangle interRect = rect.intersection(bat.getMyRectangle( ));         dy = -dy;       // reverse ball's y-step direction         locy -= interRect.height;    // move the ball up       }     }

Collision detection is a matter of seeing if the bounding boxes for the ball and the bat intersect. A sound effect is played if they overlap and the ball is made to bounce by having its y-step reversed.

The ball's y-axis location is moved up slightly so it no longer intersects the bat. This rules out the (slim) possibility that the collision test of the ball and bat during the next update will find them still overlapping. This would occur if the rebound velocity were too small to separate the objects within one update.

The collision algorithm could be improved. For instance, some consideration could be given to the relative positions and speeds of the ball and bat to determine the direction and speed of the rebound. This would complicate the coding but improve the ball's visual appeal.


Hitting a wall

hasHitWall( ) handles the [hit right wall] and [hit left wall] conditional transitions:

     private void hasHitWall( )     {       if ((locx <= 0) && (dx < 0)) {  // touching lhs and moving left         clipsLoader.play("hitLeft", false);         dx = -dx;   // move right       }       else if ((locx+getWidth( ) >= getPWidth( )) && (dx > 0)) {                                   // touching rhs and moving right         clipsLoader.play("hitRight", false);         dx = -dx;   // move left       }     }

hasHitWall( ) is made easier to understand by having it directly referring to the sprite's x-axis location (locx) and step sizes (dx and dy). They are protected variables, inherited from Sprite.

The if tests illustrate a common form of testing: a combination of location and direction tests. It isn't enough to examine the sprite's location; you must determine whether the sprite is heading out of the panel. This will correctly exclude a recently rebounded sprite, which is still near an edge but moving away from it.

A subtle coding assumption is that the boundaries of the BufferedImage correspond to the visible edges of the image on the screen. For example, a sprite surrounded by a large transparent border, as shown in Figure 11-6, would not seem to be touching the panel's left edge when its top-left x-coordinate (locx) is at pixel 0.

Figure 11-6. Image with a large transparent border


The simplest solution is to ensure that images are cropped to have as small a transparent border as possible. Alternatively, bounding box calculations could use a reduction factor to shrink the bounding region.

Leaving the screen

goneOffScreen( ) implements the [off top] and [off bottom] conditional transitions and implements the finishing and re-initialize states connected to them:

     private void goneOffScreen( )     {       if (((locy+getHeight( )) <= 0) && (dy < 0)) {         numRebounds++;      // off top and moving up         if (numRebounds == MAX_BALLS_RETURNED)           bp.gameOver( );    // finish         else           initPosition( );   // start the ball in a new position       }       else if ((locy >= getPHeight( )) && (dy > 0)) {         numRebounds--;      // off bottom and moving down         if (numRebounds == 0)           bp.gameOver( );         else           initPosition( );       }     }

The finishing state and its exiting transitions have been mapped to the bodies of the if tests. The reinitialize state has been implemented by reusing initPosition( ), which is part of the initialize code.

Drawing the sprite

The draw state in BallSprite's statechart has no equivalent method in the BallSprite class. It's handled by the inherited drawSprite( ) method from Sprite.



Killer Game Programming in Java
Killer Game Programming in Java
ISBN: 0596007302
EAN: 2147483647
Year: 2006
Pages: 340

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net