Chapter 12. A Side-Scroller


Defining the Bat

I'll define and implement the bat in the same way as the ball sprite. I'll write a textual specification of what a bat should do, translate it into a statechart, and then manually convert it into BatSprite, a subclass of Sprite. Once again, I'll refer to the ant sprite as a bat since that's what it does; that's why its class is BatSprite. The ant image is irrelevant to the game play since it could be anything.

Textual Specification

The bat can only move horizontally across the floor, controlled by arrow keys and mouse presses. Once the bat is set in motion, it continues moving until its direction is changed or it is stopped. As the bat leaves one side of the panel, it appears on the other side. The bat is assigned a left-facing and right-facing set of images (a walking ant), which are cycled through as the bat moves.

Statechart Specification

A statechart for BatSprite's actions appears in Figure 11-7.

The new statechart element in Figure 11-7 is the concurrent state diagram with two concurrent sections: user-based reactions and time-based reactions. Concurrent state diagrams allow the modeling of concurrent activities using this section notation. Concurrent activities are present in all sprites controlled by the user.

The time-based reactions section illustrates the update/draw cycle carried out by the animation loop. The user-based reactions section encapsulates the changes made to the sprite by the user pressing the arrow keys and/or the mouse. A move right transition occurs when the user presses the right arrow key or clicks the mouse to the right of the bat. The move left transition handles the left arrow key and a mouse press to the left of the bat. The stop transition deals with the down arrow key and a mouse press over the bat. The implementation of user-based reactions uses listener methods, which are processed in Swing's event thread. This means there's no need for explicit threaded coding in the BatSprite class.

Figure 11-7. The BatSprite statechart


The statechart nicely highlights an important issue with user-controlled sprites: the concurrent sharing of data between the animation loop and the event processing code. The statechart shows that the shared data will be the sprite's x-axis step size (dx) and the current image.

Translating the Statechart

The initialize state is covered by BatSprite's constructor:

     // globals     private static final int FLOOR_DIST = 41;         // distance of ant's top from the floor     private int period;      /* in ms. The game's animation period used by the image         cycling of the bat's left and right facing images. */     public BatSprite(int w, int h, ImagesLoader imsLd, int p)     {       super( w/2, h-FLOOR_DIST, w, h, imsLd, "leftBugs2");           // positioned at the bottom of the panel, near the center       period = p;       setStep(0,0);  // no movement     }

User-based reactions

The key presses that trigger move left, move right, and stop events are caught by BugPanel's key listener, which calls processKey( ). Inside processKey( ), the code for responding to the arrow keys is:

     if (!isPaused && !gameOver) {       if (keyCode == KeyEvent.VK_LEFT)              bat.moveLeft( );       else if (keyCode == KeyEvent.VK_RIGHT)              bat.moveRight( );       else if (keyCode == KeyEvent.VK_DOWN)              bat.stayStill( );     }

moveLeft( ) implements the moving left state in Figure 11-7:

     // global     private static final int XSTEP = 10;        // step distance for moving along x-axis     public void moveLeft( )     { setStep(-XSTEP, 0);       setImage("leftBugs2");       loopImage(period, DURATION);   // cycle through leftBugs2 images     }

leftBugs2 is the name for a GIF file in Images/, which contains an image strip of ants walking to the left.


moveRight( ) handles the moving right state in Figure 11-7:

     public void moveRight( )     { setStep(XSTEP, 0);       setImage("rightBugs2");       loopImage(period, DURATION);  // cycle through the images     }

The stationary state is encoded by stayStill( ):

     public void stayStill( )     { setStep(0, 0);       stopLooping( );     }

This translation of the statechart is possible because of a property of the events. A move left event always enters the moving left state, a move right event always enters moving right, and stop always goes to the stationary state. This means that I don't need to consider the current state to determine the next state when a given event arrives; the next state is always determined solely by the event.

Mouse responses

Move left, move right, and stop events can be triggered by mouse actions. BugPanel employs a mouse listener to call testPress( ) when a mouse press is detected:

     private void testPress(int x)     { if (!isPaused && !gameOver)     bat.mouseMove(x);     }

BatSprite's mouseMove( ) calls one of its move methods depending on the cursor's position relative to the bat:

     public void mouseMove(int xCoord)     {       if (xCoord < locx)  // click was to the left of the bat         moveLeft( );       // make the bat move left       else if (xCoord > (locx + getWidth( )))  // click was to the right         moveRight( );      // make the bat move right       else         stayStill( );     }

Time-based reactions

The update superstate in the time-based reactions section is coded by overriding updateSprite( ) (in a similar way to in BallSprite):

     public void updateSprite( )     {       if ((locx+getWidth( ) <= 0) && (dx < 0))   // almost gone off lhs         locx = getPWidth( )-1;      // make it just visible on the right       else if ((locx >= getPWidth( )-1)&&(dx>0)) // almost gone off rhs         locx = 1 - getWidth( );     // make it just visible on the left       super.updateSprite( );     }

The looping behavior of the examining environment has been simplified so that the [off left] and [off right] conditional transitions are implemented as two sequential if tests. The move state is handled by calling Sprite's updateSprite( ).

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

Concurrently shared data

BatSprite makes no attempt to synchronize the accesses made to the data shared between the user-based reactions and time-based reactions sections. The shared data is the sprite's x-axis step size (dx), and the current image.

The step size is modified by moveLeft( ), moveRight( ), and stayStill( ) when they call the inherited setStep( ) method. Possibly at the same time, the step is used by Sprite's updateSprite( ) method.

Fortunately, Java guarantees that an assignment to a variable (other than a long or double) is atomic, as is the accessing of the variable's value. This means that simple reads and writes of variables (other than longs and doubles) won't interfere with each other.

moveLeft( ) and moveRight( ) assign a new object to the image reference by calling the inherited setImage( ) method. Meanwhile, in Sprite, drawSprite( ) passes the reference to drawImage( ) to draw the image on the screen. This amounts to a simple assignment and a dereference of the variable, both of which will be atomic, so they won't interfere with each other.

Concurrent state diagrams highlight synchronization issues in sprite design, which will always be present when the sprite can be updated by the user and affected by the game at the same time. The decision on whether to add synchronization code to the implementation depends on tradeoffs between speed and safety. Locking and unlocking code at runtime will affect the speed, especially for operations being carried out 50 times (or more) a second.



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