Chapter 14. Galactic War: Entity Management

You have learned the basics of web game programming in previous chapters, and you have been building the Galactic War game here in Part III. The source code for an applet-based game is becoming a bit tedious at this point. You’ve seen that there is a lot of code that does not change very much from one game to the next, now that you know how to write a typical game in Java. Aren’t you getting tired of seeing the key and mouse handlers in every code listing? I sure am! I don’t want to enforce too much structure for your own game projects, but I think it will be helpful to add some organization to the code.

There are a lot of events and methods that must be called and monitored regularly, and since this code doesn’t change very often (if ever), it would clean up the source code considerably if we could move reusable code into a separate class. This chapter shows you how to create a base class for an applet-based framework—or rather, a game engine. You will be able to write a game very easily by inheriting from the new Game class, and your game won’t need to implement the interfaces (such as Runnable) any longer. Instead, you will be able to focus on high-level game design and gameplay.

Here are the specific topics you will learn about:

  • Event-driven game programming

  • Creating a Java applet-based game engine

  • Internalizing the sprite handler

  • Adapting Galactic War to the new game engine

Adjusting to Event-Driven Programming

The biggest obstacle to the adoption of a game framework, or a game engine, is that you must give up direct (active) control of the game and accept an indirect (passive) programming methodology. Adapting Galactic War from a direct to a passive game was very challenging, and I would like to share my experience with you so that you will not have to go through the pains of developing your own game engine the hard way. I could have completed the game two or three times over in the time I’ve spent building the Game class in this chapter, but the end result is a powerful engine for creating additional games for delivery on the web.

Exploring the Class Library

Let’s take a look at the class library as it exists at this point, after the changes made in the previous chapter. Figure 14.1 shows a diagram of the four main classes we’ve developed so far. The diagram doesn’t show the VectorEntity class since it is no longer being used.

The four key Java classes for working with game graphics.

Figure 14.1. The four key Java classes for working with game graphics.

First, BaseGameEntity contains the basic movement and orientation variables that are passed on to the ImageEntity class. This class adds the capability to move, rotate, and draw bitmap images, and it is the core class. From this point, a new class called Sprite was developed. Now, Sprite does not inherit from ImageEntity, but rather, it uses this core class to store its internal image used for drawing sprites. That is why the link from ImageEntity to Sprite is a dotted line. Next we have AnimatedSprite, which is the core of the game engine due to its support for animation.

When you look at this diagram and resolve the connections in the reverse direction, you find that AnimatedSprite inherits from Sprite several key properties: an image, width, and height. AnimatedSprite also makes use of an ImageEntity to handle the large, tiled images containing frames of animation. The Sprite class likewise, looking backward, consumes an image and its width and height properties. You can use this diagram if you ever start to feel overwhelmed while perusing the Galactic War source code in these final chapters, because the game is becoming tighter. When an AnimatedSprite loads its source bitmap, that is passed up to the “super” or “parent” class (Sprite) that handles the image. This is inheritance at work, because the AnimatedSprite class doesn’t actually need to handle its image at all.

I use the word “tight” to describe the situation in which the source code is not becoming bloated, as is often the case when a game becomes more advanced. Instead, Galactic War will evolve from a direct, actively coded model to an indirect, passively coded model using the game engine developed in this chapter. The source code listing for the game is about the same length as it was before, but the game has been completely rewritten. The Game class is quite complex, but the “front end” or “user” source code file, GalacticWar.java, is much, much simpler. That is the benefit of a game engine—it handles all of the messy details for you, allowing you to focus on building gameplay. From the engine’s point of view, “You are too skilled to be bothered with such minutiae as sprite management. Let me take care of that for you, while you focus on making this game as fun as possible!”

Building the New Game Class

The purpose of the game engine is to encapsulate the platform code on which the game is developed. In this case, we’re talking about a Java applet that runs in a web browser. So, the goal of this chapter is to build a game engine that simplifies building web games with Java. The first step is to create a new class. I opened the Galactic War project from the last chapter and began modifying it. First, I created a new class called Game. The Game class extends (or inherits from) the Applet class, as our programs have for the last 13 chapters. This new class will also need to implement the keyboard and mouse listener interfaces. It will also need to handle the game loop thread on its own, so the derived game will not need to be bothered with such details (or rather, logistics, since we’re trying to manage the logistics of an applet).

Encapsulating a Standard Java Applet

Here are the main events that you’re accustomed to seeing in a Java applet-based program up to this point. I’m including the mouse events because they are going to be part of the engine, even if we haven’t used them very much in Galactic War. You are welcome to add mouse support if you wish; I’m just not sure how you would control the ship with a mouse.

public void init()
public void update(Graphics g)
public void paint(Graphics g)
public void start()
public void run()
public void stop()
public void keyTyped(KeyEvent k)
public void keyPressed(KeyEvent k)
public void keyReleased(KeyEvent k)
public void mousePressed(MouseEvent e)
public void mouseReleased(MouseEvent e)
public void mouseMoved(MouseEvent e)
public void mouseDragged(MouseEvent e)
public void mouseEntered(MouseEvent e)
public void mouseExited(MouseEvent e)
public void mouseClicked(MouseEvent e)

Even if you ignore the mouse listener events, there are a lot of raw events in this listing that every applet-based game must implement at a minimum. As for things such as key events, we want to completely replace the stock events with a keyboard handler that supports multiple key presses and releases. I experimented with quite a few different ways to do this, and I came up with a solution that is versatile but not totally internal to the Game class. The key events are passed on to the game, which can then use a few global boolean variables to keep track of keys needed by the game. The mouse events are parsed, and several mouse properties are made available to provide your game with mouse button and movement information.

Custom Game Events

In place of the standard applet events, I want this class to send events to the game that are directly related to gameplay issues, such as sprite collision and screen refresh. The most crucial methods in the Game class use the Animated-Sprite class. The Game class has the following features:

  • Performs automatic double buffering

  • Maintains a consistent frame rate

  • Handles the input listeners

  • Manages the game loop thread

  • Maintains an internal linked list for sprites

  • Performs an automatic frame rate calculation

  • Automatically moves and draws all active sprites

  • Performs collision testing on all active sprites

  • Passes important events on to the game

That’s an impressive list of goals for any sprite-based game engine. This engine allows you to build far more complex games than would be possible with the simple arrays we’ve been using in the previous chapters. The key to the sprite handler is the java.util.LinkedList class. By using a linked list containing AnimatedSprite objects, you can dynamically add and remove sprites without adversely affecting performance. I’ve managed to get 300 sprites on the screen at once, with full collision testing, and the game still runs at the desired frame rate. Keep in mind, the target platform here is a web browser! Anytime you add some overhead to a system, you will inherently introduce some inefficiency.

