Step 9 - Multiple Levels and Other Layers¶
Here’s an expanded example:
This adds foreground, background, and “Don’t Touch” layers.
The background tiles appear behind the player
The foreground appears in front of the player
The Don’t Touch layer will reset the player to the start (228-237)
The player resets to the start if they fall off the map (217-226)
If the player gets to the right side of the map, the program attempts to load another layer
Add
level
attribute (69-70)Updated
setup
to load a file based on the level (76-144, specifically lines 77 and 115)Added end-of-map check(245-256)
1"""
2Platformer Game
3"""
4import arcade
5
6# Constants
7SCREEN_WIDTH = 1000
8SCREEN_HEIGHT = 650
9SCREEN_TITLE = "Platformer"
10
11# Constants used to scale our sprites from their original size
12CHARACTER_SCALING = 1
13TILE_SCALING = 0.5
14COIN_SCALING = 0.5
15SPRITE_PIXEL_SIZE = 128
16GRID_PIXEL_SIZE = (SPRITE_PIXEL_SIZE * TILE_SCALING)
17
18# Movement speed of player, in pixels per frame
19PLAYER_MOVEMENT_SPEED = 10
20GRAVITY = 1
21PLAYER_JUMP_SPEED = 20
22
23# How many pixels to keep as a minimum margin between the character
24# and the edge of the screen.
25LEFT_VIEWPORT_MARGIN = 200
26RIGHT_VIEWPORT_MARGIN = 200
27BOTTOM_VIEWPORT_MARGIN = 150
28TOP_VIEWPORT_MARGIN = 100
29
30PLAYER_START_X = 64
31PLAYER_START_Y = 225
32
33
34class MyGame(arcade.Window):
35 """
36 Main application class.
37 """
38
39 def __init__(self):
40
41 # Call the parent class and set up the window
42 super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
43
44 # These are 'lists' that keep track of our sprites. Each sprite should
45 # go into a list.
46 self.coin_list = None
47 self.wall_list = None
48 self.foreground_list = None
49 self.background_list = None
50 self.dont_touch_list = None
51 self.player_list = None
52
53 # Separate variable that holds the player sprite
54 self.player_sprite = None
55
56 # Our physics engine
57 self.physics_engine = None
58
59 # Used to keep track of our scrolling
60 self.view_bottom = 0
61 self.view_left = 0
62
63 # Keep track of the score
64 self.score = 0
65
66 # Where is the right edge of the map?
67 self.end_of_map = 0
68
69 # Level
70 self.level = 1
71
72 # Load sounds
73 self.collect_coin_sound = arcade.load_sound(":resources:sounds/coin1.wav")
74 self.jump_sound = arcade.load_sound(":resources:sounds/jump1.wav")
75 self.game_over = arcade.load_sound(":resources:sounds/gameover1.wav")
76
77 def setup(self, level):
78 """ Set up the game here. Call this function to restart the game. """
79
80 # Used to keep track of our scrolling
81 self.view_bottom = 0
82 self.view_left = 0
83
84 # Keep track of the score
85 self.score = 0
86
87 # Create the Sprite lists
88 self.player_list = arcade.SpriteList()
89 self.foreground_list = arcade.SpriteList()
90 self.background_list = arcade.SpriteList()
91 self.wall_list = arcade.SpriteList()
92 self.coin_list = arcade.SpriteList()
93
94 # Set up the player, specifically placing it at these coordinates.
95 image_source = ":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png"
96 self.player_sprite = arcade.Sprite(image_source, CHARACTER_SCALING)
97 self.player_sprite.center_x = PLAYER_START_X
98 self.player_sprite.center_y = PLAYER_START_Y
99 self.player_list.append(self.player_sprite)
100
101 # --- Load in a map from the tiled editor ---
102
103 # Name of the layer in the file that has our platforms/walls
104 platforms_layer_name = 'Platforms'
105 # Name of the layer that has items for pick-up
106 coins_layer_name = 'Coins'
107 # Name of the layer that has items for foreground
108 foreground_layer_name = 'Foreground'
109 # Name of the layer that has items for background
110 background_layer_name = 'Background'
111 # Name of the layer that has items we shouldn't touch
112 dont_touch_layer_name = "Don't Touch"
113
114 # Map name
115 map_name = f":resources:tmx_maps/map2_level_{level}.tmx"
116
117 # Read in the tiled map
118 my_map = arcade.tilemap.read_tmx(map_name)
119
120 # Calculate the right edge of the my_map in pixels
121 self.end_of_map = my_map.map_size.width * GRID_PIXEL_SIZE
122
123 # -- Background
124 self.background_list = arcade.tilemap.process_layer(my_map,
125 background_layer_name,
126 TILE_SCALING)
127
128 # -- Foreground
129 self.foreground_list = arcade.tilemap.process_layer(my_map,
130 foreground_layer_name,
131 TILE_SCALING)
132
133 # -- Platforms
134 self.wall_list = arcade.tilemap.process_layer(map_object=my_map,
135 layer_name=platforms_layer_name,
136 scaling=TILE_SCALING,
137 use_spatial_hash=True)
138
139 # -- Coins
140 self.coin_list = arcade.tilemap.process_layer(my_map,
141 coins_layer_name,
142 TILE_SCALING,
143 use_spatial_hash=True)
144
145 # -- Don't Touch Layer
146 self.dont_touch_list = arcade.tilemap.process_layer(my_map,
147 dont_touch_layer_name,
148 TILE_SCALING,
149 use_spatial_hash=True)
150
151 # --- Other stuff
152 # Set the background color
153 if my_map.background_color:
154 arcade.set_background_color(my_map.background_color)
155
156 # Create the 'physics engine'
157 self.physics_engine = arcade.PhysicsEnginePlatformer(self.player_sprite,
158 self.wall_list,
159 GRAVITY)
160
161 def on_draw(self):
162 """ Render the screen. """
163
164 # Clear the screen to the background color
165 arcade.start_render()
166
167 # Draw our sprites
168 self.wall_list.draw()
169 self.background_list.draw()
170 self.wall_list.draw()
171 self.coin_list.draw()
172 self.dont_touch_list.draw()
173 self.player_list.draw()
174 self.foreground_list.draw()
175
176 # Draw our score on the screen, scrolling it with the viewport
177 score_text = f"Score: {self.score}"
178 arcade.draw_text(score_text, 10 + self.view_left, 10 + self.view_bottom,
179 arcade.csscolor.BLACK, 18)
180
181 def on_key_press(self, key, modifiers):
182 """Called whenever a key is pressed. """
183
184 if key == arcade.key.UP or key == arcade.key.W:
185 if self.physics_engine.can_jump():
186 self.player_sprite.change_y = PLAYER_JUMP_SPEED
187 arcade.play_sound(self.jump_sound)
188 elif 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 when the user releases a key. """
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 def update(self, delta_time):
202 """ Movement and game logic """
203
204 # Move the player with the physics engine
205 self.physics_engine.update()
206
207 # See if we hit any coins
208 coin_hit_list = arcade.check_for_collision_with_list(self.player_sprite,
209 self.coin_list)
210
211 # Loop through each coin we hit (if any) and remove it
212 for coin in coin_hit_list:
213 # Remove the coin
214 coin.remove_from_sprite_lists()
215 # Play a sound
216 arcade.play_sound(self.collect_coin_sound)
217 # Add one to the score
218 self.score += 1
219
220 # Track if we need to change the viewport
221 changed_viewport = False
222
223 # Did the player fall off the map?
224 if self.player_sprite.center_y < -100:
225 self.player_sprite.center_x = PLAYER_START_X
226 self.player_sprite.center_y = PLAYER_START_Y
227
228 # Set the camera to the start
229 self.view_left = 0
230 self.view_bottom = 0
231 changed_viewport = True
232 arcade.play_sound(self.game_over)
233
234 # Did the player touch something they should not?
235 if arcade.check_for_collision_with_list(self.player_sprite,
236 self.dont_touch_list):
237 self.player_sprite.change_x = 0
238 self.player_sprite.change_y = 0
239 self.player_sprite.center_x = PLAYER_START_X
240 self.player_sprite.center_y = PLAYER_START_Y
241
242 # Set the camera to the start
243 self.view_left = 0
244 self.view_bottom = 0
245 changed_viewport = True
246 arcade.play_sound(self.game_over)
247
248 # See if the user got to the end of the level
249 if self.player_sprite.center_x >= self.end_of_map:
250 # Advance to the next level
251 self.level += 1
252
253 # Load the next level
254 self.setup(self.level)
255
256 # Set the camera to the start
257 self.view_left = 0
258 self.view_bottom = 0
259 changed_viewport = True
260
261 # --- Manage Scrolling ---
262
263 # Scroll left
264 left_boundary = self.view_left + LEFT_VIEWPORT_MARGIN
265 if self.player_sprite.left < left_boundary:
266 self.view_left -= left_boundary - self.player_sprite.left
267 changed_viewport = True
268
269 # Scroll right
270 right_boundary = self.view_left + SCREEN_WIDTH - RIGHT_VIEWPORT_MARGIN
271 if self.player_sprite.right > right_boundary:
272 self.view_left += self.player_sprite.right - right_boundary
273 changed_viewport = True
274
275 # Scroll up
276 top_boundary = self.view_bottom + SCREEN_HEIGHT - TOP_VIEWPORT_MARGIN
277 if self.player_sprite.top > top_boundary:
278 self.view_bottom += self.player_sprite.top - top_boundary
279 changed_viewport = True
280
281 # Scroll down
282 bottom_boundary = self.view_bottom + BOTTOM_VIEWPORT_MARGIN
283 if self.player_sprite.bottom < bottom_boundary:
284 self.view_bottom -= bottom_boundary - self.player_sprite.bottom
285 changed_viewport = True
286
287 if changed_viewport:
288 # Only scroll to integers. Otherwise we end up with pixels that
289 # don't line up on the screen
290 self.view_bottom = int(self.view_bottom)
291 self.view_left = int(self.view_left)
292
293 # Do the scrolling
294 arcade.set_viewport(self.view_left,
295 SCREEN_WIDTH + self.view_left,
296 self.view_bottom,
297 SCREEN_HEIGHT + self.view_bottom)
298
299
300def main():
301 """ Main method """
302 window = MyGame()
303 window.setup(window.level)
304 arcade.run()
305
306
307if __name__ == "__main__":
308 main()
Note
What else might you want to do?
Bullets (or something you can shoot)