Step 11 - Add Ladders, Properties, and a Moving Platform#

../../_images/11_ladders_and_more.png

This example shows using:

  • Ladders

  • Properties to define point value of coins and flags

  • Properties and an object layer to define a moving platform.

To create a moving platform using TMX editor, there are a few steps:

  1. Define an object layer instead of a tile layer.

  2. Select Insert Tile

  3. Select the tile you wish to insert.

  4. Place the tile.

  5. Add custom properties. You can add:

  • change_x

  • change_y

  • boundary_bottom

  • boundary_top

  • boundary_left

  • boundary_right

../../_images/moving_platform_setup.png
Ladders, Animated Tiles, and Moving Platforms#
  1"""
  2Platformer Game
  3
  4python -m arcade.examples.platform_tutorial.11_ladders_and_more
  5"""
  6from __future__ import annotations
  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
 16CHARACTER_SCALING = 1
 17TILE_SCALING = 0.5
 18COIN_SCALING = 0.5
 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 = 64
 28PLAYER_START_Y = 256
 29
 30# Layer Names from our TileMap
 31LAYER_NAME_MOVING_PLATFORMS = "Moving Platforms"
 32LAYER_NAME_PLATFORMS = "Platforms"
 33LAYER_NAME_COINS = "Coins"
 34LAYER_NAME_BACKGROUND = "Background"
 35LAYER_NAME_LADDERS = "Ladders"
 36
 37
 38class MyGame(arcade.Window):
 39    """
 40    Main application class.
 41    """
 42
 43    def __init__(self):
 44        """
 45        Initializer for the game
 46        """
 47        # Call the parent class and set up the window
 48        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
 49
 50        # Our TileMap Object
 51        self.tile_map = None
 52
 53        # Our Scene Object
 54        self.scene = None
 55
 56        # Separate variable that holds the player sprite
 57        self.player_sprite = None
 58
 59        # Our 'physics' engine
 60        self.physics_engine = None
 61
 62        # A Camera that can be used for scrolling the screen
 63        self.camera = None
 64
 65        # A Camera that can be used to draw GUI elements
 66        self.gui_camera = None
 67
 68        self.end_of_map = 0
 69
 70        # Keep track of the score
 71        self.score = 0
 72
 73        # Load sounds
 74        self.collect_coin_sound = arcade.load_sound(":resources:sounds/coin1.wav")
 75        self.jump_sound = arcade.load_sound(":resources:sounds/jump1.wav")
 76        self.game_over = arcade.load_sound(":resources:sounds/gameover1.wav")
 77
 78    def setup(self):
 79        """Set up the game here. Call this function to restart the game."""
 80
 81        # Set up the Cameras
 82        viewport = (0, 0, self.width, self.height)
 83        self.camera = arcade.SimpleCamera(viewport=viewport)
 84        self.gui_camera = arcade.SimpleCamera(viewport=viewport)
 85
 86        # Map name
 87        map_name = ":resources:tiled_maps/map_with_ladders.json"
 88
 89        # Layer Specific Options for the Tilemap
 90        layer_options = {
 91            LAYER_NAME_PLATFORMS: {
 92                "use_spatial_hash": True,
 93            },
 94            LAYER_NAME_MOVING_PLATFORMS: {
 95                "use_spatial_hash": False,
 96            },
 97            LAYER_NAME_LADDERS: {
 98                "use_spatial_hash": True,
 99            },
100            LAYER_NAME_COINS: {
101                "use_spatial_hash": True,
102            },
103        }
104
105        # Load in TileMap
106        self.tile_map = arcade.load_tilemap(map_name, TILE_SCALING, layer_options)
107
108        # Initiate New Scene with our TileMap, this will automatically add all layers
109        # from the map as SpriteLists in the scene in the proper order.
110        self.scene = arcade.Scene.from_tilemap(self.tile_map)
111
112        # Keep track of the score
113        self.score = 0
114
115        # Set up the player, specifically placing it at these coordinates.
116        image_source = ":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png"
117        self.player_sprite = arcade.Sprite(image_source, CHARACTER_SCALING)
118        self.player_sprite.center_x = PLAYER_START_X
119        self.player_sprite.center_y = PLAYER_START_Y
120        self.scene.add_sprite("Player", self.player_sprite)
121
122        # Calculate the right edge of the my_map in pixels
123        self.end_of_map = self.tile_map.width * GRID_PIXEL_SIZE
124
125        # --- Other stuff
126        # Set the background color
127        if self.tile_map.background_color:
128            self.background_color = self.tile_map.background_color
129
130        # Create the 'physics engine'
131        self.physics_engine = arcade.PhysicsEnginePlatformer(
132            self.player_sprite,
133            platforms=self.scene[LAYER_NAME_MOVING_PLATFORMS],
134            gravity_constant=GRAVITY,
135            ladders=self.scene[LAYER_NAME_LADDERS],
136            walls=self.scene[LAYER_NAME_PLATFORMS]
137        )
138
139    def on_draw(self):
140        """Render the screen."""
141        # Clear the screen to the background color
142        self.clear()
143
144        # Activate the game camera
145        self.camera.use()
146
147        # Draw our Scene
148        self.scene.draw()
149
150        # Activate the GUI camera before drawing GUI elements
151        self.gui_camera.use()
152
153        # Draw our score on the screen, scrolling it with the viewport
154        score_text = f"Score: {self.score}"
155        arcade.draw_text(
156            score_text,
157            10,
158            10,
159            arcade.csscolor.BLACK,
160            18,
161        )
162
163    def on_key_press(self, key, modifiers):
164        """Called whenever a key is pressed."""
165
166        if key == arcade.key.UP or key == arcade.key.W:
167            if self.physics_engine.is_on_ladder():
168                self.player_sprite.change_y = PLAYER_MOVEMENT_SPEED
169            elif self.physics_engine.can_jump():
170                self.player_sprite.change_y = PLAYER_JUMP_SPEED
171                arcade.play_sound(self.jump_sound)
172        elif key == arcade.key.DOWN or key == arcade.key.S:
173            if self.physics_engine.is_on_ladder():
174                self.player_sprite.change_y = -PLAYER_MOVEMENT_SPEED
175        elif key == arcade.key.LEFT or key == arcade.key.A:
176            self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
177        elif key == arcade.key.RIGHT or key == arcade.key.D:
178            self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
179
180    def on_key_release(self, key, modifiers):
181        """Called when the user releases a key."""
182
183        if key == arcade.key.UP or key == arcade.key.W:
184            if self.physics_engine.is_on_ladder():
185                self.player_sprite.change_y = 0
186        elif key == arcade.key.DOWN or key == arcade.key.S:
187            if self.physics_engine.is_on_ladder():
188                self.player_sprite.change_y = 0
189        elif key == arcade.key.LEFT or key == arcade.key.A:
190            self.player_sprite.change_x = 0
191        elif key == arcade.key.RIGHT or key == arcade.key.D:
192            self.player_sprite.change_x = 0
193
194    def center_camera_to_player(self):
195        screen_center_x = self.player_sprite.center_x - (self.camera.viewport_width / 2)
196        screen_center_y = self.player_sprite.center_y - (
197            self.camera.viewport_height / 2
198        )
199        if screen_center_x < 0:
200            screen_center_x = 0
201        if screen_center_y < 0:
202            screen_center_y = 0
203        player_centered = screen_center_x, screen_center_y
204
205        self.camera.move_to(player_centered, 0.2)
206
207    def on_update(self, delta_time):
208        """Movement and game logic"""
209        # Move the player with the physics engine
210        self.physics_engine.update()
211
212        # Update animations
213        self.scene.update_animation(
214            delta_time, [LAYER_NAME_COINS, LAYER_NAME_BACKGROUND]
215        )
216
217        # Update walls, used with moving platforms
218        self.scene.update([LAYER_NAME_MOVING_PLATFORMS])
219
220        # See if we hit any coins
221        coin_hit_list = arcade.check_for_collision_with_list(
222            self.player_sprite, self.scene[LAYER_NAME_COINS]
223        )
224
225        # Loop through each coin we hit (if any) and remove it
226        for coin in coin_hit_list:
227
228            # Figure out how many points this coin is worth
229            if "Points" not in coin.properties:
230                print("Warning, collected a coin without a Points property.")
231            else:
232                points = int(coin.properties["Points"])
233                self.score += points
234
235            # Remove the coin
236            coin.remove_from_sprite_lists()
237            arcade.play_sound(self.collect_coin_sound)
238
239        # Position the camera
240        self.center_camera_to_player()
241
242
243def main():
244    """Main function"""
245    window = MyGame()
246    window.setup()
247    arcade.run()
248
249
250if __name__ == "__main__":
251    main()