There’s a tradeoff between simple speed and your desire to have an engaging, complex game with a lot of graphics on the screen. I like having the ability to add an explosion to the game at any point without having to worry about that explosion after it has finished animating itself. AnimatedSprite has a lot of properties and methods that make this possible (such as the lifetime and lifeage variables that the Game class uses to terminate a sprite when its lifespan is completed). I want to tell the game engine, “Hey, the player’s ship has exploded right here [provide X,Y position]. Please display an explosion here.” The game engine should not only animate the explosion at that location, it should also handle timing and then remove the explosion from the linked list automatically.

Tip

The linked list that handles sprites in the game engine is a global class variable called sprites(). You can access this object anywhere in your program when it inherits from Game.

Here are the new events introduced in the sprite engine. These events are declared as abstract because the inheriting class must implement them. They do not contain any source code in the Game class, although Game does call them. This is what gives the sprite engine the ability to pass events on to the game while still handling all the real work behind the scenes.

void gameStartup()
void gameTimedUpdate()
void gameRefreshScreen()
void gameShutdown()
void gameKeyDown(int keyCode)
void gameKeyUp(int keyCode)
void gameMouseDown()
void gameMouseUp()
void gameMouseMove()
void spriteUpdate(AnimatedSprite sprite)
void spriteDraw(AnimatedSprite sprite)
void spriteDying(AnimatedSprite sprite)
void spriteCollision(AnimatedSprite spr1, AnimatedSprite spr2)

Know what’s really great about these events? They describe exactly what you need to know in your game. What does spriteDying mean? This is called just before a sprite is removed from the linked list! What about spriteUpdate and spriteDraw? These two work hand in hand to give your code an opportunity to monitor a sprite as it is updated and drawn (both of them are handled for you). For instance, you use spriteDraw to draw the bounding box around a sprite if you turn on the bounding box display option (B key). The sprites are already drawn by the time spriteDraw is called. This just gives you notice that the sprite has been drawn, and you can do whatever you want with it. Likewise, when spriteUpdate is called, the sprite will have already moved (based on velocity). It’s up to you to keep the sprite from going out of the screen bounds, or performing any other behavior you want.

The Game Class Source Code

Here is the complete source code listing for the Game class, which I have been describing to you thus far. Much of this code should be familiar to you because we’ve used it extensively in previous chapters. I will highlight in bold the crucial code that is not directly related to the applet. While perusing the source code for the Game class, pay close attention to all bold lines of code, and then take note of the rest of the code. This should help you to grasp exactly what this class does.

/*****************************************************
* Applet Game Framework class
*****************************************************/
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.lang.System;
import java.util.*;

