TMX Map With Ladders and More¶
Supports:
Ladders
Coins with attributes that contain points
Moving Platforms defined in tiled object layer
1"""
2Platformer Game
3"""
4import arcade
5import os
6
7# Constants
8SCREEN_WIDTH = 1000
9SCREEN_HEIGHT = 650
10SCREEN_TITLE = "Platformer"
11
12# Constants used to scale our sprites from their original size
13CHARACTER_SCALING = 1
14TILE_SCALING = 0.5
15COIN_SCALING = 0.5
16SPRITE_PIXEL_SIZE = 128
17GRID_PIXEL_SIZE = (SPRITE_PIXEL_SIZE * TILE_SCALING)
18
19# Movement speed of player, in pixels per frame
20PLAYER_MOVEMENT_SPEED = 7
21GRAVITY = 1.5
22PLAYER_JUMP_SPEED = 30
23
24# How many pixels to keep as a minimum margin between the character
25# and the edge of the screen.
26LEFT_VIEWPORT_MARGIN = 200
27RIGHT_VIEWPORT_MARGIN = 200
28BOTTOM_VIEWPORT_MARGIN = 150
29TOP_VIEWPORT_MARGIN = 100
30
31PLAYER_START_X = 64
32PLAYER_START_Y = 256
33
34
35class MyGame(arcade.Window):
36 """
37 Main application class.
38 """
39
40 def __init__(self):
41 """
42 Initializer for the game
43 """
44
45 # Call the parent class and set up the window
46 super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
47
48 # Set the path to start with this program
49 file_path = os.path.dirname(os.path.abspath(__file__))
50 os.chdir(file_path)
51
52 # These are 'lists' that keep track of our sprites. Each sprite should
53 # go into a list.
54 self.coin_list = None
55 self.wall_list = None
56 self.background_list = None
57 self.ladder_list = None
58 self.player_list = None
59
60 # Separate variable that holds the player sprite
61 self.player_sprite = None
62
63 # Our 'physics' engine
64 self.physics_engine = None
65
66 # Used to keep track of our scrolling
67 self.view_bottom = 0
68 self.view_left = 0
69
70 self.end_of_map = 0
71
72 # Keep track of the score
73 self.score = 0
74
75 # Load sounds
76 self.collect_coin_sound = arcade.load_sound(":resources:sounds/coin1.wav")
77 self.jump_sound = arcade.load_sound(":resources:sounds/jump1.wav")
78 self.game_over = arcade.load_sound(":resources:sounds/gameover1.wav")
79
80 def setup(self):
81 """ Set up the game here. Call this function to restart the game. """
82
83 # Used to keep track of our scrolling
84 self.view_bottom = 0
85 self.view_left = 0
86
87 # Keep track of the score
88 self.score = 0
89
90 # Create the Sprite lists
91 self.player_list = arcade.SpriteList()
92 self.background_list = arcade.SpriteList()
93 self.wall_list = arcade.SpriteList()
94 self.coin_list = arcade.SpriteList()
95
96 # Set up the player, specifically placing it at these coordinates.
97 image_source = ":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png"
98 self.player_sprite = arcade.Sprite(image_source, CHARACTER_SCALING)
99 self.player_sprite.center_x = PLAYER_START_X
100 self.player_sprite.center_y = PLAYER_START_Y
101 self.player_list.append(self.player_sprite)
102
103 # --- Load in a map from the tiled editor ---
104
105 # Name of the layer in the file that has our platforms/walls
106 platforms_layer_name = 'Platforms'
107 moving_platforms_layer_name = 'Moving Platforms'
108
109 # Name of the layer that has items for pick-up
110 coins_layer_name = 'Coins'
111
112 # Map name
113 map_name = f":resources:tmx_maps/map_with_ladders.tmx"
114
115 # Read in the tiled map
116 my_map = arcade.tilemap.read_tmx(map_name)
117
118 # Calculate the right edge of the my_map in pixels
119 self.end_of_map = my_map.map_size.width * GRID_PIXEL_SIZE
120
121 # -- Platforms
122 self.wall_list = arcade.tilemap.process_layer(my_map,
123 platforms_layer_name,
124 scaling=TILE_SCALING,
125 use_spatial_hash=True)
126
127 # -- Moving Platforms
128 moving_platforms_list = arcade.tilemap.process_layer(my_map, moving_platforms_layer_name, TILE_SCALING)
129 for sprite in moving_platforms_list:
130 self.wall_list.append(sprite)
131
132 # -- Background objects
133 self.background_list = arcade.tilemap.process_layer(my_map, "Background", TILE_SCALING)
134
135 # -- Background objects
136 self.ladder_list = arcade.tilemap.process_layer(my_map,
137 "Ladders",
138 scaling=TILE_SCALING,
139 use_spatial_hash=True)
140
141 # -- Coins
142 self.coin_list = arcade.tilemap.process_layer(my_map,
143 coins_layer_name,
144 scaling=TILE_SCALING,
145 use_spatial_hash=True)
146
147 # --- Other stuff
148 # Set the background color
149 if my_map.background_color:
150 arcade.set_background_color(my_map.background_color)
151
152 # Create the 'physics engine'
153 self.physics_engine = arcade.PhysicsEnginePlatformer(self.player_sprite,
154 self.wall_list,
155 gravity_constant=GRAVITY,
156 ladders=self.ladder_list)
157
158 def on_draw(self):
159 """ Render the screen. """
160
161 # Clear the screen to the background color
162 arcade.start_render()
163
164 # Draw our sprites
165 self.wall_list.draw()
166 self.background_list.draw()
167 self.ladder_list.draw()
168 self.coin_list.draw()
169 self.player_list.draw()
170
171 # Draw our score on the screen, scrolling it with the viewport
172 score_text = f"Score: {self.score}"
173 arcade.draw_text(score_text, 10 + self.view_left, 10 + self.view_bottom,
174 arcade.csscolor.BLACK, 18)
175
176 def on_key_press(self, key, modifiers):
177 """Called whenever a key is pressed. """
178
179 if key == arcade.key.UP or key == arcade.key.W:
180 if self.physics_engine.is_on_ladder():
181 self.player_sprite.change_y = PLAYER_MOVEMENT_SPEED
182 elif self.physics_engine.can_jump():
183 self.player_sprite.change_y = PLAYER_JUMP_SPEED
184 arcade.play_sound(self.jump_sound)
185 elif key == arcade.key.DOWN or key == arcade.key.S:
186 if self.physics_engine.is_on_ladder():
187 self.player_sprite.change_y = -PLAYER_MOVEMENT_SPEED
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.UP or key == arcade.key.W:
197 if self.physics_engine.is_on_ladder():
198 self.player_sprite.change_y = 0
199 elif key == arcade.key.DOWN or key == arcade.key.S:
200 if self.physics_engine.is_on_ladder():
201 self.player_sprite.change_y = 0
202 elif key == arcade.key.LEFT or key == arcade.key.A:
203 self.player_sprite.change_x = 0
204 elif key == arcade.key.RIGHT or key == arcade.key.D:
205 self.player_sprite.change_x = 0
206
207 def update(self, delta_time):
208 """ Movement and game logic """
209
210 # Move the player with the physics engine
211 self.physics_engine.update()
212
213 # Update animations
214 self.coin_list.update_animation(delta_time)
215 self.background_list.update_animation(delta_time)
216
217 # Update walls, used with moving platforms
218 self.wall_list.update()
219
220 # See if the wall hit a boundary and needs to reverse direction.
221 for wall in self.wall_list:
222
223 if wall.boundary_right and wall.right > wall.boundary_right and wall.change_x > 0:
224 wall.change_x *= -1
225 if wall.boundary_left and wall.left < wall.boundary_left and wall.change_x < 0:
226 wall.change_x *= -1
227 if wall.boundary_top and wall.top > wall.boundary_top and wall.change_y > 0:
228 wall.change_y *= -1
229 if wall.boundary_bottom and wall.bottom < wall.boundary_bottom and wall.change_y < 0:
230 wall.change_y *= -1
231
232 # See if we hit any coins
233 coin_hit_list = arcade.check_for_collision_with_list(self.player_sprite,
234 self.coin_list)
235
236 # Loop through each coin we hit (if any) and remove it
237 for coin in coin_hit_list:
238
239 # Figure out how many points this coin is worth
240 if 'Points' not in coin.properties:
241 print("Warning, collected a coing without a Points property.")
242 else:
243 points = int(coin.properties['Points'])
244 self.score += points
245
246 # Remove the coin
247 coin.remove_from_sprite_lists()
248 arcade.play_sound(self.collect_coin_sound)
249
250 # Track if we need to change the viewport
251 changed_viewport = False
252
253 # --- Manage Scrolling ---
254
255 # Scroll left
256 left_boundary = self.view_left + LEFT_VIEWPORT_MARGIN
257 if self.player_sprite.left < left_boundary:
258 self.view_left -= left_boundary - self.player_sprite.left
259 changed_viewport = True
260
261 # Scroll right
262 right_boundary = self.view_left + SCREEN_WIDTH - RIGHT_VIEWPORT_MARGIN
263 if self.player_sprite.right > right_boundary:
264 self.view_left += self.player_sprite.right - right_boundary
265 changed_viewport = True
266
267 # Scroll up
268 top_boundary = self.view_bottom + SCREEN_HEIGHT - TOP_VIEWPORT_MARGIN
269 if self.player_sprite.top > top_boundary:
270 self.view_bottom += self.player_sprite.top - top_boundary
271 changed_viewport = True
272
273 # Scroll down
274 bottom_boundary = self.view_bottom + BOTTOM_VIEWPORT_MARGIN
275 if self.player_sprite.bottom < bottom_boundary:
276 self.view_bottom -= bottom_boundary - self.player_sprite.bottom
277 changed_viewport = True
278
279 if changed_viewport:
280 # Only scroll to integers. Otherwise we end up with pixels that
281 # don't line up on the screen
282 self.view_bottom = int(self.view_bottom)
283 self.view_left = int(self.view_left)
284
285 # Do the scrolling
286 arcade.set_viewport(self.view_left,
287 SCREEN_WIDTH + self.view_left,
288 self.view_bottom,
289 SCREEN_HEIGHT + self.view_bottom)
290
291
292def main():
293 """ Main method """
294 window = MyGame()
295 window.setup()
296 arcade.run()
297
298
299if __name__ == "__main__":
300 main()