Step 10 - Adding a Score

Our game is starting to take shape, but we still need to give the player a reward for their hard work collecting coins. To do this we will add a score which will be increased everytime they collect a coin, and display that on the screen.

In this chapter we will cover using arcade.Text objects, as well as a technique for using two cameras to draw objects in “screen space” and objects in “world space”.

Note

What is screen space and world space? Think about other games you may have played, and let’s compare it to our game. A player moves around in the world, and we scroll a camera around based on that position. This is an example of “world space” coordinates. They can expand beyond our window and need to be positioned within the window accordingly.

An example of “screen space” coordinates is our score indicator. We will draw this on our screen, but we don’t want it to move around the screen when the camera scrolls around. To achieve this we will use two different cameras, and move the world space camera, but not move the screen space camera.

In our code, we will call this screen space camera, gui_camera

Let’s go ahead and add a variable for our new camera and initialize it in setup. We will also add a variable for our score. This will just be an integer initially set to 0. We will set this in both __init__ and setup.

# Within __init__
self.gui_camera = None
self.score = 0

# Within setup
self.gui_camera = arcade.SimpleCamera(viewport=(0, 0, width, height))
self.score = 0

Now we can go into our on_update function, and when the player collects a coin, we can increment our score variable. For now we will give the player 75 points for collecting a coin. You can change this, or as an exercise try adding different types of coins with different point values. In later chapters we’ll explore dynamically providing point values for coins from a map editor.

# Within on_update
for coin in coin_hit_list:
    coin.remove_from_sprite_lists()
    arcade.play_sound(self.collect_coin_sound)
    self.score += 75

Now that we’re incrementing our score, how do we draw it onto the screen? Well we will be using our GUI camera, but so far we haven’t talked about drawing Text in Arcade. There are a couple of ways we can do this in Arcade, the first way is using the arcade.draw_text() function. This is a simple function that you can put directly in on_draw to draw a string of text.

This function however, is not very performant, and there is a better way. We will instead use arcade.Text objects. These have many advantages, like not needing to re-calculate the text everytime it’s drawn, and also can be batch drawn much like how we do with Sprite and SpriteList. We will explore batch drawing Text later.

For now, let’s create an arcade.Text object to hold our score text. First create the empty variable in __init__ and initialize in setup.

# Within __init__
self.score_text = None

# Within setup
self.score_text = arcade.Text(f"Score: {self.score}", start_x = 0, start_y = 5)

The first parameter we send to arcade.Text is a String containing the text we want to draw. In our example we provide an f-string which adds our value from self.score into the text. The other parameters are defining the bottom left point that our text will be drawn at.

I’ve set it to draw in the bottom left of our screen here. You can try moving it around.

Now we need to add this to our on_draw function in order to get it to display on the screen.

# Within on_draw
self.gui_camera.use()
self.score_text.draw()

This will now draw our text in the bottom left of the screen. However, we stil have one problem left, we’re not updating the text when our user gets a new score. In order to do this we will go back to our on_update function, where we incremented the score when the user collects a coin, and add one more line to it:

for coin in coin_hit_list:
    coin.remove_from_sprite_lists()
    arcade.play_sound(self.collect_coin_sound)
    self.score += 75
    self.score_text.text = f"Score: {self.score}"

In this new line we’re udpating the actual text of our Text object to contain the new score value.

Source Code