abstract class Game extends Applet implements Runnable, KeyListener,
    MouseListener, MouseMotionListener {

    //the main game loop thread
    private Thread gameloop;

    //internal list of sprites
    private LinkedList _sprites;
    public LinkedList sprites() { return _sprites; }

    //screen and double buffer related variables
    private BufferedImage backbuffer;
    private Graphics2D g2d;
    private int screenWidth, screenHeight;

    //keep track of mouse position and buttons
    private Point2D mousePos = new Point2D(0,0);
    private boolean mouseButtons[] = new boolean[4];

    //frame rate counters and other timing variables
    private int _frameCount = 0;
    private int _frameRate = 0;
    private int desiredRate;
    private long startTime = System.currentTimeMillis();

    //local applet object
    public Applet applet() { return this; }

    //game pause state
    private boolean _gamePaused = false;
    public boolean gamePaused() { return _gamePaused; }
    public void pauseGame() { _gamePaused = true; }
    public void resumeGame() { _gamePaused = false; }

    //declare the game event methods that sub-class must implement
    abstract void gameStartup();
    abstract void gameTimedUpdate();
    abstract void gameRefreshScreen();
    abstract void gameShutdown();
    abstract void gameKeyDown(int keyCode);
    abstract void gameKeyUp(int keyCode);
    abstract void gameMouseDown();
    abstract void gameMouseUp();
    abstract void gameMouseMove();
    abstract void spriteUpdate(AnimatedSprite sprite);
    abstract void spriteDraw(AnimatedSprite sprite);
    abstract void spriteDying(AnimatedSprite sprite);
    abstract void spriteCollision(AnimatedSprite spr1,AnimatedSprite spr2);

    /*****************************************************
     * constructor
     *****************************************************/
    public Game(int frameRate, int width, int height) {
        desiredRate = frameRate;
        screenWidth = width;
        screenHeight = height;
    }

    //return g2d object so sub-class can draw things
    public Graphics2D graphics() { return g2d; }

    //current frame rate
    public int frameRate() { return _frameRate; }

    //mouse buttons and movement
    public boolean mouseButton(int btn) { return mouseButtons[btn]; }
    public Point2D mousePosition() { return mousePos; }

    /*****************************************************
     * applet init event method
     *****************************************************/
    public void init() {
        //create the back buffer and drawing surface
        backbuffer = new BufferedImage(screenWidth, screenHeight,
            BufferedImage.TYPE_INT_RGB);
        g2d = backbuffer.createGraphics();

        //create the internal sprite list
        _sprites = new LinkedList<AnimatedSprite>();

        //start the input listeners
        addKeyListener(this);
        addMouseListener(this);
        addMouseMotionListener(this);

        //this method implemented by sub-class
        gameStartup();
    }

    /*****************************************************
     * applet update event method
     *****************************************************/
    public void update(Graphics g) {
        //calculate frame rate
        _frameCount++;
        if (System.currentTimeMillis() > startTime + 1000) {
            startTime = System.currentTimeMillis();
            _frameRate = _frameCount;
            _frameCount = 0;

            //once every second all dead sprites are deleted
            purgeSprites();
        }
        //this method implemented by sub-class
        gameRefreshScreen();

        //draw the internal list of sprites
        if (!gamePaused()) {
            drawSprites();
        }

        //redraw the screen
        paint(g);
    }

    /*****************************************************
     * applet window paint event method
     *****************************************************/
     public void paint(Graphics g) {
         g.drawImage(backbuffer, 0, 0, this);
     }

     /*****************************************************
      * thread start event - start the game loop running
      *****************************************************/
      public void start() {
          gameloop = new Thread(this);
          gameloop.start();
      }
      /*****************************************************
       * thread run event (game loop)
       *****************************************************/
      public void run() {
          //acquire the current thread
          Thread t = Thread.currentThread();

          //process the main game loop thread
          while (t == gameloop) {
              try {
                  //set a consistent frame rate
                  Thread.sleep(1000 / desiredRate);
              }
              catch(InterruptedException e) {
                  e.printStackTrace();
              }

              //update the internal list of sprites
              if (!gamePaused()) {
                  updateSprites();
                  testCollisions();
              }

              //allow main game to update if needed
              gameTimedUpdate();

              //refresh the screen
              repaint();
          }
      }

      /*****************************************************
       * thread stop event
       *****************************************************/
      public void stop() {
          //kill the game loop
          gameloop = null;

          //this method implemented by sub-class

          gameShutdown();
      }

      /*****************************************************
       * key listener events
       *****************************************************/
      public void keyTyped(KeyEvent k) { }
      public void keyPressed(KeyEvent k) {
          gameKeyDown(k.getKeyCode());
      }
      public void keyReleased(KeyEvent k) {
          gameKeyUp(k.getKeyCode());
      }

      /*****************************************************
       * checkButtons stores the state of the mouse buttons
       *****************************************************/
      private void checkButtons(MouseEvent e) {
              switch(e.getButton()) {
              case MouseEvent.BUTTON1:
                  mouseButtons[1] = true;
                  mouseButtons[2] = false;
                  mouseButtons[3] = false;
                  break;
              case MouseEvent.BUTTON2:
                  mouseButtons[1] = false;
                  mouseButtons[2] = true;
                  mouseButtons[3] = false;
                  break;
              case MouseEvent.BUTTON3:
                  mouseButtons[1] = false;
                  mouseButtons[2] = false;
                  mouseButtons[3] = true;
                  break;
              }
      }

      /*****************************************************
       * mouse listener events
       *****************************************************/
       public void mousePressed(MouseEvent e) {
           checkButtons(e);
           mousePos.setX(e.getX());
           mousePos.setY(e.getY());
           gameMouseDown();
       }
       public void mouseReleased(MouseEvent e) {
           checkButtons(e);
           mousePos.setX(e.getX());
           mousePos.setY(e.getY());
           gameMouseUp();
       }
       public void mouseMoved(MouseEvent e) {
           checkButtons(e);
           mousePos.setX(e.getX());
           mousePos.setY(e.getY());
           gameMouseMove();
       }
       public void mouseDragged(MouseEvent e) {
           checkButtons(e);
           mousePos.setX(e.getX());
           mousePos.setY(e.getY());
           gameMouseDown();
           gameMouseMove();
       }
       public void mouseEntered(MouseEvent e) {
           mousePos.setX(e.getX());
           mousePos.setY(e.getY());
           gameMouseMove();
       }
       public void mouseExited(MouseEvent e) {
           mousePos.setX(e.getX());
           mousePos.setY(e.getY());
           gameMouseMove();
       }
       //this event is not needed
       public void mouseClicked(MouseEvent e) { }

       /*****************************************************
        * X and Y velocity calculation functions
        *****************************************************/
       protected double calcAngleMoveX(double angle) {
           return (double)(Math.cos(angle * Math.PI / 180));
       }
       protected double calcAngleMoveY(double angle) {
           return (double) (Math.sin(angle * Math.PI / 180));
       }
       /*****************************************************
        * update the sprite list from the game loop thread
        *****************************************************/
       protected void updateSprites() {
           for (int n=0; n < _sprites.size(); n++) {
               AnimatedSprite spr = (AnimatedSprite) _sprites.get(n);
               if (spr.alive()) {
                   spr.updatePosition();
                   spr.updateRotation();
                   spr.updateAnimation();
                   spriteUpdate(spr);
                   spr.updateLifetime();
                   if (!spr.alive()) {
                       spriteDying(spr);
                   }
               }
           }
       }

       /*****************************************************
        * perform collision testing of all active sprites
        *****************************************************/
       protected void testCollisions() {
         //iterate through the sprite list, test each sprite against
         //every other sprite in the list
         for (int first=0; first < _sprites.size(); first++) {

           //get the first sprite to test for collision
           AnimatedSprite spr1 = (AnimatedSprite) _sprites.get(first);
           if (spr1.alive()) {
                //look through all sprites again for collisions
                for (int second = 0; second < _sprites.size(); second++) {
                  //make sure this isn't the same sprite
                  if (first != second) {
                      //get the second sprite to test for collision
                      AnimatedSprite spr2 = (AnimatedSprite)
                          _sprites.get(second);
                      if (spr2.alive()) {
                          if (spr2.collidesWith(spr1)) {
                              spriteCollision(spr1, spr2);
                              break;
                          }
                          else
                             spr1.setCollided(false);
                      }
                  }
                }
            }
        }
    }

    /*****************************************************
     * draw all active sprites in the sprite list
     * sprites lower in the list are drawn on top
     *****************************************************/
    protected void drawSprites() {
        //draw sprites in reverse order (reverse priority)
        for (int n=0; n<_sprites.size(); n++) {
            AnimatedSprite spr = (AnimatedSprite) _sprites.get(n);
            if (spr.alive()) {
                spr.updateFrame();
                spr.transform();
                spr.draw();
                spriteDraw(spr); //notify player
            }
        }
    }

    /*****************************************************
     * once every second during the frame update, this method
     * is called to remove all dead sprites from the linked list
     *****************************************************/
    private void purgeSprites() {
        for (int n=0; n < _sprites.size(); n++) {
            AnimatedSprite spr = (AnimatedSprite) _sprites.get(n);
            if (!spr.alive()) {
                _sprites.remove(n);
            }
        }
    }
}

Enhancing Galactic War

You will be surprised to learn that the source code for Galactic War has remained about the same in length, even though the game is dramatically more complex with significant new gameplay features. It is truly only a few steps away from completion, and my goal in the next chapter will be to add more gameplay features (such as power-ups). Just play the game for a few seconds, and you’ll immediately see a need for power-ups! This game is hard! If you can manage to keep from destroying some of the larger asteroids while working on the smaller ones, you might have a chance, but once you start letting bullets fly and asteroids begin to break into smaller pieces, you will have to be quick on the maneuvering to keep from becoming space dust (which I have just become—see Figure 14.2).

My ship has just been demolished by three medium-sized asteroids.

Figure 14.2. My ship has just been demolished by three medium-sized asteroids.

Exploring the New Galactic War Source Code

Rather than go over the source code solely from a functional point of view, I’m going to take you on a tour of the code along with screenshots of key aspects of the game that are impacted by specific sections of the source code. For a game this complex, 630 or so lines of code is surprising (not counting the support classes). I have highlighted all key lines of code in bold text so they will stand out. If you pay close attention to the bold lines of code, the code listings should make more sense to you.

