1"""
2Load a Tiled map file with Levels
3
4Artwork from: https://kenney.nl
5Tiled available from: https://www.mapeditor.org/
6
7If Python and Arcade are installed, this example can be run from the command line with:
8python -m arcade.examples.sprite_tiled_map_with_levels
9"""
10import time
11
12import arcade
13
14TILE_SPRITE_SCALING = 0.5
15PLAYER_SCALING = 0.6
16
17SCREEN_WIDTH = 800
18SCREEN_HEIGHT = 600
19SCREEN_TITLE = "Sprite Tiled Map with Levels Example"
20SPRITE_PIXEL_SIZE = 128
21GRID_PIXEL_SIZE = SPRITE_PIXEL_SIZE * TILE_SPRITE_SCALING
22
23# How many pixels to keep as a minimum margin between the character
24# and the edge of the screen.
25VIEWPORT_MARGIN_TOP = 60
26VIEWPORT_MARGIN_BOTTOM = 60
27VIEWPORT_MARGIN_RIGHT = 270
28VIEWPORT_MARGIN_LEFT = 270
29
30# Physics
31MOVEMENT_SPEED = 5
32JUMP_SPEED = 23
33GRAVITY = 1.1
34
35
36class MyGame(arcade.Window):
37 """Main application class."""
38
39 def __init__(self):
40 """
41 Initializer
42 """
43 super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
44
45 # Tilemap Object
46 self.tile_map = None
47
48 # Sprite lists
49 self.player_list = None
50
51 # Set up the player
52 self.score = 0
53 self.player_sprite = None
54
55 self.physics_engine = None
56 self.cam = None
57 self.end_of_map = 0
58 self.game_over = False
59 self.last_time = None
60 self.frame_count = 0
61 self.fps_message = None
62
63 self.level = 1
64 self.max_level = 2
65
66 def setup(self):
67 """Set up the game and initialize the variables."""
68
69 # Sprite lists
70 self.player_list = arcade.SpriteList()
71
72 # Set up the player
73 self.player_sprite = arcade.Sprite(
74 ":resources:images/animated_characters/female_person/femalePerson_idle.png",
75 scale=PLAYER_SCALING,
76 )
77
78 # Starting position of the player
79 self.player_sprite.center_x = 128
80 self.player_sprite.center_y = 64
81 self.player_list.append(self.player_sprite)
82
83 self.load_level(self.level)
84
85 self.game_over = False
86
87 def load_level(self, level):
88 # layer_options = {"Platforms": {"use_spatial_hash": True}}
89
90 # Read in the tiled map
91 self.tile_map = arcade.load_tilemap(
92 f":resources:tiled_maps/level_{level}.json", scaling=TILE_SPRITE_SCALING
93 )
94
95 # --- Walls ---
96
97 # Calculate the right edge of the my_map in pixels
98 self.end_of_map = self.tile_map.width * GRID_PIXEL_SIZE
99
100 self.physics_engine = arcade.PhysicsEnginePlatformer(
101 self.player_sprite,
102 self.tile_map.sprite_lists["Platforms"],
103 gravity_constant=GRAVITY,
104 )
105
106 # --- Other stuff
107 # Set the background color
108 if self.tile_map.background_color:
109 self.background_color = self.tile_map.background_color
110
111 # Reset cam
112 self.cam = arcade.camera.Camera2D()
113
114 def on_draw(self):
115 """
116 Render the screen.
117 """
118
119 self.frame_count += 1
120
121 # This command has to happen before we start drawing
122 self.clear()
123
124 # Draw all the sprites.
125 self.player_list.draw()
126 self.tile_map.sprite_lists["Platforms"].draw()
127
128 if self.last_time and self.frame_count % 60 == 0:
129 fps = 1.0 / (time.time() - self.last_time) * 60
130 self.fps_message = f"FPS: {fps:5.0f}"
131
132 if self.fps_message:
133 arcade.draw_text(
134 self.fps_message,
135 self.cam.left + 10,
136 self.cam.bottom + 40,
137 arcade.color.BLACK,
138 14,
139 )
140
141 if self.frame_count % 60 == 0:
142 self.last_time = time.time()
143
144 # Put the text on the screen.
145 # Adjust the text position based on the view port so that we don't
146 # scroll the text too.
147 distance = self.player_sprite.right
148 left, bottom = self.cam.bottom_left
149 output = f"Distance: {distance:.0f}"
150 arcade.draw_text(
151 output, left + 10, bottom + 20, arcade.color.BLACK, 14
152 )
153
154 if self.game_over:
155 arcade.draw_text(
156 "Game Over",
157 left + 200,
158 bottom + 200,
159 arcade.color.BLACK,
160 30,
161 )
162
163 def on_key_press(self, key, modifiers):
164 """
165 Called whenever the mouse moves.
166 """
167 if key == arcade.key.UP:
168 if self.physics_engine.can_jump():
169 self.player_sprite.change_y = JUMP_SPEED
170 elif key == arcade.key.LEFT:
171 self.player_sprite.change_x = -MOVEMENT_SPEED
172 elif key == arcade.key.RIGHT:
173 self.player_sprite.change_x = MOVEMENT_SPEED
174
175 def on_key_release(self, key, modifiers):
176 """
177 Called when the user presses a mouse button.
178 """
179 if key == arcade.key.LEFT or key == arcade.key.RIGHT:
180 self.player_sprite.change_x = 0
181
182 def on_update(self, delta_time):
183 """Movement and game logic"""
184
185 if self.player_sprite.right >= self.end_of_map:
186 if self.level < self.max_level:
187 self.level += 1
188 self.load_level(self.level)
189 self.player_sprite.center_x = 128
190 self.player_sprite.center_y = 64
191 self.player_sprite.change_x = 0
192 self.player_sprite.change_y = 0
193 else:
194 self.game_over = True
195
196 # Call update on all sprites (The sprites don't do much in this
197 # example though.)
198 if not self.game_over:
199 self.physics_engine.update()
200
201 # --- Manage Scrolling ---
202
203 # Keep track of if we changed the boundary. We don't want to
204 # update the camera if we don't need to.
205 changed = False
206
207 pos = self.cam.position
208
209 top_left = self.cam.top_left
210
211 # Scroll left
212 left_boundary = top_left[0] + VIEWPORT_MARGIN_LEFT
213 if self.player_sprite.left < left_boundary:
214 changed = True
215 pos = pos[0] + (self.player_sprite.left - left_boundary), pos[1]
216
217 # Scroll up
218 top_boundary = top_left[1] - VIEWPORT_MARGIN_TOP
219 if self.player_sprite.top > top_boundary:
220 changed = True
221 pos = pos[0], pos[1] + (self.player_sprite.top - top_boundary)
222
223 bottom_right = self.cam.bottom_right
224
225 # Scroll right
226 right_boundary = bottom_right[0] - VIEWPORT_MARGIN_RIGHT
227 if self.player_sprite.right > right_boundary:
228 changed = True
229 pos = pos[0] + (self.player_sprite.right - right_boundary), pos[1]
230
231 # Scroll down
232 bottom_boundary = bottom_right[1] + VIEWPORT_MARGIN_BOTTOM
233 if self.player_sprite.bottom < bottom_boundary:
234 pos = pos[0], pos[1] + (self.player_sprite.bottom - bottom_boundary)
235
236 # If we changed the boundary values, update the view port to match
237 if changed:
238 self.cam.position = pos
239 # Make sure our boundaries are integer values. While the view port does
240 # support floating point numbers, for this application we want every pixel
241 # in the view port to map directly onto a pixel on the screen. We don't want
242 # any rounding errors.
243 bottom_left = self.cam.bottom_left
244 self.cam.bottom_left = int(bottom_left[0]), int(bottom_left[1])
245
246 self.cam.use()
247
248
249def main():
250 window = MyGame()
251 window.setup()
252 arcade.run()
253
254
255if __name__ == "__main__":
256 main()