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
  9SCREEN_WIDTH = 800
 10SCREEN_HEIGHT = 600
 11SCREEN_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 MyGame(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__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_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(":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png")
 72
 73        self.player_sprite = arcade.Sprite(self.player_texture)
 74        self.player_sprite.center_x = 64
 75        self.player_sprite.center_y = 128
 76
 77        self.player_list = arcade.SpriteList()
 78        self.player_list.append(self.player_sprite)
 79
 80        self.wall_list = arcade.SpriteList(use_spatial_hash=True)
 81        self.coin_list = arcade.SpriteList(use_spatial_hash=True)
 82
 83        # Create the ground
 84        # This shows using a loop to place multiple sprites horizontally
 85        for x in range(0, 1250, 64):
 86            wall = arcade.Sprite(":resources:images/tiles/grassMid.png", scale=TILE_SCALING)
 87            wall.center_x = x
 88            wall.center_y = 32
 89            self.wall_list.append(wall)
 90
 91        # Put some crates on the ground
 92        # This shows using a coordinate list to place sprites
 93        coordinate_list = [[512, 96], [256, 96], [768, 96]]
 94
 95        for coordinate in coordinate_list:
 96            # Add a crate on the ground
 97            wall = arcade.Sprite(
 98                ":resources:images/tiles/boxCrate_double.png", scale=TILE_SCALING
 99            )
100            wall.position = coordinate
101            self.wall_list.append(wall)
102
103        # Add coins to the world
104        for x in range(128, 1250, 256):
105            coin = arcade.Sprite(":resources:images/items/coinGold.png", scale=COIN_SCALING)
106            coin.center_x = x
107            coin.center_y = 96
108            self.coin_list.append(coin)
109
110        # Create a Platformer Physics Engine, this will handle moving our
111        # player as well as collisions between the player sprite and
112        # whatever SpriteList we specify for the walls.
113        # It is important to supply static to the walls parameter. There is a
114        # platforms parameter that is intended for moving platforms.
115        # If a platform is supposed to move, and is added to the walls list,
116        # it will not be moved.
117        self.physics_engine = arcade.PhysicsEnginePlatformer(
118            self.player_sprite, walls=self.wall_list, gravity_constant=GRAVITY
119        )
120
121        # Initialize our camera, setting a viewport the size of our window.
122        self.camera = arcade.camera.Camera2D()
123
124        # Initialize our gui camera, initial settings are the same as our world camera.
125        self.gui_camera = arcade.camera.Camera2D()
126
127        # Reset our score to 0
128        self.score = 0
129
130        # Initialize our arcade.Text object for score
131        self.score_text = arcade.Text(f"Score: {self.score}", x=0, y=5)
132
133        self.background_color = arcade.csscolor.CORNFLOWER_BLUE
134
135    def on_draw(self):
136        """Render the screen."""
137
138        # Clear the screen to the background color
139        self.clear()
140
141        # Activate our camera before drawing
142        self.camera.use()
143
144        # Draw our sprites
145        self.player_list.draw()
146        self.wall_list.draw()
147        self.coin_list.draw()
148
149        # Activate our GUI camera
150        self.gui_camera.use()
151
152        # Draw our Score
153        self.score_text.draw()
154
155    def on_update(self, delta_time):
156        """Movement and Game Logic"""
157
158        # Move the player using our physics engine
159        self.physics_engine.update()
160
161        # See if we hit any coins
162        coin_hit_list = arcade.check_for_collision_with_list(
163            self.player_sprite, self.coin_list
164        )
165
166        # Loop through each coin we hit (if any) and remove it
167        for coin in coin_hit_list:
168            # Remove the coin
169            coin.remove_from_sprite_lists()
170            arcade.play_sound(self.collect_coin_sound)
171            self.score += 75
172            self.score_text.text = f"Score: {self.score}"
173
174        # Center our camera on the player
175        self.camera.position = self.player_sprite.position
176
177    def on_key_press(self, key, modifiers):
178        """Called whenever a key is pressed."""
179
180        if key == arcade.key.ESCAPE:
181            self.setup()
182
183        if key == arcade.key.UP or key == arcade.key.W:
184            if self.physics_engine.can_jump():
185                self.player_sprite.change_y = PLAYER_JUMP_SPEED
186                arcade.play_sound(self.jump_sound)
187
188        if key == arcade.key.LEFT or key == arcade.key.A:
189            self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
190        elif key == arcade.key.RIGHT or key == arcade.key.D:
191            self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
192
193    def on_key_release(self, key, modifiers):
194        """Called whenever a key is released."""
195
196        if key == arcade.key.LEFT or key == arcade.key.A:
197            self.player_sprite.change_x = 0
198        elif key == arcade.key.RIGHT or key == arcade.key.D:
199            self.player_sprite.change_x = 0
200
201
202def main():
203    """Main function"""
204    window = MyGame()
205    window.setup()
206    arcade.run()
207
208
209if __name__ == "__main__":
210    main()

Run This Chapter

python -m arcade.examples.platform_tutorial.10_score