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