20. Using Sprites to Shoot

How do we get sprites that we can shoot?

../../_images/sprites_bullet.gif

Coins shooting

20.1. Getting Started

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

Starting program for shooting sprites
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
import random
import arcade

SPRITE_SCALING_PLAYER = 0.5
SPRITE_SCALING_COIN = 0.2
SPRITE_SCALING_LASER = 0.8
COIN_COUNT = 50

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600

BULLET_SPEED = 5


class MyGame(arcade.Window):
    """ Main application class. """

    def __init__(self):
        """ Initializer """
        # Call the parent class initializer
        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, "Sprites and Bullets Demo")

        # Variables that will hold sprite lists
        self.player_list = None
        self.coin_list = None

        # Set up the player info
        self.player_sprite = None
        self.score = 0

        # Don't show the mouse cursor
        self.set_mouse_visible(False)

        arcade.set_background_color(arcade.color.AMAZON)

    def setup(self):

        """ Set up the game and initialize the variables. """

        # Sprite lists
        self.player_list = arcade.SpriteList()
        self.coin_list = arcade.SpriteList()

        # Set up the player
        self.score = 0

        # Image from kenney.nl
        self.player_sprite = arcade.Sprite("character.png", SPRITE_SCALING_PLAYER)
        self.player_sprite.center_x = 50
        self.player_sprite.center_y = 70
        self.player_list.append(self.player_sprite)

        # 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(SCREEN_HEIGHT)

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

        # Set the background color
        arcade.set_background_color(arcade.color.AMAZON)

    def on_draw(self):
        """
        Render the screen.
        """

        # This command has to happen before we start drawing
        arcade.start_render()

        # Draw all the sprites.
        self.coin_list.draw()
        self.player_list.draw()

        # Render the text
        arcade.draw_text(f"Score: {self.score}", 10, 20, arcade.color.WHITE, 14)

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

    def on_mouse_press(self, x, y, button, modifiers):
        """
        Called whenever the mouse button is clicked.
        """
        pass

    def update(self, delta_time):
        """ Movement and game logic """

        # Call update on all sprites
        self.coin_list.update()


def main():
    window = MyGame()
    window.setup()
    arcade.run()


if __name__ == "__main__":
    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.

20.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

20.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)

20.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
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import random
import arcade

SPRITE_SCALING_PLAYER = 0.5
SPRITE_SCALING_COIN = 0.2
SPRITE_SCALING_LASER = 0.8
COIN_COUNT = 50

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600

BULLET_SPEED = 5


class MyGame(arcade.Window):
    """ Main application class. """

    def __init__(self):
        """ Initializer """
        # Call the parent class initializer
        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, "Sprites and Bullets Demo")

        # Variables that will hold sprite lists
        self.player_list = None
        self.coin_list = None
        self.bullet_list = None

        # Set up the player info
        self.player_sprite = None
        self.score = 0

        # Don't show the mouse cursor
        self.set_mouse_visible(False)

        arcade.set_background_color(arcade.color.AMAZON)

    def setup(self):

        """ Set up the game and initialize the variables. """

        # Sprite lists
        self.player_list = arcade.SpriteList()
        self.coin_list = arcade.SpriteList()
        self.bullet_list = arcade.SpriteList()

        # Set up the player
        self.score = 0

        # Image from kenney.nl
        self.player_sprite = arcade.Sprite("character.png", SPRITE_SCALING_PLAYER)
        self.player_sprite.center_x = 50
        self.player_sprite.center_y = 70
        self.player_list.append(self.player_sprite)

        # 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)

        # Set the background color
        arcade.set_background_color(arcade.color.AMAZON)

    def on_draw(self):
        """
        Render the screen.
        """

        # This command has to happen before we start drawing
        arcade.start_render()

        # Draw all the sprites.
        self.coin_list.draw()
        self.player_list.draw()
        self.bullet_list.draw()

        # Render the text
        arcade.draw_text(f"Score: {self.score}", 10, 20, arcade.color.WHITE, 14)

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

    def on_mouse_press(self, x, y, button, modifiers):
        """
        Called whenever the mouse button is clicked.
        """
        pass

    def update(self, delta_time):
        """ Movement and game logic """

        # Call update on all sprites
        self.coin_list.update()
        self.bullet_list.update()


def main():
    window = MyGame()
    window.setup()
    arcade.run()


if __name__ == "__main__":
    main()

20.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 player 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)

20.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.kill()

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

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

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
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import random
import arcade

SPRITE_SCALING_PLAYER = 0.5
SPRITE_SCALING_COIN = 0.2
SPRITE_SCALING_LASER = 0.8
COIN_COUNT = 50

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600

BULLET_SPEED = 5


class MyGame(arcade.Window):
    """ Main application class. """

    def __init__(self):
        """ Initializer """
        # Call the parent class initializer
        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, "Sprites and Bullets Demo")

        # Variables that will hold sprite lists
        self.player_list = None
        self.coin_list = None
        self.bullet_list = None

        # Set up the player info
        self.player_sprite = None
        self.score = 0

        # Don't show the mouse cursor
        self.set_mouse_visible(False)

        arcade.set_background_color(arcade.color.AMAZON)

    def setup(self):

        """ Set up the game and initialize the variables. """

        # Sprite lists
        self.player_list = arcade.SpriteList()
        self.coin_list = arcade.SpriteList()
        self.bullet_list = arcade.SpriteList()

        # Set up the player
        self.score = 0

        # Image from kenney.nl
        self.player_sprite = arcade.Sprite("character.png", SPRITE_SCALING_PLAYER)
        self.player_sprite.center_x = 50
        self.player_sprite.center_y = 70
        self.player_list.append(self.player_sprite)

        # 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(120, SCREEN_HEIGHT)

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

        # Set the background color
        arcade.set_background_color(arcade.color.AMAZON)

    def on_draw(self):
        """
        Render the screen.
        """

        # This command has to happen before we start drawing
        arcade.start_render()

        # Draw all the sprites.
        self.coin_list.draw()
        self.bullet_list.draw()
        self.player_list.draw()

        # Render the text
        arcade.draw_text(f"Score: {self.score}", 10, 20, arcade.color.WHITE, 14)

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

    def on_mouse_press(self, x, y, button, modifiers):
        """
        Called whenever the mouse button is clicked.
        """

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

        # The image points to the right, and we want it to point up. So
        # rotate it.
        bullet.angle = 90

        # Position the bullet
        bullet.center_x = self.player_sprite.center_x
        bullet.bottom = self.player_sprite.top

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

    def update(self, delta_time):
        """ Movement and game logic """

        # Call update on all sprites
        self.coin_list.update()
        self.bullet_list.update()

        # 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.kill()

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

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


def main():
    window = MyGame()
    window.setup()
    arcade.run()


if __name__ == "__main__":
    main()