We’ll make a minor enhancement to Galactic War in this chapter by adding support for an animated explosion. To facilitate this, we’ll have to write some new code to handle the timing and state properties for each sprite. The goal in this chapter is to add a handler for responding to ship-asteroid collisions. When a collision occurs, the game should animate an explosion over the ship, and then put the ship into a temporary invulnerability mode so the player can get the heck out of the way before another collision occurs.
Here are the key topics we’ll cover in this chapter:
Examining possible interactions among the game entities
Adding collision detection between the player’s ship and the asteroids
Responding to collisions in a civilized manner is the first step toward adding realistic gameplay to any game. The next step is to add a sense of timing to the response code. We’ll add explosions to show when the player gets hit by an asteroid! The new animated explosion in the game is a 16-frame sequence, which is shown in Figure 13.1.
When the explosion code is added to the game, the ship will literally blow up when it collides with an asteroid (see Figure 13.2).
Let’s take a look at the changes required to update the game to support explosions. There’s more involved here than just loading up the animation and drawing it because we have to account for timing and sprite state values. The first change is in the global
section at the top of the class—note the changes in bold.
public class GalacticWar extends Applet implements Runnable, KeyListener { //global constants static int SCREENWIDTH = 800; static int SCREENHEIGHT = 600; static int CENTERX = SCREENWIDTH / 2; static int CENTERY = SCREENHEIGHT / 2; static int ASTEROIDS = 10; static int BULLETS = 10; static int BULLET_SPEED = 4; static double ACCELERATION = 0.05; //sprite state values static int STATE_NORMAL = 0; static int STATE_COLLIDED = 1; static int STATE_EXPLODING = 2; //the main thread becomes the game loop Thread gameloop; //double buffer objects BufferedImage backbuffer; Graphics2D g2d; //various toggles boolean showBounds = true; boolean collisionTesting = true; long collisionTimer = 0; //define the game objects ImageEntity background; Sprite ship; Sprite[] ast = new Sprite[ASTEROIDS]; Sprite[] bullet = new Sprite[BULLETS]; int currentBullet = 0; AnimatedSprite explosion; //create a random number generator Random rand = new Random(); //sound effects SoundClip shoot; SoundClip explode; //simple way to handle multiple keypresses boolean keyDown, keyUp, keyLeft, keyRight, keyFire; //frame rate counters and other timing variables int frameCount = 0, frameRate = 0; long startTime = System.currentTimeMillis();
Make the following changes near the top of the init()
event method to switch the background object from an Image
to an ImageEntity.
public void init() { //create the back buffer for smooth graphics backbuffer = new BufferedImage(SCREENWIDTH, SCREENHEIGHT, BufferedImage.TYPE_INT_RGB); g2d = backbuffer.createGraphics(); //load the background image background = new ImageEntity(this); background.load("bluespace.png");
Next, scroll down inside the init()
event method a bit more until you have found the call to addKeyListener(this)
and insert the following code in bold. This code loads a 16-frame explosion animation.
//load the explosion explosion = new AnimatedSprite(this, g2d); explosion.load("explosion96x96x16.png", 4, 4, 96, 96); explosion.setFrameDelay(2); explosion.setAlive(false); //start the user input listener addKeyListener(this);
Next, go to the update()
event and look for the line of code that draws the background and make the change noted in bold. Then, a few lines further down, add the new line of code shown in bold.
//draw the background g2d.drawImage(background.getImage(),0,0,SCREENWIDTH-1, SCREENHEIGHT-1,this); //draw the game graphics drawAsteroids(); drawShip(); drawBullets(); drawExplosions();
Now, while you’re still in the update()
method, scroll down a few lines to the part of the method that draws status information on the screen and add the following code to display the ship’s current state.
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);
Now, scroll down past the three draw
methods and add the following method after drawAsteroids().
This is just the first version of the explosion code, and it only draws a single animated explosion. Later, the game will need to support several explosions at a time.
public void drawExplosions() { //explosions don't need separate update method if (explosion.alive()) { explosion.updateAnimation(); if (explosion.currentFrame() == explosion.totalFrames()-1) { explosion.setCurrentFrame(0); explosion.setAlive(false); } else { explosion.draw(); } } }
Next, scroll down a bit more to the gameUpdate()
method. Modify it with the additional lines of code shown in bold.
private void gameUpdate() { checkInput(); updateShip(); updateBullets(); updateAsteroids(); if (collisionTesting) { checkCollisions(); handleShipCollisions(); handleBulletCollisions(); handleAsteroidCollisions(); } }
Scroll down just past the checkCollisions()
method and add the following new methods to the game. The two collision handler routines for asteroids and bullets are not implemented yet, as the goal is first to get a response for ship-asteroid collisions along with an animated explosion. The handleShipCollisions()
method uses the ship sprite’s state
property extensively to monitor the current state of a collision, and it adds a three-second delay after a collision has occurred so the player can get out of the way before collisions start to occur again.
public void handleShipCollisions() { if (ship.state() == STATE_COLLIDED) { collisionTimer = System.currentTimeMillis(); ship.setVelocity(new Point2D(0,0)); ship.setState(STATE_EXPLODING); startExplosion(ship); } else if (ship.state() == STATE_EXPLODING) { if (collisionTimer + 3000 < System.currentTimeMillis()) { ship.setState(STATE_NORMAL); } } } public void startExplosion(Sprite sprite) { if (!explosion.alive()) { double x = sprite.position().X() - sprite.getBounds().width / 2; double y = sprite.position().Y() - sprite.getBounds().height / 2; explosion.setPosition(new Point2D(x, y)); explosion.setCurrentFrame(0); explosion.setAlive(true); } } public void handleBulletCollisions() { for (int n = 0; n<BULLETS; n++) { if (bullet[n].state() == STATE_COLLIDED) { //nothing to do yet } } } public void handleAsteroidCollisions() { for (int n = 0; n<ASTEROIDS; n++) { if (ast[n].state() == STATE_COLLIDED) { //nothing to do yet } } }
This chapter tackled the difficult subject of sprite collision detection. Adding support for collision testing is not an easy undertaking, but this chapter provided you with the knowledge and explained the collision code in the core classes that make it possible. You can now draw two or more sprites on the screen and very easily determine when they have intersected or collided, and then respond to such events.
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.”
Since we are constantly improving the game with each new chapter, there is little you can do now that will not be addressed in the next chapter. However, some improvements can be made now that are not added in future chapters. Here is one such example: Add another explosion to Galactic War so that the asteroids blow up like the player’s ship when they collide with the ship.
18.218.239.182