Source Code#

Ladders and More#
  1"""
  2Platformer Game
  3
  4python -m arcade.examples.platform_tutorial.11_ladders_and_more
  5"""
  6from __future__ import annotations
  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
 16CHARACTER_SCALING = 1
 17TILE_SCALING = 0.5
 18COIN_SCALING = 0.5
 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 = 64
 28PLAYER_START_Y = 256
 29
 30# Layer Names from our TileMap
 31LAYER_NAME_MOVING_PLATFORMS = "Moving Platforms"
 32LAYER_NAME_PLATFORMS = "Platforms"
 33LAYER_NAME_COINS = "Coins"
 34LAYER_NAME_BACKGROUND = "Background"
 35LAYER_NAME_LADDERS = "Ladders"
 36
 37
 38class MyGame(arcade.Window):
 39    """
 40    Main application class.
 41    """
 42
 43    def __init__(self):
 44        """
 45        Initializer for the game
 46        """
 47        # Call the parent class and set up the window
 48        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
 49
 50        # Our TileMap Object
 51        self.tile_map = None
 52
 53        # Our Scene Object
 54        self.scene = None
 55
 56        # Separate variable that holds the player sprite
 57        self.player_sprite = None
 58
 59        # Our 'physics' engine
 60        self.physics_engine = None
 61
 62        # A Camera that can be used for scrolling the screen
 63        self.camera = None
 64
 65        # A Camera that can be used to draw GUI elements
 66        self.gui_camera = None
 67
 68        self.end_of_map = 0
 69
 70        # Keep track of the score
 71        self.score = 0
 72
 73        # Load sounds
 74        self.collect_coin_sound = arcade.load_sound(":resources:sounds/coin1.wav")
 75        self.jump_sound = arcade.load_sound(":resources:sounds/jump1.wav")
 76        self.game_over = arcade.load_sound(":resources:sounds/gameover1.wav")
 77
 78    def setup(self):
 79        """Set up the game here. Call this function to restart the game."""
 80
 81        # Set up the Cameras
 82        viewport = (0, 0, self.width, self.height)
 83        self.camera = arcade.SimpleCamera(viewport=viewport)
 84        self.gui_camera = arcade.SimpleCamera(viewport=viewport)
 85
 86        # Map name
 87        map_name = ":resources:tiled_maps/map_with_ladders.json"
 88
 89        # Layer Specific Options for the Tilemap
 90        layer_options = {
 91            LAYER_NAME_PLATFORMS: {
 92                "use_spatial_hash": True,
 93            },
 94            LAYER_NAME_MOVING_PLATFORMS: {
 95                "use_spatial_hash": False,
 96            },
 97            LAYER_NAME_LADDERS: {
 98                "use_spatial_hash": True,
 99            },
100            LAYER_NAME_COINS: {
101                "use_spatial_hash": True,
102            },
103        }
104
105        # Load in TileMap
106        self.tile_map = arcade.load_tilemap(map_name, TILE_SCALING, layer_options)
107
108        # Initiate New Scene with our TileMap, this will automatically add all layers
109        # from the map as SpriteLists in the scene in the proper order.
110        self.scene = arcade.Scene.from_tilemap(self.tile_map)
111
112        # Keep track of the score
113        self.score = 0
114
115        # Set up the player, specifically placing it at these coordinates.
116        image_source = ":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png"
117        self.player_sprite = arcade.Sprite(image_source, CHARACTER_SCALING)
118        self.player_sprite.center_x = PLAYER_START_X
119        self.player_sprite.center_y = PLAYER_START_Y
120        self.scene.add_sprite("Player", self.player_sprite)
121
122        # Calculate the right edge of the my_map in pixels
123        self.end_of_map = self.tile_map.width * GRID_PIXEL_SIZE
124
125        # --- Other stuff
126        # Set the background color
127        if self.tile_map.background_color:
128            self.background_color = self.tile_map.background_color
129
130        # Create the 'physics engine'
131        self.physics_engine = arcade.PhysicsEnginePlatformer(
132            self.player_sprite,
133            platforms=self.scene[LAYER_NAME_MOVING_PLATFORMS],
134            gravity_constant=GRAVITY,
135            ladders=self.scene[LAYER_NAME_LADDERS],
136            walls=self.scene[LAYER_NAME_PLATFORMS]
137        )
138
139    def on_draw(self):
140        """Render the screen."""
141        # Clear the screen to the background color
142        self.clear()
143
144        # Activate the game camera
145        self.camera.use()
146
147        # Draw our Scene
148        self.scene.draw()
149
150        # Activate the GUI camera before drawing GUI elements
151        self.gui_camera.use()
152
153        # Draw our score on the screen, scrolling it with the viewport
154        score_text = f"Score: {self.score}"
155        arcade.draw_text(
156            score_text,
157            10,
158            10,
159            arcade.csscolor.BLACK,
160            18,
161        )
162
163    def on_key_press(self, key, modifiers):
164        """Called whenever a key is pressed."""
165
166        if key == arcade.key.UP or key == arcade.key.W:
167            if self.physics_engine.is_on_ladder():
168                self.player_sprite.change_y = PLAYER_MOVEMENT_SPEED
169            elif self.physics_engine.can_jump():
170                self.player_sprite.change_y = PLAYER_JUMP_SPEED
171                arcade.play_sound(self.jump_sound)
172        elif key == arcade.key.DOWN or key == arcade.key.S:
173            if self.physics_engine.is_on_ladder():
174                self.player_sprite.change_y = -PLAYER_MOVEMENT_SPEED
175        elif key == arcade.key.LEFT or key == arcade.key.A:
176            self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
177        elif key == arcade.key.RIGHT or key == arcade.key.D:
178            self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
179
180    def on_key_release(self, key, modifiers):
181        """Called when the user releases a key."""
182
183        if key == arcade.key.UP or key == arcade.key.W:
184            if self.physics_engine.is_on_ladder():
185                self.player_sprite.change_y = 0
186        elif key == arcade.key.DOWN or key == arcade.key.S:
187            if self.physics_engine.is_on_ladder():
188                self.player_sprite.change_y = 0
189        elif key == arcade.key.LEFT or key == arcade.key.A:
190            self.player_sprite.change_x = 0
191        elif key == arcade.key.RIGHT or key == arcade.key.D:
192            self.player_sprite.change_x = 0
193
194    def center_camera_to_player(self):
195        screen_center_x = self.player_sprite.center_x - (self.camera.viewport_width / 2)
196        screen_center_y = self.player_sprite.center_y - (
197            self.camera.viewport_height / 2
198        )
199        if screen_center_x < 0:
200            screen_center_x = 0
201        if screen_center_y < 0:
202            screen_center_y = 0
203        player_centered = screen_center_x, screen_center_y
204
205        self.camera.move_to(player_centered, 0.2)
206
207    def on_update(self, delta_time):
208        """Movement and game logic"""
209        # Move the player with the physics engine
210        self.physics_engine.update()
211
212        # Update animations
213        self.scene.update_animation(
214            delta_time, [LAYER_NAME_COINS, LAYER_NAME_BACKGROUND]
215        )
216
217        # Update walls, used with moving platforms
218        self.scene.update([LAYER_NAME_MOVING_PLATFORMS])
219
220        # See if we hit any coins
221        coin_hit_list = arcade.check_for_collision_with_list(
222            self.player_sprite, self.scene[LAYER_NAME_COINS]
223        )
224
225        # Loop through each coin we hit (if any) and remove it
226        for coin in coin_hit_list:
227
228            # Figure out how many points this coin is worth
229            if "Points" not in coin.properties:
230                print("Warning, collected a coin without a Points property.")
231            else:
232                points = int(coin.properties["Points"])
233                self.score += points
234
235            # Remove the coin
236            coin.remove_from_sprite_lists()
237            arcade.play_sound(self.collect_coin_sound)
238
239        # Position the camera
240        self.center_camera_to_player()
241
242
243def main():
244    """Main function"""
245    window = MyGame()
246    window.setup()
247    arcade.run()
248
249
250if __name__ == "__main__":
251    main()