24. Using Sprites to Shoot

How do we get sprites that we can shoot?

../../_images/sprites_bullet.gif

Coins shooting

24.1. Getting Started

First, let’s go back to a program to start with.

Starting program for shooting sprites
  1import random
  2import arcade
  3
  4SPRITE_SCALING_PLAYER = 0.5
  5SPRITE_SCALING_COIN = 0.2
  6SPRITE_SCALING_LASER = 0.8
  7COIN_COUNT = 50
  8
  9SCREEN_WIDTH = 800
 10SCREEN_HEIGHT = 600
 11
 12BULLET_SPEED = 5
 13
 14
 15class MyGame(arcade.Window):
 16    """ Main application class. """
 17
 18    def __init__(self):
 19        """ Initializer """
 20        # Call the parent class initializer
 21        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, "Sprites and Bullets Demo")
 22
 23        # Variables that will hold sprite lists
 24        self.player_list = None
 25        self.coin_list = None
 26
 27        # Set up the player info
 28        self.player_sprite = None
 29        self.score = 0
 30
 31        # Don't show the mouse cursor
 32        self.set_mouse_visible(False)
 33
 34        arcade.set_background_color(arcade.color.AMAZON)
 35
 36    def setup(self):
 37
 38        """ Set up the game and initialize the variables. """
 39
 40        # Sprite lists
 41        self.player_list = arcade.SpriteList()
 42        self.coin_list = arcade.SpriteList()
 43
 44        # Set up the player
 45        self.score = 0
 46
 47        # Image from kenney.nl
 48        self.player_sprite = arcade.Sprite("character.png", SPRITE_SCALING_PLAYER)
 49        self.player_sprite.center_x = 50
 50        self.player_sprite.center_y = 70
 51        self.player_list.append(self.player_sprite)
 52
 53        # Create the coins
 54        for i in range(COIN_COUNT):
 55
 56            # Create the coin instance
 57            # Coin image from kenney.nl
 58            coin = arcade.Sprite("coin_01.png", SPRITE_SCALING_COIN)
 59
 60            # Position the coin
 61            coin.center_x = random.randrange(SCREEN_WIDTH)
 62            coin.center_y = random.randrange(SCREEN_HEIGHT)
 63
 64            # Add the coin to the lists
 65            self.coin_list.append(coin)
 66
 67        # Set the background color
 68        arcade.set_background_color(arcade.color.AMAZON)
 69
 70    def on_draw(self):
 71        """
 72        Render the screen.
 73        """
 74
 75        # This command has to happen before we start drawing
 76        arcade.start_render()
 77
 78        # Draw all the sprites.
 79        self.coin_list.draw()
 80        self.player_list.draw()
 81
 82        # Render the text
 83        arcade.draw_text(f"Score: {self.score}", 10, 20, arcade.color.WHITE, 14)
 84
 85    def on_mouse_motion(self, x, y, dx, dy):
 86        """
 87        Called whenever the mouse moves.
 88        """
 89        self.player_sprite.center_x = x
 90        self.player_sprite.center_y = y
 91
 92    def on_mouse_press(self, x, y, button, modifiers):
 93        """
 94        Called whenever the mouse button is clicked.
 95        """
 96        pass
 97
 98    def update(self, delta_time):
 99        """ Movement and game logic """
100
101        # Call update on all sprites
102        self.coin_list.update()
103
104
105def main():
106    window = MyGame()
107    window.setup()
108    arcade.run()
109
110
111if __name__ == "__main__":
112    main()

If you run this program, the player should move around the screen, and their should be coins. But not much else is happening yet.

Next, we need a ‘shooting’ image:

../../_images/laserBlue01.png

laserBlue01.png

Download this image (originally from Kenney.nl) and make sure it is in the same folder as your code.

24.2. Keeping The Player At The Bottom

Right now the player can move anywhere on the screen. We want to keep that sprite fixed to the bottom of the screen.

To do that, just remove the line of code for moving the player on the y-axis. The player will keep the same y value that we set back in the setup method.

def on_mouse_motion(self, x, y, dx, dy):
    """
    Called whenever the mouse moves.
    """
    self.player_sprite.center_x = x
    # REMOVE THIS LINE: self.player_sprite.center_y = y