Let’s get started with the opening credits for the game, where all the initial variables and objects are defined. The most significant thing about this code is the short class definition! The GalacticWar class just extends Game—and that’s it! Beyond that, the initialization of the game’s graphics is all done here. The images defined and loaded at the beginning are used whenever a new sprite needs to be added to the internal sprite list.

/*****************************************************
* GALACTIC WAR, Chapter 14
*****************************************************/
import java.awt.*;
import java.util.*;
import java.lang.System;
import java.awt.event.*;

public class GalacticWar extends Game {
    //these must be static because they are passed to a constructor
    static int FRAMERATE = 60;
    static int SCREENWIDTH = 800;
    static int SCREENHEIGHT = 600;

    //misc global constants
    final int ASTEROIDS = 10;
    final int BULLET_SPEED = 4;
    final double ACCELERATION = 0.05;
    final double SHIPROTATION = 5.0;

    //sprite state values
    final int STATE_NORMAL = 0;
    final int STATE_COLLIDED = 1;
    final int STATE_EXPLODING = 2;

    //sprite types
    final int SPRITE_SHIP = 1;
    final int SPRITE_ASTEROID_BIG = 10;
    final int SPRITE_ASTEROID_MEDIUM = 11;
    final int SPRITE_ASTEROID_SMALL = 12;
    final int SPRITE_ASTEROID_TINY = 13;
    final int SPRITE_BULLET = 100;
    final int SPRITE_EXPLOSION = 200;

  //various toggles
  boolean showBounds = false;
  boolean collisionTesting = true;

  //define the images used in the game
 ImageEntity background;
 ImageEntity bulletImage;
 ImageEntity[] bigAsteroids = new ImageEntity[5];
 ImageEntity[] medAsteroids = new ImageEntity[2];
 ImageEntity[] smlAsteroids = new ImageEntity[3];
 ImageEntity[] tnyAsteroids = new ImageEntity[4];
 ImageEntity[] explosions = new ImageEntity[2];
 ImageEntity[] shipImage = new ImageEntity[2];

  //create a random number generator
  Random rand = new Random();

  //used to make ship temporarily invulnerable
  long collisionTimer = 0;

  //some key input tracking variables
  boolean keyLeft, keyRight, keyUp, keyFire, keyB, keyC;

/*****************************************************
  * constructor
  *****************************************************/
  public GalacticWar() {
     //call base Game class' constructor
     super(FRAMERATE, SCREENWIDTH, SCREENHEIGHT);
  }

  /*****************************************************
    * gameStartup event passed by game engine
    *****************************************************/
void gameStartup() {
    //load the background image
    background = new ImageEntity(this);
    background.load("bluespace.png");

    //create the ship sprite--first in the sprite list
   shipImage[0] = new ImageEntity(this);
   shipImage[0].load("spaceship.png");
   shipImage[1] = new ImageEntity(this);
   shipImage[1].load("ship_thrust.png");

   AnimatedSprite ship = new AnimatedSprite(this, graphics());
   ship.setSpriteType(SPRITE_SHIP);
   ship.setImage(shipImage[0].getImage());
   ship.setFrameWidth(ship.imageWidth());
   ship.setFrameHeight(ship.imageHeight());
   ship.setPosition(new Point2D(SCREENWIDTH/2, SCREENHEIGHT/2));
   ship.setAlive(true);
   ship.setState(STATE_NORMAL);
   sprites().add(ship);

    //load the bullet sprite image
    bulletImage = new ImageEntity(this);
    bulletImage.load("plasmashot.png");

    //load the explosion sprite image
   explosions[0] = new ImageEntity(this);
   explosions[0].load("explosion.png");
   explosions[1] = new ImageEntity(this);
   explosions[1].load("explosion2.png");

    //load the big asteroid images (5 total)
    for (int n = 0; n<5; n++) {
        bigAsteroids[n] = new ImageEntity(this);
        String fn = "asteroid" + (n+1) + ".png";
        bigAsteroids[n].load(fn);
    }
    //load the medium asteroid images (2 total)
    for (int n = 0; n<2; n++) {
        medAsteroids[n] = new ImageEntity(this);
        String fn = "medium" + (n+1) + ".png";
        medAsteroids[n].load(fn);
    }
    //load the small asteroid images (3 total)
    for (int n = 0; n<3; n++) {
        smlAsteroids[n] = new ImageEntity(this);
        String fn = "small" + (n+1) + ".png";
        smlAsteroids[n].load(fn);
    }
    //load the tiny asteroid images (4 total)
    for (int n = 0; n<4; n++) {
        tnyAsteroids[n] = new ImageEntity(this);
        String fn = "tiny" + (n+1) + ".png";
        tnyAsteroids[n].load(fn);
    }

    //create the random asteroid sprites
    for (int n = 0; n<ASTEROIDS; n++) {
        createAsteroid();
    }
}

Game Loop Update and Screen Refresh

Now let’s take a look at some key events passed here from the Game class. Remember, these methods were defined as abstract in Game so that they would be implemented here in the derived source code file. The gameTimedUpdate() method is called from within the timed game loop thread. The gameRe-freshScreen() method is called from the applet update() event. I’m not currently using gameShutdown(), but it is available if you need to clean house before the program ends.

Now let’s take a look at Figure 14.3, which shows the game fairly early on in the run. The same toggles are still available in the game, including collision testing and bounding box display. There are four stages to destroying an asteroid:

  • Large, detailed asteroids

  • Medium asteroid chunks

  • Small asteroid pieces

