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()