24.3. Moving The Coins Up

We want all the coins above the player. So we can adjust the starting y locations to have a starting point of 150 instead of 0. That will keep them above the player.

# Create the coins
for i in range(COIN_COUNT):

    # Create the coin instance
    # Coin image from kenney.nl
    coin = arcade.Sprite("coin_01.png", SPRITE_SCALING_COIN)

    # Position the coin
    coin.center_x = random.randrange(SCREEN_WIDTH)
    coin.center_y = random.randrange(150, SCREEN_HEIGHT)

    # Add the coin to the lists
    self.coin_list.append(coin)

24.4. Set Up Bullet List

We need to create a list to manage the bullets. There are four places we need to add this bullet_list code:

  • Create the bullet_list variable (Line 26)

  • Create an instance of SpriteList (Line 44)

  • Draw the bullet list (Line 83)

  • Update the bullet list (Line 105)

Set up bullet list
  1import random
  2import arcade
  3
  4SPRITE_SCALING_PLAYER = 0.5
  5SPRITE_SCALING_COIN = 0.2
  6SPRITE_SCALING_LASER = 0.8
  7COIN_COUNT = 50
  8
  9SCREEN_WIDTH = 800
 10SCREEN_HEIGHT = 600
 11
 12BULLET_SPEED = 5
 13
 14
 15class MyGame(arcade.Window):
 16    """ Main application class. """
 17
 18    def __init__(self):
 19        """ Initializer """
 20        # Call the parent class initializer
 21        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, "Sprites and Bullets Demo")
 22
 23        # Variables that will hold sprite lists
 24        self.player_list = None
 25        self.coin_list = None
 26        self.bullet_list = None
 27
 28        # Set up the player info
 29        self.player_sprite = None
 30        self.score = 0
 31
 32        # Don't show the mouse cursor
 33        self.set_mouse_visible(False)
 34
 35        arcade.set_background_color(arcade.color.AMAZON)
 36
 37    def setup(self):
 38
 39        """ Set up the game and initialize the variables. """
 40
 41        # Sprite lists
 42        self.player_list = arcade.SpriteList()
 43        self.coin_list = arcade.SpriteList()
 44        self.bullet_list = arcade.SpriteList()
 45
 46        # Set up the player
 47        self.score = 0
 48
 49        # Image from kenney.nl
 50        self.player_sprite = arcade.Sprite("character.png", SPRITE_SCALING_PLAYER)
 51        self.player_sprite.center_x = 50
 52        self.player_sprite.center_y = 70
 53        self.player_list.append(self.player_sprite)
 54
 55        # Create the coins
 56        for i in range(COIN_COUNT):
 57
 58            # Create the coin instance
 59            # Coin image from kenney.nl
 60            coin = arcade.Sprite("coin_01.png", SPRITE_SCALING_COIN)
 61
 62            # Position the coin
 63            coin.center_x = random.randrange(SCREEN_WIDTH)
 64            coin.center_y = random.randrange(150, SCREEN_HEIGHT)
 65
 66            # Add the coin to the lists
 67            self.coin_list.append(coin)
 68
 69        # Set the background color
 70        arcade.set_background_color(arcade.color.AMAZON)
 71
 72    def on_draw(self):
 73        """
 74        Render the screen.
 75        """
 76
 77        # This command has to happen before we start drawing
 78        arcade.start_render()
 79
 80        # Draw all the sprites.
 81        self.coin_list.draw()
 82        self.player_list.draw()
 83        self.bullet_list.draw()
 84
 85        # Render the text
 86        arcade.draw_text(f"Score: {self.score}", 10, 20, arcade.color.WHITE, 14)
 87
 88    def on_mouse_motion(self, x, y, dx, dy):
 89        """
 90        Called whenever the mouse moves.
 91        """
 92        self.player_sprite.center_x = x
 93
 94    def on_mouse_press(self, x, y, button, modifiers):
 95        """
 96        Called whenever the mouse button is clicked.
 97        """
 98        pass
 99
100    def update(self, delta_time):
101        """ Movement and game logic """
102
103        # Call update on all sprites
104        self.coin_list.update()
105        self.bullet_list.update()
106
107
108def main():
109    window = MyGame()
110    window.setup()
111    arcade.run()
112
113
114if __name__ == "__main__":
115    main()

