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

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:
Define an object layer instead of a tile layer.
Select Insert Tile
Select the tile you wish to insert.
Place the tile.
Add custom properties. You can add:
change_x
change_y
boundary_bottom
boundary_top
boundary_left
boundary_right

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