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