22. Moving Sprites

How do we get sprites to move?

To customize our sprite’s behavior, we need to subclass the Sprite class with our own child class. This is easy:

class Coin(arcade.Sprite):

We need to provide each sprite with a update method. The update method is automatically called to update the sprite’s position.

class Coin(arcade.Sprite):

    def update(self):
        # Code to move goes here

Wait! We have a new class called Coin, but we aren’t using it. Find in our original code this line:

coin = arcade.Sprite("coin_01.png", COIN_SPRITE_SCALING)

See how it is creating an instance of Sprite? We want to create an instance of our new Coin class instead:

coin = Coin("coin_01.png", COIN_SPRITE_SCALING)

Now, how do we get the coin to move?

22.1. Moving Sprites Down

To get the sprites to “fall” down the screen, we need to make their y location smaller. This is easy. Over-ride update in the sprite and subtract from y each frame:

class Coin(arcade.Sprite):

    def update(self):
        self.center_y -= 1

Next, create an instance of the Coin class instead of a Sprite class.

Sprite Sample Move Down
  1""" Sprite Sample Program """
  2
  3import random
  4import arcade
  5
  6# --- Constants ---
  7SPRITE_SCALING_PLAYER = 0.5
  8SPRITE_SCALING_COIN = 0.2
  9COIN_COUNT = 50
 10
 11SCREEN_WIDTH = 800
 12SCREEN_HEIGHT = 600
 13
 14
 15class Coin(arcade.Sprite):
 16
 17    def update(self):
 18        self.center_y -= 1
 19
 20
 21class MyGame(arcade.Window):
 22    """ Our custom Window Class"""
 23
 24    def __init__(self):
 25        """ Initializer """
 26        # Call the parent class initializer
 27        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, "Sprite Example")
 28
 29        # Variables that will hold sprite lists
 30        self.player_list = None
 31        self.coin_list = None
 32
 33        # Set up the player info
 34        self.player_sprite = None
 35        self.score = 0
 36
 37        # Don't show the mouse cursor
 38        self.set_mouse_visible(False)
 39
 40        arcade.set_background_color(arcade.color.AMAZON)
 41
 42    def setup(self):
 43        """ Set up the game and initialize the variables. """
 44
 45        # Sprite lists
 46        self.player_list = arcade.SpriteList()
 47        self.coin_list = arcade.SpriteList()
 48
 49        # Score
 50        self.score = 0
 51
 52        # Set up the player
 53        # Character image from kenney.nl
 54        self.player_sprite = arcade.Sprite("character.png", SPRITE_SCALING_PLAYER)
 55        self.player_sprite.center_x = 50
 56        self.player_sprite.center_y = 50
 57        self.player_list.append(self.player_sprite)
 58
 59        # Create the coins
 60        for i in range(COIN_COUNT):
 61
 62            # Create the coin instance
 63            # Coin image from kenney.nl
 64            coin = Coin("coin_01.png", SPRITE_SCALING_COIN)
 65
 66            # Position the coin
 67            coin.center_x = random.randrange(SCREEN_WIDTH)
 68            coin.center_y = random.randrange(SCREEN_HEIGHT)
 69
 70            # Add the coin to the lists
 71            self.coin_list.append(coin)
 72
 73    def on_draw(self):
 74        """ Draw everything """
 75        arcade.start_render()
 76        self.coin_list.draw()
 77        self.player_list.draw()
 78
 79        # Put the text on the screen.
 80        output = f"Score: {self.score}"
 81        arcade.draw_text(output, 10, 20, arcade.color.WHITE, 14)
 82
 83    def on_mouse_motion(self, x, y, dx, dy):
 84        """ Handle Mouse Motion """
 85
 86        # Move the center of the player sprite to match the mouse x, y
 87        self.player_sprite.center_x = x
 88        self.player_sprite.center_y = y
 89
 90    def update(self, delta_time):
 91        """ Movement and game logic """
 92
 93        # Call update on all sprites (The sprites don't do much in this
 94        # example though.)
 95        self.coin_list.update()
 96
 97        # Generate a list of all sprites that collided with the player.
 98        hit_list = arcade.check_for_collision_with_list(self.player_sprite,
 99                                                        self.coin_list)
