Next, I enable the ship to fire missiles. When the player presses the spacebar, a missile fires from the ship's cannon and flies off in the direction the ship faces. The missile should destroy anything it hits, but to keep things simple, I save the fun of destruction for another version of the program.
The Astrocrash04 program allows the player to fire missiles by pressing the spacebar. But there's a problem. If the player holds down the spacebar, a stream of missiles pours out of the ship, at a rate of about 50 per second. I need to limit the missile fire rate, but I leave that issue for the next version of the game. Figure 12.11 shows off the Astrocrash04 program, warts and all.
Figure 12.11: The missile fire rate is too high.
I update Ship's moved() method by adding code so that a ship can fire missiles. If the player presses the spacebar, I create a new missile:
# fire missile if spacebar pressed if self.screen.is_pressed(games.K_SPACE): Missile(self.screen, self.get_xpos(), self.get_ypos(), self.get_angle())
Of course, in order to instantiate a new object from the line Missile(self.screen, self.get_xpos(), self.get_ypos(), self.get_angle()), I need to write a little something . . . like a Missile class.
I write the Missile class for the missiles that the ship fires. I start by creating class variables and class constants:
class Missile(games.Sprite): """ A missile launched by the player's ship. """ image = games.load_image("missile.bmp") sound = games.load_sound("missile.wav") BUFFER = 40 VELOCITY_FACTOR = 7 LIFETIME = 40
image is for the image of a missile—a solid, red circle. sound is for the sound effect of a missile launching. BUFFER represents the distance from the ship that a new missile is created (so that the missile isn't created on top of the ship). VELOCITY_FACTOR affects how fast the missile travels. And LIFETIME represents how long the missile exists before it disappears (so that a missile won't float around the screen forever).
I start the class constructor with the following lines:
def __init__(self, screen, ship_x, ship_y, ship_angle): """ Initialize missile sprite. """
It may surprise you that the constructor for a missile requires values for the ship's x-and y-coordinates and the ship's angle, which are accepted into the ship_x, ship_y, and ship_angle parameters. The method needs these values so that it can determine two things: exactly where the missile first appears and the velocity components of the missile. Where the missile is created depends upon where the ship is located. And how the missile travels depends upon the angle of the ship.
Next, I play the missile-firing sound effect:
Missile.sound.play()
Then, I perform some calculations to figure out the new missile's location:
# convert to radians angle = ship_angle * math.pi / 180 # calculate missile's starting position buffer_x = Missile.BUFFER * math.sin(angle) buffer_y = -Missile.BUFFER * math.cos(angle) x = ship_x + buffer_x y = ship_y + buffer_y
I get the angle of the ship, converted to radians. Then, I calculate the missile's starting x-and y-coordinates, based on the angle of the ship and the Missile class constant BUFFER. The resulting x and y values place the missile right in front of the ship's cannon.
Next, I calculate the missile's velocity components. I use the same type of calculations as I did in the Ship class:
# calculate missile's velocity components dx = Missile.VELOCITY_FACTOR * math.sin(angle) dy = -Missile.VELOCITY_FACTOR * math.cos(angle)
Finally, I initialize the new sprite. I also make sure to give the Missile object a lifetime attribute so that the object won't be around forever.
# create the missile self.init_sprite(screen = screen, x = x, y = y, dx = dx, dy = dy, image = Missile.image) self.lifetime = Missile.LIFETIME
Then, I write a moved() method for the class. Here's the first part:
def moved(self): """ Move the missile. """ # if lifetime is up, destroy the missile self.lifetime -= 1 if not self.lifetime: self.destroy()
This code just counts down the life of the missile. lifetime is decremented. When it reaches 0, the Missile object destroys itself.
In the second part of moved(), I include the familiar code to wrap the missile around the screen:
# wrap the missile around screen if self.get_top() > SCREEN_HEIGHT: self.set_bottom(0) if self.get_bottom() < 0: self.set_top(SCREEN_HEIGHT) if self.get_left() > SCREEN_WIDTH: self.set_right(0) if self.get_right() < 0: self.set_left(SCREEN_WIDTH)
I see that the preceding code is repeated three different times my program. I'll definitely be consolidating it later.