Asteroid Smasher#

This is a sample asteroid smasher game made with the Arcade library.

asteroid_smasher.py#
  1"""
  2Asteroid Smasher
  3
  4Shoot space rocks in this demo program created with
  5Python and the Arcade library.
  6
  7Artwork from https://kenney.nl
  8
  9If Python and Arcade are installed, this example can be run from the command line with:
 10python -m arcade.examples.asteroid_smasher
 11"""
 12import random
 13import math
 14import arcade
 15
 16from typing import cast
 17
 18STARTING_ASTEROID_COUNT = 3
 19SCALE = 0.5
 20OFFSCREEN_SPACE = 300
 21SCREEN_WIDTH = 800
 22SCREEN_HEIGHT = 600
 23SCREEN_TITLE = "Asteroid Smasher"
 24LEFT_LIMIT = -OFFSCREEN_SPACE
 25RIGHT_LIMIT = SCREEN_WIDTH + OFFSCREEN_SPACE
 26BOTTOM_LIMIT = -OFFSCREEN_SPACE
 27TOP_LIMIT = SCREEN_HEIGHT + OFFSCREEN_SPACE
 28
 29
 30class TurningSprite(arcade.Sprite):
 31    """ Sprite that sets its angle to the direction it is traveling in. """
 32    def update(self):
 33        """ Move the sprite """
 34        super().update()
 35        self.angle = math.degrees(math.atan2(self.change_y, self.change_x))
 36
 37
 38class ShipSprite(arcade.Sprite):
 39    """
 40    Sprite that represents our space ship.
 41
 42    Derives from arcade.Sprite.
 43    """
 44    def __init__(self, filename, scale):
 45        """ Set up the space ship. """
 46
 47        # Call the parent Sprite constructor
 48        super().__init__(filename, scale)
 49
 50        # Info on where we are going.
 51        # Angle comes in automatically from the parent class.
 52        self.thrust = 0
 53        self.speed = 0
 54        self.max_speed = 4
 55        self.drag = 0.05
 56        self.respawning = 0
 57
 58        # Mark that we are respawning.
 59        self.respawn()
 60
 61    def respawn(self):
 62        """
 63        Called when we die and need to make a new ship.
 64        'respawning' is an invulnerability timer.
 65        """
 66        # If we are in the middle of respawning, this is non-zero.
 67        self.respawning = 1
 68        self.center_x = SCREEN_WIDTH / 2
 69        self.center_y = SCREEN_HEIGHT / 2
 70        self.angle = 0
 71
 72    def update(self):
 73        """
 74        Update our position and other particulars.
 75        """
 76        if self.respawning:
 77            self.respawning += 1
 78            self.alpha = self.respawning
 79            if self.respawning > 250:
 80                self.respawning = 0
 81                self.alpha = 255
 82        if self.speed > 0:
 83            self.speed -= self.drag
 84            if self.speed < 0:
 85                self.speed = 0
 86
 87        if self.speed < 0:
 88            self.speed += self.drag
 89            if self.speed > 0:
 90                self.speed = 0
 91
 92        self.speed += self.thrust
 93        if self.speed > self.max_speed:
 94            self.speed = self.max_speed
 95        if self.speed < -self.max_speed:
 96            self.speed = -self.max_speed
 97
 98        self.change_x = -math.sin(math.radians(self.angle)) * self.speed
 99        self.change_y = math.cos(math.radians(self.angle)) * self.speed
