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