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()