24.5. Creating Bullets

We need to create bullets when the user presses the mouse button. We can add an on_mouse_press method to do something when the user presses the mouse button:

def on_mouse_press(self, x, y, button, modifiers):

    # Create a bullet
    bullet = arcade.Sprite("laserBlue01.png", SPRITE_SCALING_LASER)

    # Add the bullet to the appropriate list
    self.bullet_list.append(bullet)

This will create a bullet, but the bullet will default to the lower left corner. You can just barely see it.

We can give the bullet a position:

def on_mouse_press(self, x, y, button, modifiers):

    # Create a bullet
    bullet = arcade.Sprite("laserBlue01.png", SPRITE_SCALING_LASER)

    bullet.center_x = x
    bullet.center_y = y

    # Add the bullet to the appropriate list
    self.bullet_list.append(bullet)

But this isn’t what we want either. The code above puts the laser where we click the mouse. We want the laser to be where the player is. That’s easy:

def on_mouse_press(self, x, y, button, modifiers):

    # Create a bullet
    bullet = arcade.Sprite("laserBlue01.png", SPRITE_SCALING_LASER)

    bullet.center_x = self.player_sprite.center_x
    bullet.center_y = self.player_sprite.center_y

    # Add the bullet to the appropriate list
    self.bullet_list.append(bullet)

We can even start the bullet a bit ABOVE the player:

def on_mouse_press(self, x, y, button, modifiers):

    # Create a bullet
    bullet = arcade.Sprite("laserBlue01.png", SPRITE_SCALING_LASER)

    bullet.center_x = self.player_sprite.center_x
    bullet.center_y = self.player_sprite.center_y + 30

    # Add the bullet to the appropriate list
    self.bullet_list.append(bullet)

We can make the bullet move up using the constant BULLET_SPEED which we set to 5 at the top of the program:

def on_mouse_press(self, x, y, button, modifiers):

    # Create a bullet
    bullet = arcade.Sprite("laserBlue01.png", SPRITE_SCALING_LASER)

    bullet.center_x = self.player_sprite.center_x
    bullet.center_y = self.player_sprite.center_y + 30
    bullet.change_y = BULLET_SPEED

    # Add the bullet to the appropriate list
    self.bullet_list.append(bullet)

We can rotate the bullet so it isn’t sideways using the angle attribute built into the Sprite class:

def on_mouse_press(self, x, y, button, modifiers):

    # Create a bullet
    bullet = arcade.Sprite("laserBlue01.png", SPRITE_SCALING_LASER)

    bullet.center_x = self.player_sprite.center_x
    bullet.center_y = self.player_sprite.center_y + 30
    bullet.change_y = BULLET_SPEED
    bullet.angle = 90

    # Add the bullet to the appropriate list
    self.bullet_list.append(bullet)

24.6. Bullet Collisions

Now that we have bullets, how do we get them to collide with the coins? We add the following to our applications update method:

# Loop through each bullet
for bullet in self.bullet_list:

    # Check this bullet to see if it hit a coin
    hit_list = arcade.check_for_collision_with_list(bullet, self.coin_list)

    # If it did, get rid of the bullet
    if len(hit_list) > 0:
        bullet.remove_from_sprite_lists()

    # For every coin we hit, add to the score and remove the coin
    for coin in hit_list:
        coin.remove_from_sprite_lists()
        self.score += 1

    # If the bullet flies off-screen, remove it.
    if bullet.bottom > SCREEN_HEIGHT:
        bullet.remove_from_sprite_lists()

We loop through each bullet with a for loop. Then we check to see if the bullet is hitting any of the coins. If it is, we get rid of the coin. We get rid of the bullet.

We also check to see if the bullet flies off the top of the screen. If it does, we get rid of the bullet. This is easy to forget, but if you do, it will cause the computer to slow down because you are tracking thousands of bullets that have long ago left the space we care about.

Here’s the full example:

