Step 11 - Using a Scene#

So far in our game, we have three SpriteLists. One for our player, one for our walls(ground and boxes), and one for our coins. This is still manageable, but whatabout as our game grows? You can probably imagine a game could end up with hundreds of SpriteLists. Using just our current approach, we would have to keep track of variables for each one, and ensure we’re drawing them in the proper order.

Arcade provides a better way to handle this, with the arcade.Scene class. This class will hold all of our spritelists for us, allow us to create new ones, change around the order they get drawn in, and more. In later chapters we will we use a special function to load a map from a map editor tool, and automatically create a Scene based on the map.

At the end of this chapter, you will have the same result as before, but the code will be a bit different to use the Scene object.

First-off, we can remove all of our SpriteList variables from __init__ and replace them with on variable to hold the scene object:

self.scene = None

Now at the very top of our setup function we can initialize the scene by doing:

self.scene = arcade.Scene()

Next, we will remove the line in setup that initializes our Player spritelist, that line looked like this:

self.player_list = arcade.SpriteList()

Then, instead of adding our player to the SpriteList using self.player_sprite.append(). We will add the player to the Scene directly:

self.player_sprite = arcade.Sprite(self.player_texture)
self.player_sprite.center_x = 64
self.player_sprite.center_y = 128
self.scene.add_sprite("Player", self.player_sprite)

Let’s analyze what happens when we do arcade.Scene.add_sprite(). The first parameter to it is a String, this defines the layer name that we want to add a Sprite to. This can be an already existing layer or a new one. If the layer already exists, the Sprite will be added to it, and if it doesn’t, Scene will automatically create it. Under the hood, a layer is just a SpriteList with a name. So when we specify Player as our Layer. Scene is creating a new SpriteList, giving it that name, and then adding our Player Sprite to it.

Next we will replace our initialization of the wall and coin SpriteLists with these functions:

self.scene.add_sprite_list("Walls", use_spatial_hash=True)
self.scene.add_sprite_list("Coins", use_spatial_hash=True)

Here we are taking a little bit different approach than we did for our Player layer. For our player, we just added a Sprite directly. Here we are initialization new empty layers, named Walls and Coins. The advantage to this approach is that we can specify that this layer should use spatial hashing, like we specified for those SpriteLists before.

Now when we use the add_sprite function on these lists later, those Sprites will be added into these existing layers.

In order to add Sprites to these, let’s modify the self.wall_list.append() functions within the for loops for placing our walls and coins in the setup function. The only part we’re actually changing of these loops is the last line where we were adding it to the SpriteList, but I’ve included the loops so you can see where all it should be changed.

# Create the ground
for x in range(0, 1250, 64):
    wall = arcade.Sprite(":resources:images/tiles/grassMid.png", scale=TILE_SCALING)
    wall.center_x = x
    wall.center_y = 32
    self.scene.add_sprite("Walls", wall)

# Putting Crates on the Ground
coordinate_list = [[512, 96], [256, 96], [768, 96]]

for coordinate in coordinate_li
    wall = arcade.Sprite(
        ":resources:images/tiles/boxCrate_double.png", scale=TILE_SCALING
    )
    wall.position = coordinate
    self.scene.add_sprite("Walls", wall)

# Add coins to the world
for x in range(128, 1250, 256):
    coin = arcade.Sprite(":resources:images/items/coinGold.png", scale=COIN_SCALING)
    coin.center_x = x
    coin.center_y = 96
    self.scene.add_sprite("Coins", coin)

The next thing we need to do is fix our Physics Engine. If you remember back in Chapter 4, we added a physics engine and sent our Wall spritelist to in the walls parameter.

We’ll need to modify that our PhysicsEnginePlatformer initialization to this:

self.physics_engine = arcade.PhysicsEnginePlatformer(
    self.player_sprite, walls=self.scene["Walls"], gravity_constant=GRAVITY
)

This is mostly the same as before, but we are pulling the Walls SpriteList from our Scene. If you are familiar with Python dictionaries, the arcade.Scene class can be interacted with in a very similar way. You can get any specific SpriteList within the scene by passing the name in brackets to the scene.

We need to also change our arcade.check_for_collision_with_list() function in on_update that we are using to get the coins we hit to use this new syntax.

coin_hit_list = arcade.check_for_collision_with_list(
    self.player_sprite, self.scene["Coins"]
)

The last thing that we need to do is update our on_draw function. In here we will remove all our SpriteLists draws, and replace them with one line drawing our Scene.

self.scene.draw()

Note

Make sure to keep this after our world camera is activated and before our GUI camera is activated. If you draw the scene while the GUI camera is activated, the centering on the player and scrolling will not work.

Source Code#