  • Tiny asteroids

The large asteroids break up into smaller ones, either when they hit your ship or when you fire a plasma bolt.

Figure 14.3. The large asteroids break up into smaller ones, either when they hit your ship or when you fire a plasma bolt.

Don’t let the tiny asteroids fool you—they will still damage your ship, and they can be destroyed by your guns. Speaking of damage, the game would be more fun if the ship’s health were displayed somewhere on the screen—a good feature to add in the next chapter.

/*****************************************************
 * gameTimedUpdate event passed by game engine
 *****************************************************/
void gameTimedUpdate() {
    checkInput();
}

/*****************************************************
 * gameRefreshScreen event passed by game engine
 *****************************************************/
void gameRefreshScreen() {
    Graphics2D g2d = graphics();

    //the ship is always the first sprite in the linked list
    AnimatedSprite ship = (AnimatedSprite)sprites().get(0);

    //draw the background
    g2d.drawImage(background.getImage(),0,0,SCREENWIDTH-1,
        SCREENHEIGHT-1,this);

    //print status information on the screen
    g2d.setColor(Color.WHITE);
    g2d.drawString("FPS: " + frameRate(), 5, 10);
    long x = Math.round(ship.position().X());
    long y = Math.round(ship.position().Y());
    g2d.drawString("Ship: " + x + "," + y , 5, 25);
    g2d.drawString("Move angle: " +
        Math.round(ship.moveAngle())+90, 5, 40);
    g2d.drawString("Face angle: " +
        Math.round(ship.faceAngle()), 5, 55);
    if (ship.state()==STATE_NORMAL)
        g2d.drawString("State: NORMAL", 5, 70);
    else if (ship.state()==STATE_COLLIDED)
        g2d.drawString("State: COLLIDED", 5, 70);
    else if (ship.state()==STATE_EXPLODING)
        g2d.drawString("State: EXPLODING", 5, 70);

    //display the number of sprites currently in use
    g2d.drawString("Sprites: " + sprites().size(), 5, 120);

    if (showBounds) {
        g2d.setColor(Color.GREEN);
        g2d.drawString("BOUNDING BOXES", SCREENWIDTH-150, 10);
    }
    if (collisionTesting) {
        g2d.setColor(Color.GREEN);
        g2d.drawString("COLLISION TESTING", SCREENWIDTH-150, 25);
    }
}
/*****************************************************
 * gameShutdown event passed by game engine
 *****************************************************/
void gameShutdown() {
     //oh well, let the garbage collector have at it..
}

Updating and Drawing Sprites

Next, let’s take a look at the spriteUpdate() and spriteDraw() events, which are passed on by the Game class. The spriteDying() event is also available here, but it’s not currently being used in the game. You would use this event to keep a sprite alive if you wanted to keep it around by setting alive back to true.

The game engine processes all of the sprites in the linked list automatically (by the game loop thread). After each sprite is updated, control is sent to spriteUpdate() to give you a chance to fool around with the sprite if you need to. (It is passed as a parameter.) You can make any changes you want to the sprite, and those changes will be stored back in the linked list because you are working with an object reference—the actual object in the linked list, not just a copy.

This is truly where the sprite engine shows its power. You can fire as many weapons as you want, and they will strike an unknown number of sprites, which will each blow up in an animated explosion—and the linked list keeps track of all these details for you. All we really have to do is put together a new scratch sprite, give it an image from one of our global images, and then pop it off to the sprite list. This sprite will then automatically move around on the screen, rotate (if rotation is set), and animate (if frames exist). This new sprite will also be included in collision testing. The engine allows you to determine whether a collision “sticks” by giving you control at the key point where a collision has occurred, passing both sprites to your implementation of the spriteCollision() event.

When I was first developing the engine, I thought it would be too limiting to move all of the sprites into the list. I kept the ship outside the list for a while, and then realized that to truly make the internal sprite handler work the way it is supposed to, you really need to give it all of the sprites it needs to update, draw, and test for collision. When you get tired of a sprite, just set its alive property to false, and then the game engine will remove it from the list a few seconds later! Furthermore, as you learned in the previous chapter, you can make use of the features in AnimatedSprite for limiting the lifetime of a sprite. By setting the lifespan of a sprite, you can have it automatically terminate after a certain number of passes through the game loop. For instance, the “flaming plasma” projectiles fired from the ship are given a lifetime of 200 loops. The engine detects when the age is beyond the lifespan threshold, and then kills the sprite automatically!

Figure 14.4 shows the game after all of the large asteroids have been broken down into smaller ones. The game is truly crazy at this point, with close to a hundred small asteroids floating around your ship on various random trajectories! It’s hard enough just to spin and shoot frantically, let alone move around. I even had to cheat to keep from getting blown up repeatedly! (I’ll share the cheat with you at our next stop in this trip through the source code.)

The ship is overwhelmed by asteroids and in need of some weapon upgrades!

Figure 14.4. The ship is overwhelmed by asteroids and in need of some weapon upgrades!

/*****************************************************
 * spriteUpdate event passed by game engine
 *****************************************************/
public void spriteUpdate(AnimatedSprite sprite) {
    switch(sprite.spriteType()) {
    case SPRITE_SHIP:
        warp(sprite);
        break;

    case SPRITE_BULLET:
        warp(sprite);
        break;

    case SPRITE_EXPLOSION:
        if (sprite.currentFrame() == sprite.totalFrames()-1) {
            sprite.setAlive(false);
        }
        break;

    case SPRITE_ASTEROID_BIG:
    case SPRITE_ASTEROID_MEDIUM:
    case SPRITE_ASTEROID_SMALL:
    case SPRITE_ASTEROID_TINY:
        warp(sprite);
        break;
    }
}

/*****************************************************
 * spriteDraw event passed by game engine
 * called by the game class after each sprite is drawn
 * to give you a chance to manipulate the sprite
 *****************************************************/
public void spriteDraw(AnimatedSprite sprite) {
    if (showBounds) {
        if (sprite.collided())
            sprite.drawBounds(Color.RED);
        else
            sprite.drawBounds(Color.BLUE);
    }
}

/*****************************************************
 * spriteDying event passed by game engine
 * called after a sprite's age reaches its lifespan
 * at which point it will be killed off, and then removed from
 * the linked list. you can cancel the purging process here.
 *****************************************************/
public void spriteDying(AnimatedSprite sprite) {
    //currently no need to revive any sprites
}

Handling Sprite Collisions

The most important event in the game is probably the spriteCollision() event, which is passed from the engine to your source code automatically. The engine goes through the list of active sprites (where the alive property is true) and tests each one for collision with all the other sprites in the list (except for the same one—we don’t want to have objects blow themselves up for no reason!).

Take a look at Figure 14.5, which shows the ship firing several volleys of flaming plasma bolts toward asteroids. There are several bolts traveling away from the ship at various angles and several explosions that are animating on the screen. Figure 14.5 shows sprite collision testing in progress. The engine, as implemented in the Game class and inherited by GalacticWar, goes through the sprites and tests them all for collision—but that’s all it does. Look at the sprite counter in this figure—132 sprites! This number includes the ship, bullets, asteroids, and explosions, and the number rises and falls dynamically with the gameplay. (The engine clears out unused sprites that have “died” once every second when the frame rate is calculated.)

Projectiles fired from the ship eventually get passed through the spriteCollision() event when they collide with asteroids.

Figure 14.5. Projectiles fired from the ship eventually get passed through the spriteCollision() event when they collide with asteroids.

There is no more logic going on behind the scenes than the test for collision itself. The engine simply passes both sprites that have interacted to the sprite Collision() method. At this point, it’s entirely up to you to decide what to do with the conflicting sprites. You can destroy them, have them bounce away from each other, or anything else. So all you have to do is figure out what kinds of sprites have been passed to you through the spriteCollision() event. You can do this with your own predefined constants (defined as a static int in Java). For example, I have defined SPRITE_EXPLOSION for all explosions. Yes, explosion sprites are tested for collision like everything else—but we simply ignore them, let them play out, and then disappear. The value of each constant is not important, as long as each one is different. Since I defined each of the four types of asteroids with a separate constant, I had to write a helper method called isAsteroid() to handle them all in the same way when they are passed through the event handlers.

Now, how about that cheat I promised? When I get into a tough spot in the game, I hold down the left or right arrow key to spin while hitting both fire buttons (the Ctrl keys). This launches twice as many volleys of plasma bolts, as Figure 14.6 shows.

You can launch twice as many bolts using both Ctrl keys!

Figure 14.6. You can launch twice as many bolts using both Ctrl keys!

