Asteroid Smasher¶
This is a sample asteroid smasher game made with the Arcade library.
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()