100
101        # Loop through each colliding sprite, remove it, and add to the score.
102        for coin in hit_list:
103            coin.remove_from_sprite_lists()
104            self.score += 1
105
106
107def main():
108    """ Main method """
109    window = MyGame()
110    window.setup()
111    arcade.run()
112
113
114if __name__ == "__main__":
115    main()

This causes the coins to move down. But once they move off the screen they keep going into negative-coordinate land. We can’t see them any more. Sad.

../../_images/coins_down_1.gif

Coins moving down

22.1.1. Resetting to the Top

We can get around this by resetting the coins up to the top. Here’s how its done:

class Coin(arcade.Sprite):

    def update(self):
        self.center_y -= 1

        # See if we went off-screen
        if self.center_y < 0:
            self.center_y = SCREEN_HEIGHT

But this isn’t perfect. Because if your eyes are fast, you can see the coin ‘pop’ in and out of existence at the edge. It doesn’t smoothly slide off. This is because we move it when the center of the coin is at the edge. Not the top of the coin has slid off.

There are a couple ways we can do this. Here’s one. We’ll check at -20 instead of 0. As long as the coin radius is 20 or less, we are good.

class Coin(arcade.Sprite):

    def update(self):
        self.center_y -= 1

        # See if we went off-screen
        if self.center_y < -20:
            self.center_y = SCREEN_HEIGHT + 20

There’s another way. In addition to center_y, sprites have other members that are useful in these cases. They are top, bottom, left and right. So we can do this:

class Coin(arcade.Sprite):

    def update(self):
        self.center_y -= 1

        # See if we went off-screen
        if self.top < 0:
            self.bottom = SCREEN_HEIGHT

Doing this allows the coins to smoothly slide on and off the screen. But since they reappear at the top, we get repeating patters. See the image below:

../../_images/pattern.gif

Coins repeating in a pattern

Instead we can randomize it a bit:

def update(self):

    # Move the coin
    self.center_y -= 1

    # See if the coin has fallen off the bottom of the screen.
    # If so, reset it.
    if self.top < 0:
        # Reset the coin to a random spot above the screen
        self.center_y = random.randrange(SCREEN_HEIGHT + 20,
                                         SCREEN_HEIGHT + 100)
        self.center_x = random.randrange(SCREEN_WIDTH)

22.1.2. Never Ending Coins

This works, but when we we collect all the coins we are done. What if it was a never-ending set of coins? Instead of “killing” the coin, let’s reset it to the top of the screen.

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

    self.coin_list.update()

    hit_list = arcade.check_for_collision_with_list(self.player_sprite, self.coin_list)

    for coin in hit_list:
        self.score += 1

        # Reset the coin to a random spot above the screen
        coin.center_y = random.randrange(SCREEN_HEIGHT + 20,
                                         SCREEN_HEIGHT + 100)
        coin.center_x = random.randrange(SCREEN_WIDTH)

We can even take that common code, and move it to a method. Here’s a full example:

