Slime Invaders#

Screenshot of 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"""
 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()