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