Full Move Down Sprite Sample
  1""" Sprite Sample Program """
  2
  3import random
  4import arcade
  5
  6# --- Constants ---
  7SPRITE_SCALING_PLAYER = 0.5
  8SPRITE_SCALING_COIN = 0.2
  9COIN_COUNT = 50
 10
 11SCREEN_WIDTH = 800
 12SCREEN_HEIGHT = 600
 13
 14
 15class Coin(arcade.Sprite):
 16    """
 17    This class represents the coins on our screen. It is a child class of
 18    the arcade library's "Sprite" class.
 19    """
 20
 21    def reset_pos(self):
 22
 23        # Reset the coin to a random spot above the screen
 24        self.center_y = random.randrange(SCREEN_HEIGHT + 20,
 25                                         SCREEN_HEIGHT + 100)
 26        self.center_x = random.randrange(SCREEN_WIDTH)
 27
 28    def update(self):
 29
 30        # Move the coin
 31        self.center_y -= 1
 32
 33        # See if the coin has fallen off the bottom of the screen.
 34        # If so, reset it.
 35        if self.top < 0:
 36            self.reset_pos()
 37
 38
 39class MyGame(arcade.Window):
 40    """ Our custom Window Class"""
 41
 42    def __init__(self):
 43        """ Initializer """
 44        # Call the parent class initializer
 45        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, "Sprite Example")
 46
 47        # Variables that will hold sprite lists
 48        self.player_list = None
 49        self.coin_list = None
 50
 51        # Set up the player info
 52        self.player_sprite = None
 53        self.score = 0
 54
 55        # Don't show the mouse cursor
 56        self.set_mouse_visible(False)
 57
 58        arcade.set_background_color(arcade.color.AMAZON)
 59
 60    def setup(self):
 61        """ Set up the game and initialize the variables. """
 62
 63        # Sprite lists
 64        self.player_list = arcade.SpriteList()
 65        self.coin_list = arcade.SpriteList()
 66
 67        # Score
 68        self.score = 0
 69
 70        # Set up the player
 71        # Character image from kenney.nl
 72        self.player_sprite = arcade.Sprite("character.png", SPRITE_SCALING_PLAYER)
 73        self.player_sprite.center_x = 50
 74        self.player_sprite.center_y = 50
 75        self.player_list.append(self.player_sprite)
 76
 77        # Create the coins
 78        for i in range(COIN_COUNT):
 79
 80            # Create the coin instance
 81            # Coin image from kenney.nl
 82            coin = Coin("coin_01.png", SPRITE_SCALING_COIN)
 83
 84            # Position the coin
 85            coin.center_x = random.randrange(SCREEN_WIDTH)
 86            coin.center_y = random.randrange(SCREEN_HEIGHT)
 87
 88            # Add the coin to the lists
 89            self.coin_list.append(coin)
 90
 91    def on_draw(self):
 92        """ Draw everything """
 93        arcade.start_render()
 94        self.coin_list.draw()
 95        self.player_list.draw()
 96
 97        # Put the text on the screen.
 98        output = f"Score: {self.score}"
 99        arcade.draw_text(output, 10, 20, arcade.color.WHITE, 14)
100
101    def on_mouse_motion(self, x, y, dx, dy):
102        """ Handle Mouse Motion """
103
104        # Move the center of the player sprite to match the mouse x, y
105        self.player_sprite.center_x = x
106        self.player_sprite.center_y = y
107
108    def update(self, delta_time):
109        """ Movement and game logic """
110
111        # Call update on all sprites (The sprites don't do much in this
112        # example though.)
113        self.coin_list.update()
114
115        # Generate a list of all sprites that collided with the player.
116        hit_list = arcade.check_for_collision_with_list(self.player_sprite,
117                                                        self.coin_list)
118
119        # Loop through each colliding sprite, remove it, and add to the score.
120        for coin in hit_list:
121            coin.reset_pos()
122            self.score += 1
123
124
125def main():
126    """ Main method """
127    window = MyGame()
128    window.setup()
129    arcade.run()
130
131
132if __name__ == "__main__":
133    main()

22.2. Bouncing Coins

Instead of always adding one to the y-coordinate and have the sprites move down, we can keep a vector by using change_x and change_y. By using these, we can have the sprite bounce around the screen:

../../_images/sprites_bouncing.gif

Coins bouncing around

