Adding Explosions


In the last version of the game, the player can destroy asteroids by firing missiles at them, but the destruction feels a bit hollow. So next, I add explosions to the game.

The Astrocrash07 Program

In the Astrocrash07 program, I write a new class for animated explosions based on games.Animation. But I also do some work behind the scenes, consolidating redundant code. Even though the player won't appreciate these additional changes, they're important nonetheless. Figure 12.14 shows the new program in action.

click to expand
Figure 12.14: All of the destruction in the game is now accompanied by fiery explosions.

The Wrapper Class

I start with the behind-the-scenes work. I create a new class, Wrapper, based on games.Sprite. Wrapper has a moved() method that automatically wraps an object around the screen:

 class Wrapper(games.Sprite):     """ A sprite that wraps around the screen. """     def moved(self):         """ Wrap sprite 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) 

You've seen this code several times already. It wraps a sprite around the screen. Now, if I base the other classes in the game on Wrapper, its moved() method will keep instances of those other classes on the screen—and the code only has to exist in one place!

I finish the class up with a die() method that destroys the object:

     def die(self):         """ Destroy self. """         self.destroy() 

The Collider Class

I take on more redundant code. I notice that both Ship and Missile share the same collision handling instructions. So, I create a new class, Collider, based on Wrapper, with a moved() method that handles collisions:

 class Collider(Wrapper):     """ A Wrapper that can collide with any other Wrapper. """     def moved(self):         """ Destroy self and overlapping object if object is Wrapper. """         Wrapper.moved(self)         for game_object in self.overlapping_objects():             if isinstance(game_object, Wrapper):                 game_object.die()                 self.die() 

The first thing I do in Collider's moved() method is invoke Wrapper's moved() method to keep the object on the screen. But notice that I invoke Wrapper's moved() method directly, with Wrapper.moved(self) and not with the super() function. I do this because the livewires classes are old-style, so I can't user super().

I also make an addition to the collision detection loop. For each overlapping object, I first check to see if the overlapping object is a Wrapper object. I do this because some objects on the screen won't be Wrapper objects and I want to ignore them, as far as collisions go. For example, the player's score shouldn't count when it comes to collisions. If a missile hits the player's score, the missile should ignore the score (as opposed to causing the score to explode and disappear!). And remember that all Collider objects are also Wrapper objects, since Collider is based on Wrapper.

I next write a die() method for the class, since all Collider objects will do the same thing when they die—create an explosion and destroy themselves:

     def die(self):         """ Destroy self and leave explosion behind. """         Explosion(self.screen, self.get_xpos(), self.get_ypos())         self.destroy() 

In this method, I create an Explosion object. Explosion is a new class whose objects are explosion animations. You'll see the class in its full glory soon.

Updating the Asteroid Class

In updating the Asteroid class, I change its class header so that the class is based on Wrapper:

 class Asteroid(Wrapper): 

Asteroid now inherits moved() from Wrapper, so I cut Asteroid's own moved() method. The redundant code is starting to disappear!

The only other thing I do in this class is change the last line of Asteroid's die() method. I replace the current self.die() with the line

 Wrapper.die(self) 

Now, if I ever change Wrapper's die() method, Asteroid will automatically reap the benefits.

Updating the Ship Class

In updating the Ship class, I change its class header so that the class is based on Collider:

 class Ship(Collider): 

At the end of Ship's moved() method, I add the line

         Collider.moved(self) 

I can now cut several more pieces of redundant code. Since Collider's moved() method handles collisions, I cut the collision detection code from Ship's moved() method. Since Collider's moved() method invokes Wrapper's moved() method, I cut the screen wrapping code from Ship's moved() method too. I also cut Ship's die() method and let the class inherit Collider's version.

Updating the Missile Class

In updating the Missile class, I change its class header so that the class is based on Collider:

 class Missile(Collider): 

At the end of the Missile's moved() method, I add the line

         Collider.moved(self) 

Just like with the Ship class, I can now cut redundant code from Missile. Since Collider's moved() method handles collisions, I cut the collision detection code from Missile's moved() method. Since Collider's moved() method invokes Wrapper's moved() method, I cut the screen wrapping code from Missile's moved() method too. I also cut Missile's die() method and let the class inherit Collider's version.

HINT

To help you understand the code changes I describe, feel free to check out the complete code for all versions of the Astrocrash game on the CD-ROM that came with this book.

The Explosion Class

Since I want to create animated explosions, I write an Explosion class based on games.Animation. I define the class variable sound, for the sound effect of an explosion. Next, I define a class variable, explosion_images, for a list of image objects of the nine frames of the explosion animation you saw in Figure 12.6. I load the images from the nine files, explosion1.bmp through explosion9.bmp, using a loop.

 class Explosion(games.Animation):     """ Explosion animation. """     sound = games.load_sound("explosion.wav")     images = []     for i in range(1, 10):         file_name = "explosion" + str(i) + ".bmp"         image = games.load_image(file_name)         images.append(image) 

In the Explosion constructor, I accept values into the screen, x, and y parameters, which represent the screen and coordinates for the explosion. I invoke the games.Animation constructor to create a new animation, and then play the explosion sound effect.

    def __init__(self, screen, x, y):        games.Animation.__init__(self, screen = screen, x = x, y = y,                                   images = Explosion.images,                                   n_repeats = 1, repeat_interval = 4)        Explosion.sound.play() 

When I invoke the games.Animation constructor, I pass screen, x, and y to their corresponding parameters. I pass to images the list of image objects, Explosion.images. Next, I pass to n_repeats the value of 1 so that the animation plays just once. Finally, I pass to repeat_interval the value of 4 so that the speed of the animation looks right.

TRICK

Remember, you can pass to the games.Animation constructor either a list of file names or a list of image objects for the frames of animation.




Python Programming for the Absolute Beginner
Python Programming for the Absolute Beginner, 3rd Edition
ISBN: 1435455002
EAN: 2147483647
Year: 2003
Pages: 194

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net