Other Side-Scroller Examples


The Jumping Sprite

A JumperSprite object can appear to move left or right, jump, and stand still. The sprite doesn't move horizontally at all, but the left and right movement requests will affect its internal state. It maintains its current world coordinates in (xWorld, yWorld).

When a sprite starts moving left or right, it will keep traveling in that direction until stopped by a brick. If the sprite runs off a raised platform, it will fall to the ground below and continue moving forward.

When the sprite jumps, it continues upward for a certain distance and falls back to the ground. The upward trajectory is stopped if the sprite hits a brick.

Statechart Specification

The JumperSprite statechart is given in Figure 12-23.

The statechart models JumperSprite as three concurrent activities: its horizontal movement in the top section, its vertical movement in the middle section, and the update/draw cycle in the bottom section.

The effects of an updateSprite( ) event have been distributed through the diagram, rather than placing them together in an examining environment state.


The horizontal movement section shows that a new updateSprite( ) event doesn't change the current state, be it moving right, moving left, or stationary. Movement stops when the user sends a stop event or when the sprite hits a brick.

The vertical movement section utilizes three states: not jumping, rising, and falling. Rising is controlled by an upCount counter, which limits how long an upward move can last. Rising may be stopped by the sprite hitting a brick. Falling is triggered when

Figure 12-23. The JumperSprite statechart


rising finishes and when no brick is underneath the sprite. This latter condition becomes true when the sprite moves horizontally off a raised platform.

The falling state can lead to termination if the sprite drops below the bottom of the panel (yWorld > pHeight). In fact, this transition led to a redesign of BricksManager to reject a bricks map with a gap in its floor. Consequently, dropping off the panel cannot occur in JumpingJack.

Though the statechart is clear, I want to avoid the complexity of multiple threads in JumperSprite. Instead, the concurrent activities are interleaved together in my code, making it somewhat harder to understand but easier to write.

Representing the States

The moving right, moving left, and stationary states are represented indirectly as two BooleansisFacingRight and isStillwhich combine to define the current horizontal state. For instance, when isStill is false and isFacingRight is true, then the sprite is moving right.

The not jumping, rising, and falling states are encoded as constants, assigned to a vertMoveMode variable:

     private static final int NOT_JUMPING = 0;     private static final int RISING = 1;     private static final int FALLING = 2;     private int vertMoveMode;       /* can be NOT_JUMPING, RISING, or FALLING */     private boolean isFacingRight, isStill;

In J2SE 5.0, vertMoveMode could be defined using an enumerated type.


Initialization

The initialize state is coded in JumperSprite's constructor:

     // some globals     private int vertStep;   // distance to move vertically in one step     private int upCount;     private int moveSize;   // obtained from BricksManager     private int xWorld, yWorld;       /* the current position of the sprite in 'world' coordinates.          The x-values may be negative. The y-values will be between          0 and pHeight. */     public JumperSprite(int w, int h, int brickMvSz, BricksManager bm,                                           ImagesLoader imsLd, int p)     {       super(w/2, h/2, w, h, imsLd, "runningRight");          // standing center screen, facing right       moveSize = brickMvSz;             // the move size is the same as the bricks ribbon       brickMan = bm;       period = p;       setStep(0,0);     // no movement       isFacingRight = true;       isStill = true;       /* Adjust the sprite's y- position so it is          standing on the brick at its mid x- position. */       locy = brickMan.findFloor(locx+getWidth( )/2)-getHeight( );       xWorld = locx; yWorld = locy;    // store current position       vertMoveMode = NOT_JUMPING;       vertStep = brickMan.getBrickHeight( )/2;                   // the jump step is half a brick's height       upCount = 0;     }  // end of JumperSprite( )

The (xWorld, yWorld) coordinates and the sprite's position and speed are set. The state variables isFacingRight, isStill, and vertMoveMode define a stationary, nonjumping sprite, facing to the right.

BricksManager's findFloor( ) method is used to get a y location for the sprite that lets it stand on top of a brick. The method's input argument is the sprite's midpoint along the x-axis, which is its leftmost x-coordinate plus half its width (locx+getWidth( )/2).

Key Event Processing

The events move left, move right, stop, and jump in the statechart are caught as key presses by the key listener in JackPanel, TRiggering calls to the JumperSprite methods moveLeft( ), moveRight( ), stayStill( ), and jump( ).

