Defining a Sprite


The Animation Framework

BugPanel is a subclass of JPanel that implements the animation framework described in Chapters 2 and 3; BugPanel closely resembles the WormPanel class. The constructor sets up keyboard and mouse listeners, prepares the ImagesLoader and ClipsLoader objects, and creates the bat and ball sprites:

     public BugPanel(BugRunner br, long period)     {       bugTop = br;       this.period = period;       setDoubleBuffered(false);       setBackground(Color.black);       setPreferredSize( new Dimension(PWIDTH, PHEIGHT));       setFocusable(true);       requestFocus( );    // now has focus, so receives key events       addKeyListener( new KeyAdapter( ) {         public void keyPressed(KeyEvent e)         { processKey(e);  }   // handle key presses       });       // load the background image       ImagesLoader imsLoader = new ImagesLoader(IMS_INFO);       bgImage = imsLoader.getImage("bladerunner");       // initialise the clips loader       clipsLoader = new ClipsLoader(SNDS_FILE);       // create game sprites       bat = new BatSprite(PWIDTH, PHEIGHT, imsLoader,                                     (int)(period/1000000L) ); // in ms       ball = new BallSprite(PWIDTH, PHEIGHT, imsLoader,                                    clipsLoader, this, bat);       addMouseListener( new MouseAdapter( ) {         public void mousePressed(MouseEvent e)         { testPress(e.getX( )); }  // handle mouse presses       });       // set up message font       msgsFont = new Font("SansSerif", Font.BOLD, 24);       metrics = this.getFontMetrics(msgsFont);     }  // end of BugPanel( )

The image loaded by ImagesLoader is stored in the global bgImage and later used as the game's background image (see Figure 11-1).

BladeRunner fans will recognize the background image as a still from the movie, so would be another source of copyright problems in a commercial game.


The ClipsLoader object is stored in BugPanel and passes as an argument to the ball sprite, which plays various clips when its ball hits the walls or bat. The clips information file SNDS_FILE (clipsInfo.txt) is assumed to be in the Sounds/ subdirectory. It contains:

     hitBat jump.au     hitLeft clack.au     hitRight outch.au     gameOver clap.wav

The gameOver clip is used by BugPanel when the game finishes; the others are utilized by BallSprite.

User Interaction

The game panel supports user input via the keyboard and mouse, which is dealt with by processKey( ) and testPress( ). They are attached to the listeners in the BugPanel() constructor.

processKey( ) handles two kinds of key operations: those related to termination (e.g., Ctrl-C) and those affecting the ant (the arrow keys):

     private void processKey(KeyEvent e)     {       int keyCode = e.getKeyCode( );       // termination keys       if ((keyCode==KeyEvent.VK_ESCAPE) || (keyCode==KeyEvent.VK_Q) ||           (keyCode == KeyEvent.VK_END) ||           ((keyCode == KeyEvent.VK_C) && e.isControlDown( )) )         running = false;       // game-play keys       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( );       }     }  // end of processKey( )

The game-related keys are normally mapped to calls to BatSprite methods but are ignored if the game has been paused or finished. These extra tests aren't applied to the termination keys since it should be possible to exit the game, whatever its current state.

testPress( ) passes the cursor's x-coordinate to BatSprite to determine which way to move the ant:

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

The Animation Loop

BugPanel implements the Runnable interface, allowing its animation loop to be placed in the run( ) method. run( ) is almost the same as the one in the WormPanel class without the overheads of FPS statistics gathering:

     public void run( )     /* The frames of the animation are drawn inside the while loop. */     {       long beforeTime, afterTime, timeDiff, sleepTime;       long overSleepTime = 0L;       int noDelays = 0;       long excess = 0L;            gameStartTime = J3DTimer.getValue( );       beforeTime = gameStartTime;       running = true;       while(running) {              gameUpdate( );         gameRender( );         paintScreen( );         afterTime = J3DTimer.getValue( );         timeDiff = afterTime - beforeTime;         sleepTime = (period - timeDiff) - overSleepTime;         if (sleepTime > 0) {   // some time left in this cycle           try {             Thread.sleep(sleepTime/1000000L);  // nano -> ms           }           catch(InterruptedException ex){}           overSleepTime = (J3DTimer.getValue( ) - afterTime) - sleepTime;         }         else {    // sleepTime <= 0; frame took longer than period           excess -= sleepTime;  // store excess time value           overSleepTime = 0L;           if (++noDelays >= NO_DELAYS_PER_YIELD) {             Thread.yield( );   // give another thread a chance to run             noDelays = 0;           }         }         beforeTime = J3DTimer.getValue( );         /* If frame animation is taking too long, update the game state            without rendering it, to get the updates/sec nearer to            the required FPS. */         int skips = 0;         while((excess > period) && (skips < MAX_FRAME_SKIPS)) {           excess -= period;           gameUpdate( );    // update state but don't render           skips++;         }       }       System.exit(0);   // so window disappears     } // end of run( )

The Java 3D timer is used mainly because it's an excellent timer for J2SE 1.4.2 across a range of platforms. However, as J2SE 5.0 gains popularity, a better choice may be System.nanoTime( ). Porting the code is a matter of replacing calls to J3DTimer.getValue( ) with System.nanoTime( ).


The application-specific elements of the animation are located in gameUpdate( ) and gameRender( ). A new gameStartTime variable is initialized at the start of run( ); it's used later to calculate the elapsed time displayed in the game panel.

gameUpdate( ) updates the active game entitiesthe ball and bat sprites:

     private void gameUpdate( )     { if (!isPaused && !gameOver) {              ball.updateSprite( );         bat.updateSprite( );       }     }

gameRender( ) draws the background, the sprites, and the game statistics (the number of rebounds and the elapsed time):

     private void gameRender( )     {       if (dbImage == null){         dbImage = createImage(PWIDTH, PHEIGHT);         if (dbImage == null) {           System.out.println("dbImage is null");           return;         }         else           dbg = dbImage.getGraphics( );       }       // draw the background: use the image or a black screen       if (bgImage == null) {  // no background image         dbg.setColor(Color.black);         dbg.fillRect (0, 0, PWIDTH, PHEIGHT);       }       else         dbg.drawImage(bgImage, 0, 0, this);       // draw game elements            ball.drawSprite(dbg);       bat.drawSprite(dbg);       reportStats(dbg);       if (gameOver)         gameOverMessage(dbg);     }  // end of gameRender( )

gameUpdate( ) and gameRender( ) show the main way that the sprites are utilized. First their states are updated via calls to updateSprite( ), and then they're drawn by invoking drawSprite( ).

reportStats( ) calculates and renders the current time and the number of rebounds:

     private void reportStats(Graphics g)     {       if (!gameOver)   // stop incrementing timer once game is over         timeSpentInGame =             (int) ((J3DTimer.getValue( ) - gameStartTime)/1000000000L);                        // ns --> secs       g.setColor(Color.yellow);       g.setFont(msgsFont);       ball.drawBallStats(g, 15, 25);  // ball sprite reports ball stats       g.drawString("Time: " + timeSpentInGame + " secs", 15, 50);       g.setColor(Color.black);     }

The number of rebounds is reported by the ball sprite, which is passed the graphics context in the drawBallStats( ) call.

Finishing the Game

The game is terminated when the gameOver boolean is set to true. This stops any further updates to the active entities via gameUpdate( ) and disables the processing of keyboard and mouse actions. However, the screen is still periodically redrawn, and the background music keeps playing until the application is closed.

The gameOver boolean is set by the BallSprite object calling gameOver( ) in BugPanel:

     public void gameOver( )     { int finalTime =             (int) ((J3DTimer.getValue( ) - gameStartTime)/1000000000L);                          // ns --> secs       score = finalTime;   // could be more fancy!       clipsLoader.play("gameOver", false);   // play clip once       gameOver = true;     }

A score is calculated and the gameOver clip (polite applause) is played.



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