sprites_sample_move_bouncing.py
  1""" Sprite Sample Program """
  2
  3import random
  4import arcade
  5
  6# --- Constants ---
  7SPRITE_SCALING_PLAYER = 0.5
  8SPRITE_SCALING_COIN = 0.2
  9COIN_COUNT = 50
 10
 11SCREEN_WIDTH = 800
 12SCREEN_HEIGHT = 600
 13
 14
 15class Coin(arcade.Sprite):
 16
 17    def __init__(self, filename, sprite_scaling):
 18
 19        super().__init__(filename, sprite_scaling)
 20
 21        self.change_x = 0
 22        self.change_y = 0
 23
 24    def update(self):
 25
 26        # Move the coin
 27        self.center_x += self.change_x
 28        self.center_y += self.change_y
 29
 30        # If we are out-of-bounds, then 'bounce'
 31        if self.left < 0:
 32            self.change_x *= -1
 33
 34        if self.right > SCREEN_WIDTH:
 35            self.change_x *= -1
 36
 37        if self.bottom < 0:
 38            self.change_y *= -1
 39
 40        if self.top > SCREEN_HEIGHT:
 41            self.change_y *= -1
 42
 43
 44class MyGame(arcade.Window):
 45    """ Our custom Window Class"""
 46
 47    def __init__(self):
 48        """ Initializer """
 49        # Call the parent class initializer
 50        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, "Sprite Example")
 51
 52        # Variables that will hold sprite lists
 53        self.player_list = None
 54        self.coin_list = None
 55
 56        # Set up the player info
 57        self.player_sprite = None
 58        self.score = 0
 59
 60        # Don't show the mouse cursor
 61        self.set_mouse_visible(False)
 62
 63        arcade.set_background_color(arcade.color.AMAZON)
 64
 65    def setup(self):
 66        """ Set up the game and initialize the variables. """
 67
 68        # Sprite lists
 69        self.player_list = arcade.SpriteList()
 70        self.coin_list = arcade.SpriteList()
 71
 72        # Score
 73        self.score = 0
 74
 75        # Set up the player
 76        # Character image from kenney.nl
 77        self.player_sprite = arcade.Sprite("character.png", SPRITE_SCALING_PLAYER)
 78        self.player_sprite.center_x = 50
 79        self.player_sprite.center_y = 50
 80        self.player_list.append(self.player_sprite)
 81
 82        # Create the coins
 83        for i in range(COIN_COUNT):
 84
 85            # Create the coin instance
 86            # Coin image from kenney.nl
 87            coin = Coin("coin_01.png", SPRITE_SCALING_COIN)
 88
 89            # Position the coin
 90            coin.center_x = random.randrange(SCREEN_WIDTH)
 91            coin.center_y = random.randrange(SCREEN_HEIGHT)
 92            coin.change_x = random.randrange(-3, 4)
 93            coin.change_y = random.randrange(-3, 4)
 94
 95            # Add the coin to the lists
 96            self.coin_list.append(coin)
 97
 98    def on_draw(self):
 99        """ Draw everything """
100        arcade.start_render()
101        self.coin_list.draw()
102        self.player_list.draw()
103
104        # Put the text on the screen.
105        output = f"Score: {self.score}"
106        arcade.draw_text(output, 10, 20, arcade.color.WHITE, 14)
107
108    def on_mouse_motion(self, x, y, dx, dy):
109        """ Handle Mouse Motion """
110
111        # Move the center of the player sprite to match the mouse x, y
112        self.player_sprite.center_x = x
113        self.player_sprite.center_y = y
114
115    def update(self, delta_time):
116        """ Movement and game logic """
117
118        # Call update on all sprites (The sprites don't do much in this
119        # example though.)
120        self.coin_list.update()
121
122        # Generate a list of all sprites that collided with the player.
123        hit_list = arcade.check_for_collision_with_list(self.player_sprite,
124                                                        self.coin_list)
125
126        # Loop through each colliding sprite, remove it, and add to the score.
127        for coin in hit_list:
128            coin.remove_from_sprite_lists()
129            self.score += 1
130
131
132def main():
133    """ Main method """
134    window = MyGame()
135    window.setup()
136    arcade.run()
137
138
139if __name__ == "__main__":
140    main()

TODO: Put in some text about spawning a sprite too close to the edge. Also make a refer to it from the final project.

Take what you’ve learned from the example above, and see if you can replicate this:

../../_images/Test_Pattern.gif

Test Pattern

22.3. Coins Moving In Circles

../../_images/sprites_circle.gif

Coins moving in a circle