Multiple Levels
  1"""
  2Platformer Game
  3
  4python -m arcade.examples.platform_tutorial.10_score
  5"""
  6import arcade
  7
  8# Constants
  9WINDOW_WIDTH = 1280
 10WINDOW_HEIGHT = 720
 11WINDOW_TITLE = "Platformer"
 12
 13# Constants used to scale our sprites from their original size
 14TILE_SCALING = 0.5
 15COIN_SCALING = 0.5
 16
 17# Movement speed of player, in pixels per frame
 18PLAYER_MOVEMENT_SPEED = 5
 19GRAVITY = 1
 20PLAYER_JUMP_SPEED = 20
 21
 22
 23class GameView(arcade.Window):
 24    """
 25    Main application class.
 26    """
 27
 28    def __init__(self):
 29
 30        # Call the parent class and set up the window
 31        super().__init__(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE)
 32
 33        # Variable to hold our texture for our player
 34        self.player_texture = None
 35
 36        # Separate variable that holds the player sprite
 37        self.player_sprite = None
 38
 39        # SpriteList for our player
 40        self.player_list = None
 41
 42        # SpriteList for our boxes and ground
 43        # Putting our ground and box Sprites in the same SpriteList
 44        # will make it easier to perform collision detection against
 45        # them later on. Setting the spatial hash to True will make
 46        # collision detection much faster if the objects in this
 47        # SpriteList do not move.
 48        self.wall_list = None
 49
 50        # SpriteList for coins the player can collect
 51        self.coin_list = None
 52
 53        # A variable to store our camera object
 54        self.camera = None
 55
 56        # A variable to store our gui camera object
 57        self.gui_camera = None
 58
 59        # This variable will store our score as an integer.
 60        self.score = 0
 61
 62        # This variable will store the text for score that we will draw to the screen.
 63        self.score_text = None
 64
 65        # Load sounds
 66        self.collect_coin_sound = arcade.load_sound(":resources:sounds/coin1.wav")
 67        self.jump_sound = arcade.load_sound(":resources:sounds/jump1.wav")
 68
 69    def setup(self):
 70        """Set up the game here. Call this function to restart the game."""
 71        self.player_texture = arcade.load_texture(
 72            ":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png"
 73        )
 74
 75        self.player_sprite = arcade.Sprite(self.player_texture)
 76        self.player_sprite.center_x = 64
 77        self.player_sprite.center_y = 128
 78
 79        self.player_list = arcade.SpriteList()
 80        self.player_list.append(self.player_sprite)
 81
 82        self.wall_list = arcade.SpriteList(use_spatial_hash=True)
 83        self.coin_list = arcade.SpriteList(use_spatial_hash=True)
 84
 85        # Create the ground
 86        # This shows using a loop to place multiple sprites horizontally
 87        for x in range(0, 1250, 64):
 88            wall = arcade.Sprite(":resources:images/tiles/grassMid.png", scale=TILE_SCALING)
 89            wall.center_x = x
 90            wall.center_y = 32
 91            self.wall_list.append(wall)
 92
 93        # Put some crates on the ground
 94        # This shows using a coordinate list to place sprites
 95        coordinate_list = [[512, 96], [256, 96], [768, 96]]
 96
 97        for coordinate in coordinate_list:
 98            # Add a crate on the ground
 99            wall = arcade.Sprite(
100                ":resources:images/tiles/boxCrate_double.png", scale=TILE_SCALING
101            )
102            wall.position = coordinate
103            self.wall_list.append(wall)
104
105        # Add coins to the world
106        for x in range(128, 1250, 256):
107            coin = arcade.Sprite(":resources:images/items/coinGold.png", scale=COIN_SCALING)
108            coin.center_x = x
109            coin.center_y = 96
110            self.coin_list.append(coin)
111
112        # Create a Platformer Physics Engine, this will handle moving our
113        # player as well as collisions between the player sprite and
114        # whatever SpriteList we specify for the walls.
115        # It is important to supply static to the walls parameter. There is a
116        # platforms parameter that is intended for moving platforms.
117        # If a platform is supposed to move, and is added to the walls list,
118        # it will not be moved.
119        self.physics_engine = arcade.PhysicsEnginePlatformer(
120            self.player_sprite, walls=self.wall_list, gravity_constant=GRAVITY
121        )
122
123        # Initialize our camera, setting a viewport the size of our window.
124        self.camera = arcade.camera.Camera2D()
125
126        # Initialize our gui camera, initial settings are the same as our world camera.
127        self.gui_camera = arcade.camera.Camera2D()
128
129        # Reset our score to 0
130        self.score = 0
131
132        # Initialize our arcade.Text object for score
133        self.score_text = arcade.Text(f"Score: {self.score}", x=0, y=5)
134
135        self.background_color = arcade.csscolor.CORNFLOWER_BLUE
136
137    def on_draw(self):
138        """Render the screen."""
139
140        # Clear the screen to the background color
141        self.clear()
142
143        # Activate our camera before drawing
144        self.camera.use()
145
146        # Draw our sprites
147        self.player_list.draw()
148        self.wall_list.draw()
149        self.coin_list.draw()
150
151        # Activate our GUI camera
152        self.gui_camera.use()
153
154        # Draw our Score
155        self.score_text.draw()
156
157    def on_update(self, delta_time):
158        """Movement and Game Logic"""
159
160        # Move the player using our physics engine
161        self.physics_engine.update()
162
163        # See if we hit any coins
164        coin_hit_list = arcade.check_for_collision_with_list(
165            self.player_sprite, self.coin_list
166        )
167
168        # Loop through each coin we hit (if any) and remove it
169        for coin in coin_hit_list:
170            # Remove the coin
171            coin.remove_from_sprite_lists()
172            arcade.play_sound(self.collect_coin_sound)
173            self.score += 75
174            self.score_text.text = f"Score: {self.score}"
175
176        # Center our camera on the player
177        self.camera.position = self.player_sprite.position
178
179    def on_key_press(self, key, modifiers):
180        """Called whenever a key is pressed."""
181
182        if key == arcade.key.ESCAPE:
183            self.setup()
184
185        if key == arcade.key.UP or key == arcade.key.W:
186            if self.physics_engine.can_jump():
187                self.player_sprite.change_y = PLAYER_JUMP_SPEED
188                arcade.play_sound(self.jump_sound)
189
190        if key == arcade.key.LEFT or key == arcade.key.A:
191            self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
192        elif key == arcade.key.RIGHT or key == arcade.key.D:
193            self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
194
195    def on_key_release(self, key, modifiers):
196        """Called whenever a key is released."""
197
198        if key == arcade.key.LEFT or key == arcade.key.A:
199            self.player_sprite.change_x = 0
200        elif key == arcade.key.RIGHT or key == arcade.key.D:
201            self.player_sprite.change_x = 0
202
203
204def main():
205    """Main function"""
206    window = GameView()
207    window.setup()
208    arcade.run()
209
210
211if __name__ == "__main__":
212    main()

Run This Chapter

python -m arcade.examples.platform_tutorial.10_score