    /*****************************************************
     * spriteCollision event passed by game engine
     *****************************************************/
    public void spriteCollision(AnimatedSprite spr1,AnimatedSprite spr2) {
        //jump out quickly if collisions are off
        if (!collisionTesting) return;
    
        //figure out what type of sprite has collided
        switch(spr1.spriteType()) {
        case SPRITE_BULLET:
            //did bullet hit an asteroid?
            if (isAsteroid(spr2.spriteType())) {
                spr1.setAlive(false);
                spr2.setAlive(false);
                breakAsteroid(spr2);
            }
            break;
        case SPRITE_SHIP:
            //did asteroid crash into the ship?
            if (isAsteroid(spr2.spriteType())) {
                if (spr1.state() == STATE_NORMAL) {
                    collisionTimer = System.currentTimeMillis();
                    spr1.setVelocity(new Point2D(0, 0));
                    double x = spr1.position().X() - 10;
                    double y = spr1.position().Y() - 10;
                    startBigExplosion(new Point2D(x, y));
                    spr1.setState(STATE_EXPLODING);
                    spr2.setAlive(false);
                    breakAsteroid(spr2);
                }
                //make ship temporarily invulnerable
                else if (spr1.state() == STATE_EXPLODING) {
                    if (collisionTimer + 3000 <
                        System.currentTimeMillis()) {
                        spr1.setState(STATE_NORMAL);
                     }
                }
            }
            break;
        }
    }

Keyboard and Mouse Events

The keyboard and mouse handlers are not always used, but you must still implement them in your program. You might wonder, then, how is this any better than just using the listeners directly in the game’s source code file? That’s a good question! Basically, we want to homogenize the events as much as possible. Note that the keyboard events pass nothing but the key scan code, and the mouse events pass nothing at all—you must access the mouse information through the mousePos and mouseButtons variables (with their associated accessor methods). Simply knowing about a keyboard or mouse event is enough; we don’t need all of the extra information that Java sends the program.

    /*****************************************************
     * gameKeyDown event passed by game engine
     *****************************************************/
    public void gameKeyDown(int keyCode) {
        switch(keyCode) {
        case KeyEvent.VK_LEFT:
            keyLeft = true;
            break;
        case KeyEvent.VK_RIGHT:
            keyRight = true;
            break;
        case KeyEvent.VK_UP:
            keyUp = true;
            break;
        case KeyEvent.VK_CONTROL:
            keyFire = true;
            break;
        case KeyEvent.VK_B:
            //toggle bounding rectangles
            showBounds = !showBounds;
            break;
        case KeyEvent.VK_C:
            //toggle collision testing
            collisionTesting = !collisionTesting;
            break;
        }
    }
    
    /*****************************************************
     * gameKeyUp event passed by game engine
     *****************************************************/
    public void gameKeyUp(int keyCode) {
        switch(keyCode) {
        case KeyEvent.VK_LEFT:
            keyLeft = false;
            break;
        case KeyEvent.VK_RIGHT:
            keyRight = false;
            break;
        case KeyEvent.VK_UP:
            keyUp = false;
            break;
        case KeyEvent.VK_CONTROL:
            keyFire = false;
            fireBullet();
            break;
        }
    }
    
