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

Source Code#

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