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