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