The Animation FrameworkBugPanel 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).
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 InteractionThe 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 LoopBugPanel 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 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 GameThe 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. |