moveLeft( ), moveRight( ), and stayStill( ) affect the horizontal state by adjusting the isFacingRight and isStill variables. The animated image associated with the sprite changes:

     public void moveLeft( )     { setImage("runningLeft");       loopImage(period, DURATION);   // cycle through the images       isFacingRight = false;  isStill = false;     }     public void moveRight( )     { setImage("runningRight");       loopImage(period, DURATION);   // cycle through the images       isFacingRight = true;  isStill = false;     }     public void stayStill( )     { stopLooping( );       isStill = true;     }

The jump( ) method represents the transition from the not jumping to the rising state in the statechart. This is coded by changing the value stored in vertMoveMode. The sprite's image is modified:

     public void jump( )     { if (vertMoveMode == NOT_JUMPING) {         vertMoveMode = RISING;         upCount = 0;         if (isStill) {    // only change image if the sprite is 'still'           if (isFacingRight)             setImage("jumpRight");           else             setImage("jumpLeft");         }       }     }

JackPanel Collision Testing

The [will hit brick on the right] and [will it brick on the left] conditional transitions in the statechart are implemented as a public willHitBrick( ) method called from JackPanel's gameUpdate( ) method:

     private void gameUpdate( )     {       if (!isPaused && !gameOver) {         if (jack.willHitBrick( )) { // collision checking first           jack.stayStill( );    // stop everything moving           bricksMan.stayStill( );           ribsMan.stayStill( );         }         ribsMan.update( );   // update background and sprites         bricksMan.update( );         jack.updateSprite( );         fireball.updateSprite( );         if (showExplosion)           explosionPlayer.updateTick( );  // update the animation       }     }

The reason for placing the test in JackPanel's hands is so it can coordinate the other game entities when a collision occurs. The JumperSprite and the background layers in the game are halted:

     public boolean willHitBrick( )     {       if (isStill)         return false;   // can't hit anything if not moving       int xTest;   // for testing the new x- position       if (isFacingRight)   // moving right         xTest = xWorld + moveSize;       else // moving left         xTest = xWorld - moveSize;       // test a point near the base of the sprite       int xMid = xTest + getWidth( )/2;       int yMid = yWorld + (int)(getHeight( )*0.8);   // use y posn       return brickMan.insideBrick(xMid,yMid);     }  // end of willHitBrick( )

willHitBrick( ) represents two conditional transitions, so the isFacingRight flag is used to distinguish how xTest should be modified. The proposed new coordinate is generated and passed to BricksManager's insideBrick( ) for evaluation.

The vertical collision testing in the middle section of the statechart, [will hit brick below] and [will hit brick above], is carried out by JumperSprite, not JackPanel, since a collision affects only the sprite.

Updating the Sprite

The statechart distributes the actions of the updateState( ) event around the statechart: actions are associated with the moving right,moving left, rising, and falling states. These actions are implemented in the updateState( ) method, and the functions it calls:

     public void updateSprite( )     {       if (!isStill) {    // moving         if (isFacingRight)  // moving right           xWorld += moveSize;         else // moving left           xWorld -= moveSize;         if (vertMoveMode == NOT_JUMPING)   // if not jumping           checkIfFalling( );   // may have moved out into empty space       }       // vertical movement has two components: RISING and FALLING       if (vertMoveMode == RISING)         updateRising( );       else if (vertMoveMode == FALLING)         updateFalling( );       super.updateSprite( );     }  // end of updateSprite( )

The method updates its horizontal position (xWorld) first, distinguishing between moving right or left by examining isStill and isFacingRight. After the move, checkIfFalling( ) decides whether the [no brick below] transition from not jumping to falling should be applied. The third stage of the method is to update the vertical states.

Lastly, the call to Sprite's updateSprite( ) method modifies the sprite's position and image. updateSprite( ) illustrates the coding issues that arise when concurrent activities (in this case, horizontal and vertical movement) are sequentialized. The statechart places no restraints on the ordering of the two types of movement, but an ordering must be imposed when it's programmed as a sequence. In updateSprite( ), the horizontal actions are carried out before the vertical ones.

Falling?

checkIfFalling( ) determines whether the not jumping state should be changed to falling:

     private void checkIfFalling( )     {       // could the sprite move downwards if it wanted to?       // test its center x-coord, base y-coord       int yTrans = brickMan.checkBrickTop( xWorld+(getWidth( )/2),                        yWorld+getHeight( )+vertStep, vertStep);       if (yTrans != 0)   // yes it could         vertMoveMode = FALLING;   // set it to be in falling mode     }

The test is carried out by passing the coordinates of the sprite's feet, plus a vertical offset downward, to checkBrickTop( ) in BricksManager.

Vertical Movement

updateRising( ) deals with the updateSprite( ) event associated with the rising state, and tests for the two conditional transitions that leave the state: rising can stop either when upCount = MAX or when [will hit brick above] becomes true. Rising will continue until the maximum number of vertical steps is reached or the sprite hits the base of a brick. The sprite then switches to falling mode. checkBrickBase( ) in BricksManager carries out the collision detection:

     private void updateRising( )     { if (upCount == MAX_UP_STEPS) {         vertMoveMode = FALLING;   // at top, now start falling         upCount = 0;       }       else {         int yTrans = brickMan.checkBrickBase(xWorld+(getWidth( )/2),                                         yWorld-vertStep, vertStep);         if (yTrans == 0) {   // hit the base of a brick           vertMoveMode = FALLING;   // start falling           upCount = 0;         }         else {   // can move upwards another step           translate(0, -yTrans);           yWorld -= yTrans;   // update position           upCount++;         }       }     }  // end of updateRising( )

updateFalling( ) processes the updateSprite( ) event associated with the falling state, and deals with the [will hit brick below] transition going to the not jumping state. checkBrickTop( ) in BricksManager carries out the collision detection.

The other conditional leading to termination is not implemented since the bricks map cannot contain any holes for the sprite to fall through:

     private void updateFalling( )     { int yTrans = brickMan.checkBrickTop(xWorld+(getWidth( )/2),                           yWorld+getHeight( )+vertStep, vertStep);       if (yTrans == 0)   // hit the top of a brick         finishJumping( );       else {    // can move downwards another step         translate(0, yTrans);         yWorld += yTrans;   // update position       }     }     private void finishJumping( )     { vertMoveMode = NOT_JUMPING;       upCount = 0;       if (isStill) {    // change to running image, but not looping yet         if (isFacingRight)           setImage("runningRight");         else    // facing left           setImage("runningLeft");       }     }



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