Using a Scene#
  1"""
  2Platformer Game
  3
  4python -m arcade.examples.platform_tutorial.11_scene
  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        # Replacing all of our SpriteLists with a Scene variable
 40        self.scene = None
 41
 42        # A variable to store our camera object
 43        self.camera = None
 44
 45        # A variable to store our gui camera object
 46        self.gui_camera = None
 47
 48        # This variable will store our score as an integer.
 49        self.score = 0
 50
 51        # This variable will store the text for score that we will draw to the screen.
 52        self.score_text = None
 53
 54        # Load sounds
 55        self.collect_coin_sound = arcade.load_sound(":resources:sounds/coin1.wav")
 56        self.jump_sound = arcade.load_sound(":resources:sounds/jump1.wav")
 57
 58    def setup(self):
 59        """Set up the game here. Call this function to restart the game."""
 60        self.scene = arcade.Scene()
 61
 62        self.player_texture = arcade.load_texture(":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png")
 63
 64        self.player_sprite = arcade.Sprite(self.player_texture)
 65        self.player_sprite.center_x = 64
 66        self.player_sprite.center_y = 128
 67        self.scene.add_sprite("Player", self.player_sprite)
 68
 69        self.scene.add_sprite_list("Walls", use_spatial_hash=True)
 70        self.scene.add_sprite_list("Coins", use_spatial_hash=True)
 71
 72        # Create the ground
 73        # This shows using a loop to place multiple sprites horizontally
 74        for x in range(0, 1250, 64):
 75            wall = arcade.Sprite(":resources:images/tiles/grassMid.png", scale=TILE_SCALING)
 76            wall.center_x = x
 77            wall.center_y = 32
 78            self.scene.add_sprite("Walls", wall)
 79
 80        # Put some crates on the ground
 81        # This shows using a coordinate list to place sprites
 82        coordinate_list = [[512, 96], [256, 96], [768, 96]]
 83
 84        for coordinate in coordinate_list:
 85            # Add a crate on the ground
 86            wall = arcade.Sprite(
 87                ":resources:images/tiles/boxCrate_double.png", scale=TILE_SCALING
 88            )
 89            wall.position = coordinate
 90            self.scene.add_sprite("Walls", wall)
 91
 92        # Add coins to the world
 93        for x in range(128, 1250, 256):
 94            coin = arcade.Sprite(":resources:images/items/coinGold.png", scale=COIN_SCALING)
 95            coin.center_x = x
 96            coin.center_y = 96
 97            self.scene.add_sprite("Coins", coin)
 98
 99        # Create a Platformer Physics Engine, this will handle moving our
100        # player as well as collisions between the player sprite and
101        # whatever SpriteList we specify for the walls.
102        # It is important to supply static to the walls parameter. There is a
103        # platforms parameter that is intended for moving platforms.
104        # If a platform is supposed to move, and is added to the walls list,
105        # it will not be moved.
106        self.physics_engine = arcade.PhysicsEnginePlatformer(
107            self.player_sprite, walls=self.scene["Walls"], gravity_constant=GRAVITY
108        )
109
110        # Initialize our camera, setting a viewport the size of our window.
111        self.camera = arcade.SimpleCamera(viewport=(0, 0, self.width, self.height))
112
113        # Initialize our gui camera, initial settings are the same as our world camera.
114        self.gui_camera = arcade.SimpleCamera(viewport=(0, 0, self.width, self.height))
115
116        # Reset our score to 0
117        self.score = 0
118
119        # Initialize our arcade.Text object for score
120        self.score_text = arcade.Text(f"Score: {self.score}", start_x = 0, start_y = 5)
121
122        self.background_color = arcade.csscolor.CORNFLOWER_BLUE
123
124    def on_draw(self):
125        """Render the screen."""
126
127        # Clear the screen to the background color
128        self.clear()
129
130        # Activate our camera before drawing
131        self.camera.use()
132
133        # Draw our Scene
134        self.scene.draw()
135
136        # Activate our GUI camera
137        self.gui_camera.use()
138
139        # Draw our Score
140        self.score_text.draw()
141
142    def on_update(self, delta_time):
143        """Movement and Game Logic"""
144
145        # Move the player using our physics engine
146        self.physics_engine.update()
147
148        # See if we hit any coins
149        coin_hit_list = arcade.check_for_collision_with_list(
150            self.player_sprite, self.scene["Coins"]
151        )
152
153        # Loop through each coin we hit (if any) and remove it
154        for coin in coin_hit_list:
155            # Remove the coin
156            coin.remove_from_sprite_lists()
157            arcade.play_sound(self.collect_coin_sound)
158            self.score += 75
159            self.score_text.text = f"Score: {self.score}"
160
161        # Center our camera on the player
162        self.camera.center(self.player_sprite.position)
163
164    def on_key_press(self, key, modifiers):
165        """Called whenever a key is pressed."""
166
167        if key == arcade.key.ESCAPE:
168            self.setup()
169
170        if key == arcade.key.UP or key == arcade.key.W:
171            if self.physics_engine.can_jump():
172                self.player_sprite.change_y = PLAYER_JUMP_SPEED
173                arcade.play_sound(self.jump_sound)
174
175        if key == arcade.key.LEFT or key == arcade.key.A:
176            self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
177        elif key == arcade.key.RIGHT or key == arcade.key.D:
178            self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
179
180    def on_key_release(self, key, modifiers):
181        """Called whenever a key is released."""
182
183        if key == arcade.key.LEFT or key == arcade.key.A:
184            self.player_sprite.change_x = 0
185        elif key == arcade.key.RIGHT or key == arcade.key.D:
186            self.player_sprite.change_x = 0
187
188
189def main():
190    """Main function"""
191    window = MyGame()
192    window.setup()
193    arcade.run()
194
195
196if __name__ == "__main__":
197    main()

Run This Chapter#

python -m arcade.examples.platform_tutorial.11_scene