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