    /*****************************************************
     * mouse events passed by game engine
     * the game is not currently using mouse input
     *****************************************************/
    public void gameMouseDown() { }
    public void gameMouseUp() { }
    public void gameMouseMove() { }

Asteroid Manipulation Methods

The asteroids are the most complicated part of Galactic War. First of all, there are four types of asteroids in the game, each with several different images available (which are chosen randomly when a new asteroid is created). I have written several methods for working with asteroids, mainly called from the collision events that occur. The larger asteroids are destroyed and replaced by smaller asteroids, while an explosion is animated over the old asteroid.

Figure 14.7 shows the game with bounding boxes turned on so you can see the dimensions of each sprite in the game. Note how even the explosions have a bounding box—they are included in collision testing as well, even though the game ignores them. All of the original asteroids have been destroyed and replaced with increasingly smaller ones. If you want to grab some power-ups (something that will be added in the next chapter), you will have to be careful not to destroy all of the larger asteroids—the smaller ones are next to impossible to get around, and your ship will get hosed by them in short order if you let the guns go carelessly. As a result of this gameplay factor, this game involves some strategy.

The bounding boxes and collision testing can still be toggled on or off in the game.

Figure 14.7. The bounding boxes and collision testing can still be toggled on or off in the game.

This section of code marks the end of the game engine events. From this point forward, all of the methods are custom programmed and provide the real gameplay in Galactic War.

/*****************************************************
 * break up an asteroid into smaller pieces
 *****************************************************/
private void breakAsteroid(AnimatedSprite sprite) {
    switch(sprite.spriteType()) {
    case SPRITE_ASTEROID_BIG:
        //spawn medium asteroids over the old one
        spawnAsteroid(sprite);
        spawnAsteroid(sprite);
        spawnAsteroid(sprite);
        //draw big explosion
        startBigExplosion(sprite.position());
        break;
    case SPRITE_ASTEROID_MEDIUM:
        //spawn small asteroids over the old one
        spawnAsteroid(sprite);
        spawnAsteroid(sprite);
        spawnAsteroid(sprite);
        //draw small explosion
        startSmallExplosion(sprite.position());
        break;
    case SPRITE_ASTEROID_SMALL:
        //spawn tiny asteroids over the old one
        spawnAsteroid(sprite);
        spawnAsteroid(sprite);
        spawnAsteroid(sprite);
        //draw small explosion
        startSmallExplosion(sprite.position());
        break;
    case SPRITE_ASTEROID_TINY:
        //spawn a random powerup
        spawnPowerup(sprite);
        //draw small explosion
        startSmallExplosion(sprite.position());
        break;
    }
}

/*****************************************************
 * spawn a smaller asteroid based on passed sprite
 *****************************************************/
private void spawnAsteroid(AnimatedSprite sprite) {
    //create a new asteroid sprite
    AnimatedSprite ast = new AnimatedSprite(this, graphics());
    ast.setAlive(true);

    //set pseudo-random position around source sprite
   int w = sprite.getBounds().width;
   int h = sprite.getBounds().height;
   double x = sprite.position().X() + w/2 + rand.nextInt(20)-40;
   double y = sprite.position().Y() + h/2 + rand.nextInt(20)-40;
   ast.setPosition(new Point2D(x,y));

    //set rotation and direction angles
   ast.setFaceAngle(rand.nextInt(360));
   ast.setMoveAngle(rand.nextInt(360));
   ast.setRotationRate(rand.nextDouble());

   //set velocity based on movement direction
   double ang = ast.moveAngle() - 90;
   double velx = calcAngleMoveX(ang);
   double vely = calcAngleMoveY(ang);
   ast.setVelocity(new Point2D(velx, vely));

   //set some size-specific properties
   switch(sprite.spriteType()) {
   case SPRITE_ASTEROID_BIG:
       ast.setSpriteType(SPRITE_ASTEROID_MEDIUM);

       //pick one of the random asteroid images
       int i = rand.nextInt(2);
       ast.setImage(medAsteroids[i].getImage());
       ast.setFrameWidth(medAsteroids[i].width());
       ast.setFrameHeight(medAsteroids[i].height());

       break;
   case SPRITE_ASTEROID_MEDIUM:
       ast.setSpriteType(SPRITE_ASTEROID_SMALL);

       //pick one of the random asteroid images
       i = rand.nextInt(3);
       ast.setImage(smlAsteroids[i].getImage());
       ast.setFrameWidth(smlAsteroids[i].width());
       ast.setFrameHeight(smlAsteroids[i].height());
       break;

   case SPRITE_ASTEROID_SMALL:
       ast.setSpriteType(SPRITE_ASTEROID_TINY);

       //pick one of the random asteroid images
       i = rand.nextInt(4);
       ast.setImage(tnyAsteroids[i].getImage());
       ast.setFrameWidth(tnyAsteroids[i].width());
       ast.setFrameHeight(tnyAsteroids[i].height());
       break;
   }

   //add the new asteroid to the sprite list
   sprites().add(ast);
}

/*****************************************************
 * create a random powerup at the supplied sprite location
 * (this will be implemented in the next chapter)
 *****************************************************/
private void spawnPowerup(AnimatedSprite sprite) {
}
/*****************************************************
 * create a random "big" asteroid
 *****************************************************/
public void createAsteroid() {
    //create a new asteroid sprite
    AnimatedSprite ast = new AnimatedSprite(this, graphics());
    ast.setAlive(true);
    ast.setSpriteType(SPRITE_ASTEROID_BIG);

    //pick one of the random asteroid images
    int i = rand.nextInt(5);
    ast.setImage(bigAsteroids[i].getImage());
    ast.setFrameWidth(bigAsteroids[i].width());
    ast.setFrameHeight(bigAsteroids[i].height());

    //set to a random position on the screen
    int x = rand.nextInt(SCREENWIDTH-128);
    int y = rand.nextInt(SCREENHEIGHT-128);
    ast.setPosition(new Point2D(x, y));

    //set rotation and direction angles
    ast.setFaceAngle(rand.nextInt(360));
    ast.setMoveAngle(rand.nextInt(360));
    ast.setRotationRate(rand.nextDouble());

    //set velocity based on movement direction
    double ang = ast.moveAngle() - 90;
    double velx = calcAngleMoveX(ang);
    double vely = calcAngleMoveY(ang);
    ast.setVelocity(new Point2D(velx, vely));

    //add the new asteroid to the sprite list
    sprites().add(ast);
}

/*****************************************************
 * returns true if passed sprite type is an asteroid type
 *****************************************************/
private boolean isAsteroid(int spriteType) {
    switch(spriteType) {
    case SPRITE_ASTEROID_BIG:
    case SPRITE_ASTEROID_MEDIUM:
    case SPRITE_ASTEROID_SMALL:
    case SPRITE_ASTEROID_TINY:
        return true;
    default:
        return false;
    }
}

Handling Multiple Key Presses

I spent a lot of time trying to incorporate a multiple key-press system into the game engine itself, but this proved to be too troublesome. As a result, the main game keeps track of key presses and releases using global boolean variables. Since only a handful of keys are ever used in an arcade-style game like this, a more complex form of key handler is not necessary. As it is implemented here, the engine calls a few events and passes the key code when a key is pressed or released so that you don’t have to bother decoding the KeyEvent, MouseEvent, or MouseMotionEvent class.

/*****************************************************
 * process keys that have been pressed
 *****************************************************/
public void checkInput() {
    //the ship is always the first sprite in the linked list
    AnimatedSprite ship = (AnimatedSprite)sprites().get(0);
    if (keyLeft) {
        //left arrow rotates ship left 5 degrees
        ship.setFaceAngle(ship.faceAngle() - SHIPROTATION);
        if (ship.faceAngle() < 0)
            ship.setFaceAngle(360 - SHIPROTATION);
    }
    else if (keyRight) {
        //right arrow rotates ship right 5 degrees
        ship.setFaceAngle(ship.faceAngle() + SHIPROTATION);
        if (ship.faceAngle() > 360)
            ship.setFaceAngle(SHIPROTATION);
    }
    if (keyUp) {
        //up arrow applies thrust to ship
        ship.setImage(shipImage[1].getImage());
        applyThrust();
    }
    else
        //set ship image to normal non-thrust image
        ship.setImage(shipImage[0].getImage());
}

Moving the Spaceship

The spaceship in Galactic War is rotated using the left and right arrow keys, and thrust is applied by pressing the up arrow key. The applyThrust() method handles the acceleration of the ship while keeping the ship within a reasonable velocity threshold.

/*****************************************************
 * increase the thrust of the ship based on facing angle
 *****************************************************/
public void applyThrust() {
    //the ship is always the first sprite in the linked list
    AnimatedSprite ship = (AnimatedSprite)sprites().get(0);

    //up arrow adds thrust to ship (1/10 normal speed)
    ship.setMoveAngle(ship.faceAngle() - 90);

    //calculate the X and Y velocity based on angle
    double velx = ship.velocity().X();
    velx += calcAngleMoveX(ship.moveAngle()) * ACCELERATION;
    if (velx < -10) velx = -10;
    else if (velx > 10) velx = 10;
    double vely = ship.velocity().Y();
    vely += calcAngleMoveY(ship.moveAngle()) * ACCELERATION;
    if (vely < -10) vely = -10;
    else if (vely > 10) vely = 10;
    ship.setVelocity(new Point2D(velx, vely));

}

Firing Weapons

The most significant area of improvement for the game is in the weaponry department, so some time will be spent in the next chapter adding power-ups to the game. You will be able to grab power-up icons that are dropped by exploding asteroids, which will then enhance the ship in various ways. The current version of the game here has not changed from the previous chapter, except that it now functions with the game engine. The Ctrl key is used to fire weapons, but you can change this to another key if you want by examining the key handlers.

/*****************************************************
 * fire a bullet from the ship's position and orientation
 *****************************************************/
public void fireBullet() {
    //the ship is always the first sprite in the linked list
    AnimatedSprite ship = (AnimatedSprite)sprites().get(0);

    //create the new bullet sprite
    AnimatedSprite bullet = new AnimatedSprite(this,graphics());
    bullet.setImage(bulletImage.getImage());
    bullet.setFrameWidth(bulletImage.width());
    bullet.setFrameHeight(bulletImage.height());
    bullet.setSpriteType(SPRITE_BULLET);
    bullet.setAlive(true);
    bullet.setLifespan(200);
    bullet.setFaceAngle(ship.faceAngle());
    bullet.setMoveAngle(ship.faceAngle() - 90);

    //set the bullet's starting position
    double x = ship.center().X() - bullet.imageWidth()/2;
    double y = ship.center().Y() - bullet.imageHeight()/2;
    bullet.setPosition(new Point2D(x,y));

    //set the bullet's velocity
    double angle = bullet.moveAngle();
    double svx = calcAngleMoveY(angle) * BULLET_SPEED;
    double svy = calcAngleMoveY(angle) * BULLET_SPEED;
    bullet.setVelocity(new Point2D(svx, svy));

    //add bullet to the sprite list
    sprites().add(bullet);
}

Give Me Something to Blow Up!

There are two main methods for starting explosions. I could have come up with a craftier way to do this, but I decided to just write two similar methods: one for initiating large explosions and another for smaller explosions. They use images stored in the explosions array (of ImageEntity objects). There are currently only two explosion animations. The large explosion is used when you hit a large asteroid. The small explosion is drawn when you hit smaller asteroids. I think the result looks pretty good.

Because there are a lot of small asteroids and bullets flying every which way, you don’t want too complex of an explosion sucking up the game’s resources when there could be a couple dozen such explosions animating at a time. Check out Figure 14.8. The large explosion’s frames are 96 × 96 pixels in size and there are 16 frames; the small explosion has 8 frames, and each one is only 40 × 40 pixels in size. The big explosion is used for the ship and large asteroids. This should be used sparingly because it is such a large image.

The two explosion animations compared side by side.

Figure 14.8. The two explosion animations compared side by side.

/*****************************************************
 * launch a big explosion at the passed location
 *****************************************************/
public void startBigExplosion(Point2D point) {
    //create a new explosion at the passed location
    AnimatedSprite expl = new AnimatedSprite(this,graphics());
    expl.setSpriteType(SPRITE_EXPLOSION);
    expl.setAlive(true);
    expl.setAnimImage(explosions[0].getImage());
    expl.setTotalFrames(16);
    expl.setColumns(4);
    expl.setFrameWidth(96);
    expl.setFrameHeight(96);
    expl.setFrameDelay(2);
    expl.setPosition(point);

    //add the new explosion to the sprite list
    sprites().add(expl);
}

/*****************************************************
 * launch a small explosion at the passed location
 *****************************************************/
public void startSmallExplosion(Point2D point) {
    //create a new explosion at the passed location
    AnimatedSprite expl = new AnimatedSprite(this,graphics());
    expl.setSpriteType(SPRITE_EXPLOSION);
    expl.setAlive(true);
    expl.setAnimImage(explosions[1].getImage());
    expl.setTotalFrames(8);
    expl.setColumns(4);
    expl.setFrameWidth(40);
    expl.setFrameHeight(40);
    expl.setFrameDelay(2);
    expl.setPosition(point);

    //add the new explosion to the sprite list
    sprites().add(expl);
}

Additional Game Logic

The last method in the game is called warp(), and it has the duty of making sprites wrap around the edges of the screen (right, left, top, and bottom). This is kind of a strange occurrence if you think about it, but a lot of games use this technique. The idea is that this makes an otherwise small playing field appear larger because objects can just travel through ether-space behind the monitor and magically reappear on the other side, otherwise unscathed. It helps to contain the gameplay when a scrolling game world is not a goal for the game.

/*****************************************************
 * cause sprite to warp around the edges of the screen
 *****************************************************/
public void warp(AnimatedSprite spr) {
    //create some shortcut variables
    int w = spr.frameWidth()-1;
    int h = spr.frameHeight()-1;

    //wrap the sprite around the screen edges
    if (spr.position().X() < 0-w)
        spr.position().setX(SCREENWIDTH);
    else if (spr.position().X() > SCREENWIDTH)
        spr.position().setX(0-w);
    if (spr.position().Y() < 0-h)
        spr.position().setY(SCREENHEIGHT);
    else if (spr.position().Y() > SCREENHEIGHT)
        spr.position().setY(0-h);
    }
}