sprites_circle.py
  1import random
  2import arcade
  3import math
  4
  5SPRITE_SCALING = 0.5
  6
  7SCREEN_WIDTH = 800
  8SCREEN_HEIGHT = 600
  9
 10
 11class Coin(arcade.Sprite):
 12
 13    def __init__(self, filename, sprite_scaling):
 14        """ Constructor. """
 15        # Call the parent class (Sprite) constructor
 16        super().__init__(filename, sprite_scaling)
 17
 18        # Current angle in radians
 19        self.circle_angle = 0
 20
 21        # How far away from the center to orbit, in pixels
 22        self.circle_radius = 0
 23
 24        # How fast to orbit, in radians per frame
 25        self.circle_speed = 0.008
 26
 27        # Set the center of the point we will orbit around
 28        self.circle_center_x = 0
 29        self.circle_center_y = 0
 30
 31    def update(self):
 32
 33        """ Update the ball's position. """
 34        # Calculate a new x, y
 35        self.center_x = self.circle_radius * math.sin(self.circle_angle) \
 36            + self.circle_center_x
 37        self.center_y = self.circle_radius * math.cos(self.circle_angle) \
 38            + self.circle_center_y
 39
 40        # Increase the angle in prep for the next round.
 41        self.circle_angle += self.circle_speed
 42
 43
 44class MyGame(arcade.Window):
 45    """ Main application class. """
 46
 47    def __init__(self, width, height):
 48
 49        super().__init__(width, height)
 50
 51        # Sprite lists
 52        self.player_list = None
 53        self.coin_list = None
 54
 55        # Set up the player
 56        self.score = 0
 57        self.player_sprite = None
 58
 59    def start_new_game(self):
 60        """ Set up the game and initialize the variables. """
 61
 62        # Sprite lists
 63        self.player_list = arcade.SpriteList()
 64        self.coin_list = arcade.SpriteList()
 65
 66        # Set up the player
 67        self.score = 0
 68
 69        # Character image from kenney.nl
 70        self.player_sprite = arcade.Sprite("character.png", SPRITE_SCALING)
 71        self.player_sprite.center_x = 50
 72        self.player_sprite.center_y = 70
 73        self.player_list.append(self.player_sprite)
 74
 75        for i in range(50):
 76
 77            # Create the coin instance
 78            # Coin image from kenney.nl
 79            coin = Coin("coin_01.png", SPRITE_SCALING / 3)
 80
 81            # Position the center of the circle the coin will orbit
 82            coin.circle_center_x = random.randrange(SCREEN_WIDTH)
 83            coin.circle_center_y = random.randrange(SCREEN_HEIGHT)
 84
 85            # Random radius from 10 to 200
 86            coin.circle_radius = random.randrange(10, 200)
 87
 88            # Random start angle from 0 to 2pi
 89            coin.circle_angle = random.random() * 2 * math.pi
 90
 91            # Add the coin to the lists
 92            self.coin_list.append(coin)
 93
 94        # Don't show the mouse cursor
 95        self.set_mouse_visible(False)
 96
 97        # Set the background color
 98        arcade.set_background_color(arcade.color.AMAZON)
 99
100    def on_draw(self):
101
102        # This command has to happen before we start drawing
103        arcade.start_render()
104
105        # Draw all the sprites.
106        self.coin_list.draw()
107        self.player_list.draw()
108
109        # Put the text on the screen.
110        output = "Score: " + str(self.score)
111        arcade.draw_text(output, 10, 20, arcade.color.WHITE, 14)
112
113    def on_mouse_motion(self, x, y, dx, dy):
114        self.player_sprite.center_x = x
115        self.player_sprite.center_y = y
116
117    def update(self, delta_time):
118        """ Movement and game logic """
119
120        # Call update on all sprites (The sprites don't do much in this
121        # example though.)
122        self.coin_list.update()
123
124        # Generate a list of all sprites that collided with the player.
125        hit_list = arcade.check_for_collision_with_list(self.player_sprite,
126                                                        self.coin_list)
127
128        # Loop through each colliding sprite, remove it, and add to the score.
129        for coin in hit_list:
130            self.score += 1
131            coin.remove_from_sprite_lists()
132
133
134def main():
135    window = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT)
136    window.start_new_game()
137    arcade.run()
138
139
140if __name__ == "__main__":
141    main()

22.4. Rotating Sprites

Sprites can easily be rotated by setting their angle attribute. Angles are in degrees. So the following will flip the player upside down:

self.player_sprite.angle = 180

If you put this in the coin’s update method, it would cause the coins to spin:

self.angle += 1

# If we rotate past 360, reset it back a turn.
if self.angle > 359:
    self.angle -= 360