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