Step 12 - Add Character Animations, and Better Keyboard Control#
Add character animations!
Animate Characters#
1"""
2Platformer Game
3
4python -m arcade.examples.platform_tutorial.12_animate_character
5"""
6import arcade
7
8# Constants
9SCREEN_WIDTH = 1000
10SCREEN_HEIGHT = 650
11SCREEN_TITLE = "Platformer"
12
13# Constants used to scale our sprites from their original size
14TILE_SCALING = 0.5
15CHARACTER_SCALING = TILE_SCALING * 2
16COIN_SCALING = TILE_SCALING
17SPRITE_PIXEL_SIZE = 128
18GRID_PIXEL_SIZE = SPRITE_PIXEL_SIZE * TILE_SCALING
19
20# Movement speed of player, in pixels per frame
21PLAYER_MOVEMENT_SPEED = 7
22GRAVITY = 1.5
23PLAYER_JUMP_SPEED = 30
24
25PLAYER_START_X = SPRITE_PIXEL_SIZE * TILE_SCALING * 2
26PLAYER_START_Y = SPRITE_PIXEL_SIZE * TILE_SCALING * 1
27
28# Constants used to track if the player is facing left or right
29RIGHT_FACING = 0
30LEFT_FACING = 1
31
32LAYER_NAME_MOVING_PLATFORMS = "Moving Platforms"
33LAYER_NAME_PLATFORMS = "Platforms"
34LAYER_NAME_COINS = "Coins"
35LAYER_NAME_BACKGROUND = "Background"
36LAYER_NAME_LADDERS = "Ladders"
37LAYER_NAME_PLAYER = "Player"
38
39
40def load_texture_pair(filename):
41 """
42 Load a texture pair, with the second being a mirror image.
43 """
44 return [
45 arcade.load_texture(filename),
46 arcade.load_texture(filename, flipped_horizontally=True),
47 ]
48
49
50class PlayerCharacter(arcade.Sprite):
51 """Player Sprite"""
52
53 def __init__(self):
54 # Set up parent class
55 super().__init__()
56
57 # Default to face-right
58 self.character_face_direction = RIGHT_FACING
59
60 # Used for flipping between image sequences
61 self.cur_texture = 0
62 self.scale = CHARACTER_SCALING
63
64 # Track our state
65 self.jumping = False
66 self.climbing = False
67 self.is_on_ladder = False
68
69 # --- Load Textures ---
70
71 # Images from Kenney.nl's Asset Pack 3
72 main_path = ":resources:images/animated_characters/male_person/malePerson"
73
74 # Load textures for idle standing
75 self.idle_texture_pair = load_texture_pair(f"{main_path}_idle.png")
76 self.jump_texture_pair = load_texture_pair(f"{main_path}_jump.png")
77 self.fall_texture_pair = load_texture_pair(f"{main_path}_fall.png")
78
79 # Load textures for walking
80 self.walk_textures = []
81 for i in range(8):
82 texture = load_texture_pair(f"{main_path}_walk{i}.png")
83 self.walk_textures.append(texture)
84
85 # Load textures for climbing
86 self.climbing_textures = []
87 texture = arcade.load_texture(f"{main_path}_climb0.png")
88 self.climbing_textures.append(texture)
89 texture = arcade.load_texture(f"{main_path}_climb1.png")
90 self.climbing_textures.append(texture)
91
92 # Set the initial texture
93 self.texture = self.idle_texture_pair[0]
94
95 # Hit box will be set based on the first image used. If you want to specify
96 # a different hit box, you can do it like the code below. Doing this when
97 # changing the texture for example would make the hitbox update whenever the
98 # texture is changed. This can be expensive so if the textures are very similar
99 # it may not be worth doing.
100 #
101 # self.hit_box = arcade.hitbox.RotatableHitBox(
102 # self.texture.hit_box_points,
103 # position=self.position,
104 # scale=self.scale_xy,
105 # angle=self.angle,
106 # )
107
108 def update_animation(self, delta_time: float = 1 / 60):
109 # Figure out if we need to flip face left or right
110 if self.change_x < 0 and self.character_face_direction == RIGHT_FACING:
111 self.character_face_direction = LEFT_FACING
112 elif self.change_x > 0 and self.character_face_direction == LEFT_FACING:
113 self.character_face_direction = RIGHT_FACING
114
115 # Climbing animation
116 if self.is_on_ladder:
117 self.climbing = True
118 if not self.is_on_ladder and self.climbing:
119 self.climbing = False
120 if self.climbing and abs(self.change_y) > 1:
121 self.cur_texture += 1
122 if self.cur_texture > 7:
123 self.cur_texture = 0
124 if self.climbing:
125 self.texture = self.climbing_textures[self.cur_texture // 4]
126 return
127
128 # Jumping animation
129 if self.change_y > 0 and not self.is_on_ladder:
130 self.texture = self.jump_texture_pair[self.character_face_direction]
131 return
132 elif self.change_y < 0 and not self.is_on_ladder:
133 self.texture = self.fall_texture_pair[self.character_face_direction]
134 return
135
136 # Idle animation
137 if self.change_x == 0:
138 self.texture = self.idle_texture_pair[self.character_face_direction]
139 return
140
141 # Walking animation
142 self.cur_texture += 1
143 if self.cur_texture > 7:
144 self.cur_texture = 0
145 self.texture = self.walk_textures[self.cur_texture][
146 self.character_face_direction
147 ]
148
149
150class MyGame(arcade.Window):
151 """
152 Main application class.
153 """
154
155 def __init__(self):
156 """
157 Initializer for the game
158 """
159 # Call the parent class and set up the window
160 super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
161
162 # Track the current state of what key is pressed
163 self.left_pressed = False
164 self.right_pressed = False
165 self.up_pressed = False
166 self.down_pressed = False
167 self.jump_needs_reset = False
168
169 # Our TileMap Object
170 self.tile_map = None
171
172 # Our Scene Object
173 self.scene = None
174
175 # Separate variable that holds the player sprite
176 self.player_sprite = None
177
178 # Our 'physics' engine
179 self.physics_engine = None
180
181 # A Camera that can be used for scrolling the screen
182 self.camera = None
183
184 # A Camera that can be used to draw GUI elements
185 self.gui_camera = None
186
187 self.end_of_map = 0
188
189 # Keep track of the score
190 self.score = 0
191
192 # Load sounds
193 self.collect_coin_sound = arcade.load_sound(":resources:sounds/coin1.wav")
194 self.jump_sound = arcade.load_sound(":resources:sounds/jump1.wav")
195 self.game_over = arcade.load_sound(":resources:sounds/gameover1.wav")
196
197 def setup(self):
198 """Set up the game here. Call this function to restart the game."""
199
200 # Set up the Cameras
201 viewport = (0, 0, self.width, self.height)
202 self.camera = arcade.SimpleCamera(viewport=viewport)
203 self.gui_camera = arcade.SimpleCamera(viewport=viewport)
204
205 # Map name
206 map_name = ":resources:tiled_maps/map_with_ladders.json"
207
208 # Layer Specific Options for the Tilemap
209 layer_options = {
210 LAYER_NAME_PLATFORMS: {
211 "use_spatial_hash": True,
212 },
213 LAYER_NAME_MOVING_PLATFORMS: {
214 "use_spatial_hash": False,
215 },
216 LAYER_NAME_LADDERS: {
217 "use_spatial_hash": True,
218 },
219 LAYER_NAME_COINS: {
220 "use_spatial_hash": True,
221 },
222 }
223
224 # Load in TileMap
225 self.tile_map = arcade.load_tilemap(map_name, TILE_SCALING, layer_options)
226
227 # Initiate New Scene with our TileMap, this will automatically add all layers
228 # from the map as SpriteLists in the scene in the proper order.
229 self.scene = arcade.Scene.from_tilemap(self.tile_map)
230
231 # Keep track of the score
232 self.score = 0
233
234 # Set up the player, specifically placing it at these coordinates.
235 self.player_sprite = PlayerCharacter()
236 self.player_sprite.center_x = PLAYER_START_X
237 self.player_sprite.center_y = PLAYER_START_Y
238 self.scene.add_sprite(LAYER_NAME_PLAYER, self.player_sprite)
239
240 # Calculate the right edge of the my_map in pixels
241 self.end_of_map = self.tile_map.width * GRID_PIXEL_SIZE
242
243 # --- Other stuff
244 # Set the background color
245 if self.tile_map.background_color:
246 self.background_color = self.tile_map.background_color
247
248 # Create the 'physics engine'
249 self.physics_engine = arcade.PhysicsEnginePlatformer(
250 self.player_sprite,
251 platforms=self.scene[LAYER_NAME_MOVING_PLATFORMS],
252 gravity_constant=GRAVITY,
253 ladders=self.scene[LAYER_NAME_LADDERS],
254 walls=self.scene[LAYER_NAME_PLATFORMS],
255 )
256
257 def on_draw(self):
258 """Render the screen."""
259
260 # Clear the screen to the background color
261 self.clear()
262
263 # Activate the game camera
264 self.camera.use()
265
266 # Draw our Scene
267 self.scene.draw()
268
269 # Activate the GUI camera before drawing GUI elements
270 self.gui_camera.use()
271
272 # Draw our score on the screen, scrolling it with the viewport
273 score_text = f"Score: {self.score}"
274 arcade.draw_text(
275 score_text,
276 10,
277 10,
278 arcade.csscolor.BLACK,
279 18,
280 )
281
282 # Draw hit boxes.
283 # for wall in self.wall_list:
284 # wall.draw_hit_box(arcade.color.BLACK, 3)
285 #
286 # self.player_sprite.draw_hit_box(arcade.color.RED, 3)
287
288 def process_keychange(self):
289 """
290 Called when we change a key up/down or we move on/off a ladder.
291 """
292 # Process up/down
293 if self.up_pressed and not self.down_pressed:
294 if self.physics_engine.is_on_ladder():
295 self.player_sprite.change_y = PLAYER_MOVEMENT_SPEED
296 elif (
297 self.physics_engine.can_jump(y_distance=10)
298 and not self.jump_needs_reset
299 ):
300 self.player_sprite.change_y = PLAYER_JUMP_SPEED
301 self.jump_needs_reset = True
302 arcade.play_sound(self.jump_sound)
303 elif self.down_pressed and not self.up_pressed:
304 if self.physics_engine.is_on_ladder():
305 self.player_sprite.change_y = -PLAYER_MOVEMENT_SPEED
306
307 # Process up/down when on a ladder and no movement
308 if self.physics_engine.is_on_ladder():
309 if not self.up_pressed and not self.down_pressed:
310 self.player_sprite.change_y = 0
311 elif self.up_pressed and self.down_pressed:
312 self.player_sprite.change_y = 0
313
314 # Process left/right
315 if self.right_pressed and not self.left_pressed:
316 self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
317 elif self.left_pressed and not self.right_pressed:
318 self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
319 else:
320 self.player_sprite.change_x = 0
321
322 def on_key_press(self, key, modifiers):
323 """Called whenever a key is pressed."""
324
325 if key == arcade.key.UP or key == arcade.key.W:
326 self.up_pressed = True
327 elif key == arcade.key.DOWN or key == arcade.key.S:
328 self.down_pressed = True
329 elif key == arcade.key.LEFT or key == arcade.key.A:
330 self.left_pressed = True
331 elif key == arcade.key.RIGHT or key == arcade.key.D:
332 self.right_pressed = True
333
334 self.process_keychange()
335
336 def on_key_release(self, key, modifiers):
337 """Called when the user releases a key."""
338
339 if key == arcade.key.UP or key == arcade.key.W:
340 self.up_pressed = False
341 self.jump_needs_reset = False
342 elif key == arcade.key.DOWN or key == arcade.key.S:
343 self.down_pressed = False
344 elif key == arcade.key.LEFT or key == arcade.key.A:
345 self.left_pressed = False
346 elif key == arcade.key.RIGHT or key == arcade.key.D:
347 self.right_pressed = False
348
349 self.process_keychange()
350
351 def center_camera_to_player(self):
352 screen_center_x = self.player_sprite.center_x - (self.camera.viewport_width / 2)
353 screen_center_y = self.player_sprite.center_y - (
354 self.camera.viewport_height / 2
355 )
356 if screen_center_x < 0:
357 screen_center_x = 0
358 if screen_center_y < 0:
359 screen_center_y = 0
360 player_centered = screen_center_x, screen_center_y
361
362 self.camera.move_to(player_centered, 0.2)
363
364 def on_update(self, delta_time):
365 """Movement and game logic"""
366
367 # Move the player with the physics engine
368 self.physics_engine.update()
369
370 # Update animations
371 if self.physics_engine.can_jump():
372 self.player_sprite.can_jump = False
373 else:
374 self.player_sprite.can_jump = True
375
376 if self.physics_engine.is_on_ladder() and not self.physics_engine.can_jump():
377 self.player_sprite.is_on_ladder = True
378 self.process_keychange()
379 else:
380 self.player_sprite.is_on_ladder = False
381 self.process_keychange()
382
383 # Update Animations
384 self.scene.update_animation(
385 delta_time, [LAYER_NAME_COINS, LAYER_NAME_BACKGROUND, LAYER_NAME_PLAYER]
386 )
387
388 # Update walls, used with moving platforms
389 self.scene.update([LAYER_NAME_MOVING_PLATFORMS])
390
391 # See if we hit any coins
392 coin_hit_list = arcade.check_for_collision_with_list(
393 self.player_sprite, self.scene[LAYER_NAME_COINS]
394 )
395
396 # Loop through each coin we hit (if any) and remove it
397 for coin in coin_hit_list:
398 # Figure out how many points this coin is worth
399 if "Points" not in coin.properties:
400 print("Warning, collected a coin without a Points property.")
401 else:
402 points = int(coin.properties["Points"])
403 self.score += points
404
405 # Remove the coin
406 coin.remove_from_sprite_lists()
407 arcade.play_sound(self.collect_coin_sound)
408
409 # Position the camera
410 self.center_camera_to_player()
411
412
413def main():
414 """Main function"""
415 window = MyGame()
416 window.setup()
417 arcade.run()
418
419
420if __name__ == "__main__":
421 main()
Source Code#
Animate the player character#
1"""
2Platformer Game
3
4python -m arcade.examples.platform_tutorial.12_animate_character
5"""
6import arcade
7
8# Constants
9SCREEN_WIDTH = 1000
10SCREEN_HEIGHT = 650
11SCREEN_TITLE = "Platformer"
12
13# Constants used to scale our sprites from their original size
14TILE_SCALING = 0.5
15CHARACTER_SCALING = TILE_SCALING * 2
16COIN_SCALING = TILE_SCALING
17SPRITE_PIXEL_SIZE = 128
18GRID_PIXEL_SIZE = SPRITE_PIXEL_SIZE * TILE_SCALING
19
20# Movement speed of player, in pixels per frame
21PLAYER_MOVEMENT_SPEED = 7
22GRAVITY = 1.5
23PLAYER_JUMP_SPEED = 30
24
25PLAYER_START_X = SPRITE_PIXEL_SIZE * TILE_SCALING * 2
26PLAYER_START_Y = SPRITE_PIXEL_SIZE * TILE_SCALING * 1
27
28# Constants used to track if the player is facing left or right
29RIGHT_FACING = 0
30LEFT_FACING = 1
31
32LAYER_NAME_MOVING_PLATFORMS = "Moving Platforms"
33LAYER_NAME_PLATFORMS = "Platforms"
34LAYER_NAME_COINS = "Coins"
35LAYER_NAME_BACKGROUND = "Background"
36LAYER_NAME_LADDERS = "Ladders"
37LAYER_NAME_PLAYER = "Player"
38
39
40def load_texture_pair(filename):
41 """
42 Load a texture pair, with the second being a mirror image.
43 """
44 return [
45 arcade.load_texture(filename),
46 arcade.load_texture(filename, flipped_horizontally=True),
47 ]
48
49
50class PlayerCharacter(arcade.Sprite):
51 """Player Sprite"""
52
53 def __init__(self):
54 # Set up parent class
55 super().__init__()
56
57 # Default to face-right
58 self.character_face_direction = RIGHT_FACING
59
60 # Used for flipping between image sequences
61 self.cur_texture = 0
62 self.scale = CHARACTER_SCALING
63
64 # Track our state
65 self.jumping = False
66 self.climbing = False
67 self.is_on_ladder = False
68
69 # --- Load Textures ---
70
71 # Images from Kenney.nl's Asset Pack 3
72 main_path = ":resources:images/animated_characters/male_person/malePerson"
73
74 # Load textures for idle standing
75 self.idle_texture_pair = load_texture_pair(f"{main_path}_idle.png")
76 self.jump_texture_pair = load_texture_pair(f"{main_path}_jump.png")
77 self.fall_texture_pair = load_texture_pair(f"{main_path}_fall.png")
78
79 # Load textures for walking
80 self.walk_textures = []
81 for i in range(8):
82 texture = load_texture_pair(f"{main_path}_walk{i}.png")
83 self.walk_textures.append(texture)
84
85 # Load textures for climbing
86 self.climbing_textures = []
87 texture = arcade.load_texture(f"{main_path}_climb0.png")
88 self.climbing_textures.append(texture)
89 texture = arcade.load_texture(f"{main_path}_climb1.png")
90 self.climbing_textures.append(texture)
91
92 # Set the initial texture
93 self.texture = self.idle_texture_pair[0]
94
95 # Hit box will be set based on the first image used. If you want to specify
96 # a different hit box, you can do it like the code below. Doing this when
97 # changing the texture for example would make the hitbox update whenever the
98 # texture is changed. This can be expensive so if the textures are very similar
99 # it may not be worth doing.
100 #
101 # self.hit_box = arcade.hitbox.RotatableHitBox(
102 # self.texture.hit_box_points,
103 # position=self.position,
104 # scale=self.scale_xy,
105 # angle=self.angle,
106 # )
107
108 def update_animation(self, delta_time: float = 1 / 60):
109 # Figure out if we need to flip face left or right
110 if self.change_x < 0 and self.character_face_direction == RIGHT_FACING:
111 self.character_face_direction = LEFT_FACING
112 elif self.change_x > 0 and self.character_face_direction == LEFT_FACING:
113 self.character_face_direction = RIGHT_FACING
114
115 # Climbing animation
116 if self.is_on_ladder:
117 self.climbing = True
118 if not self.is_on_ladder and self.climbing:
119 self.climbing = False
120 if self.climbing and abs(self.change_y) > 1:
121 self.cur_texture += 1
122 if self.cur_texture > 7:
123 self.cur_texture = 0
124 if self.climbing:
125 self.texture = self.climbing_textures[self.cur_texture // 4]
126 return
127
128 # Jumping animation
129 if self.change_y > 0 and not self.is_on_ladder:
130 self.texture = self.jump_texture_pair[self.character_face_direction]
131 return
132 elif self.change_y < 0 and not self.is_on_ladder:
133 self.texture = self.fall_texture_pair[self.character_face_direction]
134 return
135
136 # Idle animation
137 if self.change_x == 0:
138 self.texture = self.idle_texture_pair[self.character_face_direction]
139 return
140
141 # Walking animation
142 self.cur_texture += 1
143 if self.cur_texture > 7:
144 self.cur_texture = 0
145 self.texture = self.walk_textures[self.cur_texture][
146 self.character_face_direction
147 ]
148
149
150class MyGame(arcade.Window):
151 """
152 Main application class.
153 """
154
155 def __init__(self):
156 """
157 Initializer for the game
158 """
159 # Call the parent class and set up the window
160 super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
161
162 # Track the current state of what key is pressed
163 self.left_pressed = False
164 self.right_pressed = False
165 self.up_pressed = False
166 self.down_pressed = False
167 self.jump_needs_reset = False
168
169 # Our TileMap Object
170 self.tile_map = None
171
172 # Our Scene Object
173 self.scene = None
174
175 # Separate variable that holds the player sprite
176 self.player_sprite = None
177
178 # Our 'physics' engine
179 self.physics_engine = None
180
181 # A Camera that can be used for scrolling the screen
182 self.camera = None
183
184 # A Camera that can be used to draw GUI elements
185 self.gui_camera = None
186
187 self.end_of_map = 0
188
189 # Keep track of the score
190 self.score = 0
191
192 # Load sounds
193 self.collect_coin_sound = arcade.load_sound(":resources:sounds/coin1.wav")
194 self.jump_sound = arcade.load_sound(":resources:sounds/jump1.wav")
195 self.game_over = arcade.load_sound(":resources:sounds/gameover1.wav")
196
197 def setup(self):
198 """Set up the game here. Call this function to restart the game."""
199
200 # Set up the Cameras
201 viewport = (0, 0, self.width, self.height)
202 self.camera = arcade.SimpleCamera(viewport=viewport)
203 self.gui_camera = arcade.SimpleCamera(viewport=viewport)
204
205 # Map name
206 map_name = ":resources:tiled_maps/map_with_ladders.json"
207
208 # Layer Specific Options for the Tilemap
209 layer_options = {
210 LAYER_NAME_PLATFORMS: {
211 "use_spatial_hash": True,
212 },
213 LAYER_NAME_MOVING_PLATFORMS: {
214 "use_spatial_hash": False,
215 },
216 LAYER_NAME_LADDERS: {
217 "use_spatial_hash": True,
218 },
219 LAYER_NAME_COINS: {
220 "use_spatial_hash": True,
221 },
222 }
223
224 # Load in TileMap
225 self.tile_map = arcade.load_tilemap(map_name, TILE_SCALING, layer_options)
226
227 # Initiate New Scene with our TileMap, this will automatically add all layers
228 # from the map as SpriteLists in the scene in the proper order.
229 self.scene = arcade.Scene.from_tilemap(self.tile_map)
230
231 # Keep track of the score
232 self.score = 0
233
234 # Set up the player, specifically placing it at these coordinates.
235 self.player_sprite = PlayerCharacter()
236 self.player_sprite.center_x = PLAYER_START_X
237 self.player_sprite.center_y = PLAYER_START_Y
238 self.scene.add_sprite(LAYER_NAME_PLAYER, self.player_sprite)
239
240 # Calculate the right edge of the my_map in pixels
241 self.end_of_map = self.tile_map.width * GRID_PIXEL_SIZE
242
243 # --- Other stuff
244 # Set the background color
245 if self.tile_map.background_color:
246 self.background_color = self.tile_map.background_color
247
248 # Create the 'physics engine'
249 self.physics_engine = arcade.PhysicsEnginePlatformer(
250 self.player_sprite,
251 platforms=self.scene[LAYER_NAME_MOVING_PLATFORMS],
252 gravity_constant=GRAVITY,
253 ladders=self.scene[LAYER_NAME_LADDERS],
254 walls=self.scene[LAYER_NAME_PLATFORMS],
255 )
256
257 def on_draw(self):
258 """Render the screen."""
259
260 # Clear the screen to the background color
261 self.clear()
262
263 # Activate the game camera
264 self.camera.use()
265
266 # Draw our Scene
267 self.scene.draw()
268
269 # Activate the GUI camera before drawing GUI elements
270 self.gui_camera.use()
271
272 # Draw our score on the screen, scrolling it with the viewport
273 score_text = f"Score: {self.score}"
274 arcade.draw_text(
275 score_text,
276 10,
277 10,
278 arcade.csscolor.BLACK,
279 18,
280 )
281
282 # Draw hit boxes.
283 # for wall in self.wall_list:
284 # wall.draw_hit_box(arcade.color.BLACK, 3)
285 #
286 # self.player_sprite.draw_hit_box(arcade.color.RED, 3)
287
288 def process_keychange(self):
289 """
290 Called when we change a key up/down or we move on/off a ladder.
291 """
292 # Process up/down
293 if self.up_pressed and not self.down_pressed:
294 if self.physics_engine.is_on_ladder():
295 self.player_sprite.change_y = PLAYER_MOVEMENT_SPEED
296 elif (
297 self.physics_engine.can_jump(y_distance=10)
298 and not self.jump_needs_reset
299 ):
300 self.player_sprite.change_y = PLAYER_JUMP_SPEED
301 self.jump_needs_reset = True
302 arcade.play_sound(self.jump_sound)
303 elif self.down_pressed and not self.up_pressed:
304 if self.physics_engine.is_on_ladder():
305 self.player_sprite.change_y = -PLAYER_MOVEMENT_SPEED
306
307 # Process up/down when on a ladder and no movement
308 if self.physics_engine.is_on_ladder():
309 if not self.up_pressed and not self.down_pressed:
310 self.player_sprite.change_y = 0
311 elif self.up_pressed and self.down_pressed:
312 self.player_sprite.change_y = 0
313
314 # Process left/right
315 if self.right_pressed and not self.left_pressed:
316 self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
317 elif self.left_pressed and not self.right_pressed:
318 self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
319 else:
320 self.player_sprite.change_x = 0
321
322 def on_key_press(self, key, modifiers):
323 """Called whenever a key is pressed."""
324
325 if key == arcade.key.UP or key == arcade.key.W:
326 self.up_pressed = True
327 elif key == arcade.key.DOWN or key == arcade.key.S:
328 self.down_pressed = True
329 elif key == arcade.key.LEFT or key == arcade.key.A:
330 self.left_pressed = True
331 elif key == arcade.key.RIGHT or key == arcade.key.D:
332 self.right_pressed = True
333
334 self.process_keychange()
335
336 def on_key_release(self, key, modifiers):
337 """Called when the user releases a key."""
338
339 if key == arcade.key.UP or key == arcade.key.W:
340 self.up_pressed = False
341 self.jump_needs_reset = False
342 elif key == arcade.key.DOWN or key == arcade.key.S:
343 self.down_pressed = False
344 elif key == arcade.key.LEFT or key == arcade.key.A:
345 self.left_pressed = False
346 elif key == arcade.key.RIGHT or key == arcade.key.D:
347 self.right_pressed = False
348
349 self.process_keychange()
350
351 def center_camera_to_player(self):
352 screen_center_x = self.player_sprite.center_x - (self.camera.viewport_width / 2)
353 screen_center_y = self.player_sprite.center_y - (
354 self.camera.viewport_height / 2
355 )
356 if screen_center_x < 0:
357 screen_center_x = 0
358 if screen_center_y < 0:
359 screen_center_y = 0
360 player_centered = screen_center_x, screen_center_y
361
362 self.camera.move_to(player_centered, 0.2)
363
364 def on_update(self, delta_time):
365 """Movement and game logic"""
366
367 # Move the player with the physics engine
368 self.physics_engine.update()
369
370 # Update animations
371 if self.physics_engine.can_jump():
372 self.player_sprite.can_jump = False
373 else:
374 self.player_sprite.can_jump = True
375
376 if self.physics_engine.is_on_ladder() and not self.physics_engine.can_jump():
377 self.player_sprite.is_on_ladder = True
378 self.process_keychange()
379 else:
380 self.player_sprite.is_on_ladder = False
381 self.process_keychange()
382
383 # Update Animations
384 self.scene.update_animation(
385 delta_time, [LAYER_NAME_COINS, LAYER_NAME_BACKGROUND, LAYER_NAME_PLAYER]
386 )
387
388 # Update walls, used with moving platforms
389 self.scene.update([LAYER_NAME_MOVING_PLATFORMS])
390
391 # See if we hit any coins
392 coin_hit_list = arcade.check_for_collision_with_list(
393 self.player_sprite, self.scene[LAYER_NAME_COINS]
394 )
395
396 # Loop through each coin we hit (if any) and remove it
397 for coin in coin_hit_list:
398 # Figure out how many points this coin is worth
399 if "Points" not in coin.properties:
400 print("Warning, collected a coin without a Points property.")
401 else:
402 points = int(coin.properties["Points"])
403 self.score += points
404
405 # Remove the coin
406 coin.remove_from_sprite_lists()
407 arcade.play_sound(self.collect_coin_sound)
408
409 # Position the camera
410 self.center_camera_to_player()
411
412
413def main():
414 """Main function"""
415 window = MyGame()
416 window.setup()
417 arcade.run()
418
419
420if __name__ == "__main__":
421 main()