What You Have Learned

This was a code-heavy chapter that involved significant changes to the coding model we’ve been following in each chapter up to this point. Now that Galactic War is event-driven and makes use of a sprite engine, the game has many more upgrade possibilities than it might have had before (where each new sprite had to be implemented from scratch). Here are the key topics you learned:

  • How to create an event-driven game engine

  • How to use the new Game class

  • How to handle all sprites uniformly, regardless of type

  • How to adapt Galactic War to an event-driven game

Review Questions

The following questions will help you to determine how well you have learned the subjects discussed in this chapter. The answers are provided in Appendix A, “Chapter Quiz Answers.”

1.

What is the name of the new game engine class developed in this chapter?

2.

How many sprites can the new engine handle on the screen simultaneously?

3.

Which of the four key classes in the game engine handles image loading?

4.

How many different asteroid sizes does the game use?

5.

True or False: Collisions are handled inside the game engine.

6.

What type of object is animImage, a private variable in AnimatedSprite?

7.

Which class is responsible for rendering a single frame of an animation in AnimatedSprite?

8.

What is the maximum velocity value for the player’s spaceship?

9.

Which class does the game/sprite engine pass in some of its events?

10.

What is the name of the support method in AnimatedSprite that returns a properly formed URL for a file to be loaded?

On Your Own

The following exercise will help you to see how well you have integrated the new material in this chapter with your existing knowledge of Java game programming.

There are currently two methods used to start an explosion—a custom method for large explosions and another one for small explosions. These methods do the same thing, but they use a few different properties. How could you revise one of them to handle both cases for a large or small explosion with a single method?

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.103.28