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