Slime Invaders

slime_invaders.py
1"""
2Slime Invaders
3
4Artwork from https://kenney.nl
5
6This example shows how to:
7
8* Get sprites to move as a group
9* Change texture of sprites as a group
10* Only have the bottom sprite in the group fire lasers
11* Create 'shields' like in space invaders
12
13If Python and Arcade are installed, this example can be run from the command line with:
14python -m arcade.examples.slime_invaders
15"""
16import random
17import arcade
18
19SPRITE_SCALING_PLAYER = 0.75
20SPRITE_SCALING_enemy = 0.75
21SPRITE_SCALING_LASER = 1.0
22
23WINDOW_WIDTH = 1280
24WINDOW_HEIGHT = 720
25WINDOW_TITLE = "Slime Invaders"
26
27BULLET_SPEED = 5
28ENEMY_SPEED = 2
29
30MAX_PLAYER_BULLETS = 3
31
32# This margin controls how close the enemy gets to the left or right side
33# before reversing direction.
34ENEMY_VERTICAL_MARGIN = 15
35RIGHT_ENEMY_BORDER = WINDOW_WIDTH - ENEMY_VERTICAL_MARGIN
36LEFT_ENEMY_BORDER = ENEMY_VERTICAL_MARGIN
37
38# How many pixels to move the enemy down when reversing
39ENEMY_MOVE_DOWN_AMOUNT = 30
40
41# Game state
42GAME_OVER = 1
43PLAY_GAME = 0
44
45
46class GameView(arcade.View):
47 """ Main application class. """
48
49 def __init__(self):
50 """ Initializer """
51 # Call the parent class initializer
52 super().__init__()
53
54 # Variables that will hold sprite lists
55 self.player_list = arcade.SpriteList()
56 self.enemy_list = arcade.SpriteList()
57 self.player_bullet_list = arcade.SpriteList()
58 self.enemy_bullet_list = arcade.SpriteList()
59 self.shield_list = arcade.SpriteList()
60
61 # State of the game
62 self.game_state = PLAY_GAME
63
64 # Set up the player info
65 self.player_sprite = arcade.Sprite(
66 ":resources:images/animated_characters/female_person/femalePerson_idle.png",
67 scale=SPRITE_SCALING_PLAYER,
68 )
69 self.player_list.append(self.player_sprite)
70
71 self.score = 0
72
73 # Enemy movement
74 self.enemy_change_x = -ENEMY_SPEED
75
76 # Don't show the mouse cursor
77 self.window.set_mouse_visible(False)
78
79 # Load sounds. Sounds from kenney.nl
80 self.gun_sound = arcade.load_sound(":resources:sounds/hurt5.wav")
81 self.hit_sound = arcade.load_sound(":resources:sounds/hit5.wav")
82 self.texture_enemy_left = arcade.load_texture(
83 ":resources:images/enemies/slimeBlue.png",
84 )
85 self.texture_enemy_right = self.texture_enemy_left.flip_left_right()
86 # The laser points right so we rotate it 270 clockwise to point up
87 self.texture_blue_laser = arcade.load_texture(
88 ":resources:images/space_shooter/laserBlue01.png",
89 ).rotate_270()
90
91 self.background_color = arcade.color.AMAZON
92 self.score_text = arcade.Text("Score: 0", 10, 20, arcade.color.WHITE, 14)
93 self.game_over_text = arcade.Text(
94 "GAME OVER",
95 x=self.width / 2,
96 y=self.height / 2,
97 color=arcade.color.WHITE,
98 font_size=60,
99 anchor_x="center",
100 )
101
102 def setup_level_one(self):
103 # Load the textures for the enemies, one facing left, one right
104 # Create rows and columns of enemies
105 x_count = 7
106 x_start = 380
107 x_spacing = 80
108 y_count = 5
109 y_start = 470
110 y_spacing = 60
111 for x in range(x_start, x_spacing * x_count + x_start, x_spacing):
112 for y in range(y_start, y_spacing * y_count + y_start, y_spacing):
113 # Create the enemy instance
114 # enemy image from kenney.nl
115 enemy = arcade.Sprite(
116 self.texture_enemy_right,
117 scale=SPRITE_SCALING_enemy,
118 center_x=x,
119 center_y=y
120 )
121 # Add the enemy to the lists
122 self.enemy_list.append(enemy)
123
124 def make_shield(self, x_start):
125 """
126 Make a shield, which is just a 2D grid of solid color sprites
127 stuck together with no margin so you can't tell them apart.
128 """
129 shield_block_width = 10
130 shield_block_height = 20
131 shield_width_count = 20
132 shield_height_count = 5
133 y_start = 150
134 for x in range(x_start,
135 x_start + shield_width_count * shield_block_width,
136 shield_block_width):
137 for y in range(y_start,
138 y_start + shield_height_count * shield_block_height,
139 shield_block_height):
140 shield_sprite = arcade.SpriteSolidColor(shield_block_width,
141 shield_block_height,
142 color=arcade.color.WHITE)
143 shield_sprite.center_x = x
144 shield_sprite.center_y = y
145 self.shield_list.append(shield_sprite)
146
147 def reset(self):
148 """
149 Reset the game so it can be played again.
150 This is not a standard Arcade method. It's simply an example of how
151 you might reset the game.
152 """
153 self.game_state = PLAY_GAME
154
155 # Clear the sprite lists
156 self.enemy_list.clear()
157 self.player_bullet_list.clear()
158 self.enemy_bullet_list.clear()
159 self.shield_list.clear()
160
161 # Set up the player
162 self.score = 0
163
164 # Set default position for player
165 self.player_sprite.center_x = 50
166 self.player_sprite.center_y = 70
167
168 # Make each of the shields
169 step = self.width // 4 - 50
170 print("make_shield", step)
171 for x in [step, step * 2, step * 3]:
172 print(x)
173 self.make_shield(x)
174
175 # Set the background color
176 self.background_color = arcade.color.AMAZON
177
178 self.setup_level_one()
179
180 def on_draw(self):
181 """Render the screen."""
182 # Clear the window / screen with the configured background color
183 self.clear()
184
185 # Draw all the sprites.
186 self.enemy_list.draw()
187 self.player_bullet_list.draw()
188 self.enemy_bullet_list.draw()
189 self.shield_list.draw(pixelated=True)
190 self.player_list.draw()
191
192 # Update and draw the score
193 self.score_text.text = f"Score: {self.score}"
194 self.score_text.draw()
195
196 # Draw game over if the game state is such
197 if self.game_state == GAME_OVER:
198 self.game_over_text.draw()
199 self.window.set_mouse_visible(True)
200
201 def on_key_press(self, key, modifiers):
202 if key == arcade.key.ESCAPE:
203 self.window.close()
204
205 def on_mouse_motion(self, x, y, dx, dy):
206 """
207 Called whenever the mouse moves.
208 """
209 # Don't move the player if the game is over
210 if self.game_state == GAME_OVER:
211 return
212
213 self.player_sprite.center_x = x
214
215 def on_mouse_press(self, x, y, button, modifiers):
216 """
217 Called whenever the mouse button is clicked.
218 """
219 # Only allow the user so many bullets on screen at a time to prevent
220 # them from spamming bullets.
221 if len(self.player_bullet_list) < MAX_PLAYER_BULLETS:
222 # Gunshot sound
223 # arcade.play_sound(self.gun_sound)
224
225 # Create a bullet
226 bullet = arcade.Sprite(self.texture_blue_laser, scale=SPRITE_SCALING_LASER)
227
228 # Give the bullet a speed
229 bullet.change_y = BULLET_SPEED
230
231 # Position the bullet
232 bullet.center_x = self.player_sprite.center_x
233 bullet.bottom = self.player_sprite.top
234
235 # Add the bullet to the appropriate lists
236 self.player_bullet_list.append(bullet)
237
238 def update_enemies(self):
239 # Move the enemy vertically
240 for enemy in self.enemy_list:
241 enemy.center_x += self.enemy_change_x
242
243 # Check every enemy to see if any hit the edge. If so, reverse the
244 # direction and flag to move down.
245 move_down = False
246 for enemy in self.enemy_list:
247 if enemy.right > RIGHT_ENEMY_BORDER and self.enemy_change_x > 0:
248 self.enemy_change_x *= -1
249 move_down = True
250 if enemy.left < LEFT_ENEMY_BORDER and self.enemy_change_x < 0:
251 self.enemy_change_x *= -1
252 move_down = True
253
254 # Did we hit the edge above, and need to move t he enemy down?
255 if move_down:
256 # Yes
257 for enemy in self.enemy_list:
258 # Move enemy down
259 enemy.center_y -= ENEMY_MOVE_DOWN_AMOUNT
260 # Flip texture on enemy so it faces the other way
261 if self.enemy_change_x > 0:
262 enemy.texture = self.texture_enemy_left
263 else:
264 enemy.texture = self.texture_enemy_right
265
266 def allow_enemies_to_fire(self):
267 """
268 See if any enemies will fire this frame.
269 """
270 # Track which x values have had a chance to fire a bullet.
271 # Since enemy list is build from the bottom up, we can use
272 # this to only allow the bottom row to fire.
273 x_spawn = []
274 for enemy in self.enemy_list:
275 # Adjust the chance depending on the number of enemies. Fewer
276 # enemies, more likely to fire.
277 chance = 4 + len(self.enemy_list) * 4
278
279 # Fire if we roll a zero, and no one else in this column has had
280 # a chance to fire.
281 if random.randrange(chance) == 0 and enemy.center_x not in x_spawn:
282 # Create a bullet
283 bullet = arcade.Sprite(
284 ":resources:images/space_shooter/laserRed01.png",
285 scale=SPRITE_SCALING_LASER,
286 )
287
288 # Angle down.
289 bullet.angle = 180
290
291 # Give the bullet a speed
292 bullet.change_y = -BULLET_SPEED
293
294 # Position the bullet so its top id right below the enemy
295 bullet.center_x = enemy.center_x
296 bullet.top = enemy.bottom
297
298 # Add the bullet to the appropriate list
299 self.enemy_bullet_list.append(bullet)
300
301 # Ok, this column has had a chance to fire. Add to list so we don't
302 # try it again this frame.
303 x_spawn.append(enemy.center_x)
304
305 def process_enemy_bullets(self):
306 # Move the bullets
307 self.enemy_bullet_list.update()
308
309 # Loop through each bullet
310 for bullet in self.enemy_bullet_list:
311 # Check this bullet to see if it hit a shield
312 hit_list = arcade.check_for_collision_with_list(bullet, self.shield_list)
313
314 # If it did, get rid of the bullet and shield blocks
315 if len(hit_list) > 0:
316 bullet.remove_from_sprite_lists()
317 for shield in hit_list:
318 shield.remove_from_sprite_lists()
319 continue
320
321 # See if the player got hit with a bullet
322 if arcade.check_for_collision_with_list(self.player_sprite, self.enemy_bullet_list):
323 self.game_state = GAME_OVER
324
325 # If the bullet falls off the screen get rid of it
326 if bullet.top < 0:
327 bullet.remove_from_sprite_lists()
328
329 def process_player_bullets(self):
330 # Move the bullets
331 self.player_bullet_list.update()
332
333 # Loop through each bullet
334 for bullet in self.player_bullet_list:
335
336 # Check this bullet to see if it hit a enemy
337 hit_list = arcade.check_for_collision_with_list(bullet, self.shield_list)
338 # If it did, get rid of the bullet
339 if len(hit_list) > 0:
340 bullet.remove_from_sprite_lists()
341 for shield in hit_list:
342 shield.remove_from_sprite_lists()
343 continue
344
345 # Check this bullet to see if it hit a enemy
346 hit_list = arcade.check_for_collision_with_list(bullet, self.enemy_list)
347
348 # If it did, get rid of the bullet
349 if len(hit_list) > 0:
350 bullet.remove_from_sprite_lists()
351
352 # For every enemy we hit, add to the score and remove the enemy
353 for enemy in hit_list:
354 enemy.remove_from_sprite_lists()
355 self.score += 1
356
357 # Hit Sound
358 arcade.play_sound(self.hit_sound)
359
360 # If the bullet flies off-screen, remove it.
361 if bullet.bottom > WINDOW_HEIGHT:
362 bullet.remove_from_sprite_lists()
363
364 def on_update(self, delta_time):
365 """ Movement and game logic """
366 if self.game_state == GAME_OVER:
367 return
368
369 self.update_enemies()
370 self.allow_enemies_to_fire()
371 self.process_enemy_bullets()
372 self.process_player_bullets()
373
374 if len(self.enemy_list) == 0:
375 self.setup_level_one()
376
377
378def main():
379 """ Main function """
380 # Create a window class. This is what actually shows up on screen
381 window = arcade.Window(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE)
382
383 # Create and setup the GameView
384 game = GameView()
385 game.reset()
386
387 # Show GameView on screen
388 window.show_view(game)
389
390 # Start the arcade game loop
391 arcade.run()
392
393
394if __name__ == "__main__":
395 main()