sprites_bullet.py
  1import random
  2import arcade
  3
  4SPRITE_SCALING_PLAYER = 0.5
  5SPRITE_SCALING_COIN = 0.2
  6SPRITE_SCALING_LASER = 0.8
  7COIN_COUNT = 50
  8
  9SCREEN_WIDTH = 800
 10SCREEN_HEIGHT = 600
 11
 12BULLET_SPEED = 5
 13
 14
 15class MyGame(arcade.Window):
 16    """ Main application class. """
 17
 18    def __init__(self):
 19        """ Initializer """
 20        # Call the parent class initializer
 21        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, "Sprites and Bullets Demo")
 22
 23        # Variables that will hold sprite lists
 24        self.player_list = None
 25        self.coin_list = None
 26        self.bullet_list = None
 27
 28        # Set up the player info
 29        self.player_sprite = None
 30        self.score = 0
 31
 32        # Don't show the mouse cursor
 33        self.set_mouse_visible(False)
 34
 35        arcade.set_background_color(arcade.color.AMAZON)
 36
 37    def setup(self):
 38
 39        """ Set up the game and initialize the variables. """
 40
 41        # Sprite lists
 42        self.player_list = arcade.SpriteList()
 43        self.coin_list = arcade.SpriteList()
 44        self.bullet_list = arcade.SpriteList()
 45
 46        # Set up the player
 47        self.score = 0
 48
 49        # Image from kenney.nl
 50        self.player_sprite = arcade.Sprite("character.png", SPRITE_SCALING_PLAYER)
 51        self.player_sprite.center_x = 50
 52        self.player_sprite.center_y = 70
 53        self.player_list.append(self.player_sprite)
 54
 55        # Create the coins
 56        for i in range(COIN_COUNT):
 57
 58            # Create the coin instance
 59            # Coin image from kenney.nl
 60            coin = arcade.Sprite("coin_01.png", SPRITE_SCALING_COIN)
 61
 62            # Position the coin
 63            coin.center_x = random.randrange(SCREEN_WIDTH)
 64            coin.center_y = random.randrange(120, SCREEN_HEIGHT)
 65
 66            # Add the coin to the lists
 67            self.coin_list.append(coin)
 68
 69        # Set the background color
 70        arcade.set_background_color(arcade.color.AMAZON)
 71
 72    def on_draw(self):
 73        """
 74        Render the screen.
 75        """
 76
 77        # This command has to happen before we start drawing
 78        arcade.start_render()
 79
 80        # Draw all the sprites.
 81        self.coin_list.draw()
 82        self.bullet_list.draw()
 83        self.player_list.draw()
 84
 85        # Render the text
 86        arcade.draw_text(f"Score: {self.score}", 10, 20, arcade.color.WHITE, 14)
 87
 88    def on_mouse_motion(self, x, y, dx, dy):
 89        """
 90        Called whenever the mouse moves.
 91        """
 92        self.player_sprite.center_x = x
 93
 94    def on_mouse_press(self, x, y, button, modifiers):
 95        """
 96        Called whenever the mouse button is clicked.
 97        """
 98
 99        # Create a bullet
100        bullet = arcade.Sprite("laserBlue01.png", SPRITE_SCALING_LASER)
101
102        # The image points to the right, and we want it to point up. So
103        # rotate it.
104        bullet.angle = 90
105
106        # Position the bullet
107        bullet.center_x = self.player_sprite.center_x
108        bullet.bottom = self.player_sprite.top
109
110        # Add the bullet to the appropriate lists
111        self.bullet_list.append(bullet)
112
113    def update(self, delta_time):
114        """ Movement and game logic """
115
116        # Call update on all sprites
117        self.coin_list.update()
118        self.bullet_list.update()
119
120        # Loop through each bullet
121        for bullet in self.bullet_list:
122
123            # Check this bullet to see if it hit a coin
124            hit_list = arcade.check_for_collision_with_list(bullet, self.coin_list)
125
126            # If it did, get rid of the bullet
127            if len(hit_list) > 0:
128                bullet.remove_from_sprite_lists()
129
130            # For every coin we hit, add to the score and remove the coin
131            for coin in hit_list:
132                coin.remove_from_sprite_lists()
133                self.score += 1
134
135            # If the bullet flies off-screen, remove it.
136            if bullet.bottom > SCREEN_HEIGHT:
137                bullet.remove_from_sprite_lists()
138
139
140def main():
141    window = MyGame()
142    window.setup()
143    arcade.run()
144
145
146if __name__ == "__main__":
147    main()