100
101        self.center_x += self.change_x
102        self.center_y += self.change_y
103
104        # If the ship goes off-screen, move it to the other side of the window
105        if self.right < 0:
106            self.left = SCREEN_WIDTH
107
108        if self.left > SCREEN_WIDTH:
109            self.right = 0
110
111        if self.bottom < 0:
112            self.top = SCREEN_HEIGHT
113
114        if self.top > SCREEN_HEIGHT:
115            self.bottom = 0
116
117        """ Call the parent class. """
118        super().update()
119
120
121class AsteroidSprite(arcade.Sprite):
122    """ Sprite that represents an asteroid. """
123
124    def __init__(self, image_file_name, scale):
125        super().__init__(image_file_name, scale=scale)
126        self.size = 0
127
128    def update(self):
129        """ Move the asteroid around. """
130        super().update()
131        if self.center_x < LEFT_LIMIT:
132            self.center_x = RIGHT_LIMIT
133        if self.center_x > RIGHT_LIMIT:
134            self.center_x = LEFT_LIMIT
135        if self.center_y > TOP_LIMIT:
136            self.center_y = BOTTOM_LIMIT
137        if self.center_y < BOTTOM_LIMIT:
138            self.center_y = TOP_LIMIT
139
140
141class MyGame(arcade.Window):
142    """ Main application class. """
143
144    def __init__(self):
145        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
146
147        self.game_over = False
148
149        # Sprite lists
150        self.player_sprite_list = arcade.SpriteList()
151        self.asteroid_list = arcade.SpriteList()
152        self.bullet_list = arcade.SpriteList()
153        self.ship_life_list = arcade.SpriteList()
154
155        # Set up the player
156        self.score = 0
157        self.player_sprite = None
158        self.lives = 3
159
160        # Sounds
161        self.laser_sound = arcade.load_sound(":resources:sounds/hurt5.wav")
162        self.hit_sound1 = arcade.load_sound(":resources:sounds/explosion1.wav")
163        self.hit_sound2 = arcade.load_sound(":resources:sounds/explosion2.wav")
164        self.hit_sound3 = arcade.load_sound(":resources:sounds/hit1.wav")
165        self.hit_sound4 = arcade.load_sound(":resources:sounds/hit2.wav")
166
167        # Text
168        self.text_score = None
169        self.text_asteroid_count = None
170
171    def start_new_game(self):
172        """ Set up the game and initialize the variables. """
173
174        self.game_over = False
175
176        # Sprite lists
177        self.player_sprite_list = arcade.SpriteList()
178        self.asteroid_list = arcade.SpriteList()
179        self.bullet_list = arcade.SpriteList()
180        self.ship_life_list = arcade.SpriteList()
181
182        # Set up the player
183        self.score = 0
184        self.player_sprite = ShipSprite(":resources:images/space_shooter/"
185                                        "playerShip1_orange.png",
186                                        SCALE)
187        self.player_sprite_list.append(self.player_sprite)
188        self.lives = 3
189
190        # Set up the little icons that represent the player lives.
191        cur_pos = 10
192        for i in range(self.lives):
193            life = arcade.Sprite(":resources:images/space_shooter/"
194                                 "playerLife1_orange.png",
195                                 SCALE)
196            life.center_x = cur_pos + life.width
197            life.center_y = life.height
198            cur_pos += life.width
199            self.ship_life_list.append(life)
200
201        # Make the asteroids
202        image_list = (":resources:images/space_shooter/meteorGrey_big1.png",
203                      ":resources:images/space_shooter/meteorGrey_big2.png",
204                      ":resources:images/space_shooter/meteorGrey_big3.png",
205                      ":resources:images/space_shooter/meteorGrey_big4.png")
206        for i in range(STARTING_ASTEROID_COUNT):
207            image_no = random.randrange(4)
208            enemy_sprite = AsteroidSprite(image_list[image_no], SCALE)
209            enemy_sprite.guid = "Asteroid"
210
211            enemy_sprite.center_y = random.randrange(BOTTOM_LIMIT, TOP_LIMIT)
212            enemy_sprite.center_x = random.randrange(LEFT_LIMIT, RIGHT_LIMIT)
213
214            enemy_sprite.change_x = random.random() * 2 - 1
215            enemy_sprite.change_y = random.random() * 2 - 1
216
217            enemy_sprite.change_angle = (random.random() - 0.5) * 2
218            enemy_sprite.size = 4
219            self.asteroid_list.append(enemy_sprite)
220
221        # Create new text objects with initial values
222        self.text_score = arcade.Text(
223            f"Score: {self.score}",
224            start_x=10,
225            start_y=70,
226            font_size=13,
227        )
228        self.text_asteroid_count = arcade.Text(
229            f"Asteroid Count: {len(self.asteroid_list)}",
230            start_x=10,
231            start_y=50,
232            font_size=13,
233        )
234
235    def on_draw(self):
236        """
237        Render the screen.
238        """
239
240        # This command has to happen before we start drawing
241        self.clear()
242
243        # Draw all the sprites.
244        self.asteroid_list.draw()
245        self.ship_life_list.draw()
246        self.bullet_list.draw()
247        self.player_sprite_list.draw()
248
249        # Draw the text
250        self.text_score.draw()
251        self.text_asteroid_count.draw()
252
253    def on_key_press(self, symbol, modifiers):
254        """ Called whenever a key is pressed. """
255        # Shoot if the player hit the space bar and we aren't respawning.
256        if not self.player_sprite.respawning and symbol == arcade.key.SPACE:
257            bullet_sprite = TurningSprite(":resources:images/space_shooter/"
258                                          "laserBlue01.png",
259                                          SCALE)
260            bullet_sprite.guid = "Bullet"
261
262            bullet_speed = 13
263            bullet_sprite.change_y = \
264                math.cos(math.radians(self.player_sprite.angle)) * bullet_speed
265            bullet_sprite.change_x = \
266                -math.sin(math.radians(self.player_sprite.angle)) \
267                * bullet_speed
268
269            bullet_sprite.center_x = self.player_sprite.center_x
270            bullet_sprite.center_y = self.player_sprite.center_y
271            bullet_sprite.update()
272
273            self.bullet_list.append(bullet_sprite)
274
275            arcade.play_sound(self.laser_sound, speed=random.random() * 3 + 0.5)
276
277        if symbol == arcade.key.LEFT:
278            self.player_sprite.change_angle = 3
279        elif symbol == arcade.key.RIGHT:
280            self.player_sprite.change_angle = -3
281        elif symbol == arcade.key.UP:
282            self.player_sprite.thrust = 0.15
283        elif symbol == arcade.key.DOWN:
284            self.player_sprite.thrust = -.2
285
286    def on_key_release(self, symbol, modifiers):
287        """ Called whenever a key is released. """
288        if symbol == arcade.key.LEFT:
289            self.player_sprite.change_angle = 0
290        elif symbol == arcade.key.RIGHT:
291            self.player_sprite.change_angle = 0
292        elif symbol == arcade.key.UP:
293            self.player_sprite.thrust = 0
294        elif symbol == arcade.key.DOWN:
295            self.player_sprite.thrust = 0
296
297    def split_asteroid(self, asteroid: AsteroidSprite):
298        """ Split an asteroid into chunks. """
299        x = asteroid.center_x
300        y = asteroid.center_y
301        self.score += 1
302
303        if asteroid.size == 4:
304            for i in range(3):
305                image_no = random.randrange(2)
306                image_list = [":resources:images/space_shooter/meteorGrey_med1.png",
307                              ":resources:images/space_shooter/meteorGrey_med2.png"]
308
309                enemy_sprite = AsteroidSprite(image_list[image_no],
310                                              SCALE * 1.5)
311
312                enemy_sprite.center_y = y
313                enemy_sprite.center_x = x
314
315                enemy_sprite.change_x = random.random() * 2.5 - 1.25
316                enemy_sprite.change_y = random.random() * 2.5 - 1.25
317
318                enemy_sprite.change_angle = (random.random() - 0.5) * 2
319                enemy_sprite.size = 3
320
321                self.asteroid_list.append(enemy_sprite)
322                self.hit_sound1.play()
323
324        elif asteroid.size == 3:
325            for i in range(3):
326                image_no = random.randrange(2)
327                image_list = [":resources:images/space_shooter/meteorGrey_small1.png",
328                              ":resources:images/space_shooter/meteorGrey_small2.png"]
329
330                enemy_sprite = AsteroidSprite(image_list[image_no],
331                                              SCALE * 1.5)
332
333                enemy_sprite.center_y = y
334                enemy_sprite.center_x = x
335
336                enemy_sprite.change_x = random.random() * 3 - 1.5
337                enemy_sprite.change_y = random.random() * 3 - 1.5
338
339                enemy_sprite.change_angle = (random.random() - 0.5) * 2
340                enemy_sprite.size = 2
341
342                self.asteroid_list.append(enemy_sprite)
343                self.hit_sound2.play()
344
345        elif asteroid.size == 2:
346            for i in range(3):
347                image_no = random.randrange(2)
348                image_list = [":resources:images/space_shooter/meteorGrey_tiny1.png",
349                              ":resources:images/space_shooter/meteorGrey_tiny2.png"]
350
351                enemy_sprite = AsteroidSprite(image_list[image_no],
352                                              SCALE * 1.5)
353
354                enemy_sprite.center_y = y
355                enemy_sprite.center_x = x
356
357                enemy_sprite.change_x = random.random() * 3.5 - 1.75
358                enemy_sprite.change_y = random.random() * 3.5 - 1.75
359
360                enemy_sprite.change_angle = (random.random() - 0.5) * 2
361                enemy_sprite.size = 1
362
363                self.asteroid_list.append(enemy_sprite)
364                self.hit_sound3.play()
365
366        elif asteroid.size == 1:
367            self.hit_sound4.play()
368
369    def on_update(self, x):
370        """ Move everything """
371
372        if not self.game_over:
373            self.asteroid_list.update()
374            self.bullet_list.update()
375            self.player_sprite_list.update()
376
377            for bullet in self.bullet_list:
378                asteroids = arcade.check_for_collision_with_list(bullet,
379                                                                 self.asteroid_list)
380
381                for asteroid in asteroids:
382                    # expected AsteroidSprite, got Sprite instead
383                    self.split_asteroid(cast(AsteroidSprite, asteroid))
384                    asteroid.remove_from_sprite_lists()
385                    bullet.remove_from_sprite_lists()
386
387                # Remove bullet if it goes off-screen
388                size = max(bullet.width, bullet.height)
389                if bullet.center_x < 0 - size:
390                    bullet.remove_from_sprite_lists()
391                if bullet.center_x > SCREEN_WIDTH + size:
392                    bullet.remove_from_sprite_lists()
393                if bullet.center_y < 0 - size:
394                    bullet.remove_from_sprite_lists()
395                if bullet.center_y > SCREEN_HEIGHT + size:
396                    bullet.remove_from_sprite_lists()
397
398            if not self.player_sprite.respawning:
399                asteroids = arcade.check_for_collision_with_list(self.player_sprite,
400                                                                 self.asteroid_list)
401                if len(asteroids) > 0:
402                    if self.lives > 0:
403                        self.lives -= 1
404                        self.player_sprite.respawn()
405                        self.split_asteroid(cast(AsteroidSprite, asteroids[0]))
406                        asteroids[0].remove_from_sprite_lists()
407                        self.ship_life_list.pop().remove_from_sprite_lists()
408                        print("Crash")
409                    else:
410                        self.game_over = True
411                        print("Game over")
412
413        # Update the text objects
414        self.text_score.text = f"Score: {self.score}"
415        self.text_asteroid_count.text = f"Asteroid Count: {len(self.asteroid_list)}"
416
417
418def main():
419    """ Start the game """
420    window = MyGame()
421    window.start_new_game()
422    arcade.run()
423
424
425if __name__ == "__main__":
426    main()