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