Slime Invaders

Screenshot of Slime Invaders
slime_invaders.py
  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()