TMX Map With Ladders and More

Screenshot of using a larger map created by the Tiled Map Editor

Supports:

  • Ladders

  • Coins with attributes that contain points

  • Moving Platforms defined in tiled object layer

platform_tutorial/10_ladders_and_more.py
  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()