Step 12 - Loading a Map From a Map Editor#

In this chapter we will start using a map editor called Tiled. Tiled is a popular 2D map editor, it can be used with any game engine, but Arcade has specific integrations for working with Tiled.

We’ll explore how to load maps from Tiled in this tutorial using Arcade’s built-in arcade.TileMap class using some maps from the built-in resources that Arcade comes with. We won’t cover actually building a map in Tiled this tutorial, but if you want to learn more about Tiled check out the resources below:

You won’t actually need Tiled to continue following this tutorial. We will be using all pre-built maps included with Arcade. However if you want to experiment with your own maps or changing things, I recommend getting Tiled and getting familiar with it, it is a really useful tool for 2D Game Development.

To start off with, we’re going to remove a bunch of code. Namely we’ll remove the creation of our ground, boxes, and coin sprites(We’ll leave the player one). Go ahead and remove the following blocks of code from the setup function.

self.scene.add_sprite_list("Walls", use_spatial_hash=True)
self.scene.add_sprite_list("Coins", use_spatial_hash=True)

for x in range(0, 1250, 64):
    wall = arcade.Sprite(":resources:images/tiles/grassMid.png", scale=TILE_SCALING)
    wall.center_x = x
    wall.center_y = 32
    self.scene.add_sprite("Walls", wall)

coordinate_list = [[512, 96], [256, 96], [768, 96]]

for coordinate in coordinate_list:
    wall = arcade.Sprite(
        ":resources:images/tiles/boxCrate_double.png", scale=TILE_SCALING
    )
    wall.position = coordinate
    self.scene.add_sprite("Walls", wall)

for x in range(128, 1250, 256):
    coin = arcade.Sprite(":resources:images/items/coinGold.png", scale=COIN_SCALING)
    coin.center_x = x
    coin.center_y = 96
    self.scene.add_sprite("Coins", coin)

These things will now be handled by our map file automatically once we start loading it.

In order to load our map, we will first create a variable for it in __init__:

self.tile_map = None

Next we will load our map in our setup function, and then create a Scene from it using a built-in function Arcade provides. This will give us a drawable scene completely based off of the map file automatically. This code will all go at the top of the setup function.

Make sure to replace the line that sets self.scene with the new one below.

layer_options = {
    "Platforms": {
        "use_spatial_hash": True
    }
}

self.tile_map = arcade.load_tilemap(
    ":resources:tiled_maps/map.json",
    scaling=TILE_SCALING,
    layer_options=layer_options
)

self.scene = arcade.Scene.from_tilemap(self.tile_map)

This code will load in our built-in Tiled Map and automatically build a Scene from it. The Scene at this stage is ready for drawing and we don’t need to do anything else to it(other than add our player).

Note

What is layer_options and where are those values in it coming from?

layer_options is a special dictionary that can be provided to the load_tilemap function. This will send special options for each layer into the map loader. In this example our map has a layer called Platforms, and we want to enable spatial hashing on it. Much like we did for our wall SpriteList before. For more info on the layer options dictionary and the available keys, check out :class`arcade.TileMap`

At this point we only have one piece of code left to change. In switching to our new map, you may have noticed by the layer_options dictionary that we now have a layer named Platforms. Previously in our Scene we were calling this layer Walls. We’ll need to go update that reference when we create our Physics Engine.

In the setup function update the Physics Engine creation to use the the new Platforms layer:

self.physics_engine = arcade.PhysicsEnginePlatformer(
    self.player_sprite, walls=self.scene["Platforms"], gravity_constant=GRAVITY
)

Source Code#

Loading a Map From a Map Editor#
  1"""
  2Platformer Game
  3
  4python -m arcade.examples.platform_tutorial.12_tiled
  5"""
  6import arcade
  7
  8# Constants
  9SCREEN_WIDTH = 800
 10SCREEN_HEIGHT = 600
 11SCREEN_TITLE = "Platformer"
 12
 13# Constants used to scale our sprites from their original size
 14TILE_SCALING = 0.5
 15COIN_SCALING = 0.5
 16
 17# Movement speed of player, in pixels per frame
 18PLAYER_MOVEMENT_SPEED = 5
 19GRAVITY = 1
 20PLAYER_JUMP_SPEED = 20
 21
 22
 23class MyGame(arcade.Window):
 24    """
 25    Main application class.
 26    """
 27
 28    def __init__(self):
 29
 30        # Call the parent class and set up the window
 31        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
 32
 33        # Variable to hold our texture for our player
 34        self.player_texture = None
 35
 36        # Separate variable that holds the player sprite
 37        self.player_sprite = None
 38
 39        # Variable to hold our Tiled Map
 40        self.tile_map = None
 41
 42        # Replacing all of our SpriteLists with a Scene variable
 43        self.scene = None
 44
 45        # A variable to store our camera object
 46        self.camera = None
 47
 48        # A variable to store our gui camera object
 49        self.gui_camera = None
 50
 51        # This variable will store our score as an integer.
 52        self.score = 0
 53
 54        # This variable will store the text for score that we will draw to the screen.
 55        self.score_text = None
 56
 57        # Load sounds
 58        self.collect_coin_sound = arcade.load_sound(":resources:sounds/coin1.wav")
 59        self.jump_sound = arcade.load_sound(":resources:sounds/jump1.wav")
 60
 61    def setup(self):
 62        """Set up the game here. Call this function to restart the game."""
 63        layer_options = {
 64            "Platforms": {
 65                "use_spatial_hash": True
 66            }
 67        }
 68
 69        # Load our TileMap
 70        self.tile_map = arcade.load_tilemap(":resources:tiled_maps/map.json", scaling=TILE_SCALING, layer_options=layer_options)
 71
 72        # Create our Scene Based on the TileMap
 73        self.scene = arcade.Scene.from_tilemap(self.tile_map)
 74
 75        self.player_texture = arcade.load_texture(":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png")
 76
 77        self.player_sprite = arcade.Sprite(self.player_texture)
 78        self.player_sprite.center_x = 128
 79        self.player_sprite.center_y = 128
 80        self.scene.add_sprite("Player", self.player_sprite)
 81
 82        # Create a Platformer Physics Engine, this will handle moving our
 83        # player as well as collisions between the player sprite and
 84        # whatever SpriteList we specify for the walls.
 85        # It is important to supply static to the walls parameter. There is a
 86        # platforms parameter that is intended for moving platforms.
 87        # If a platform is supposed to move, and is added to the walls list,
 88        # it will not be moved.
 89        self.physics_engine = arcade.PhysicsEnginePlatformer(
 90            self.player_sprite, walls=self.scene["Platforms"], gravity_constant=GRAVITY
 91        )
 92
 93        # Initialize our camera, setting a viewport the size of our window.
 94        self.camera = arcade.camera.Camera2D()
 95
 96        # Initialize our gui camera, initial settings are the same as our world camera.
 97        self.gui_camera = arcade.camera.Camera2D()
 98
 99        # Reset our score to 0
100        self.score = 0
101
102        # Initialize our arcade.Text object for score
103        self.score_text = arcade.Text(f"Score: {self.score}", x=0, y=5)
104
105        self.background_color = arcade.csscolor.CORNFLOWER_BLUE
106
107    def on_draw(self):
108        """Render the screen."""
109
110        # Clear the screen to the background color
111        self.clear()
112
113        # Activate our camera before drawing
114        self.camera.use()
115
116        # Draw our Scene
117        self.scene.draw()
118
119        # Activate our GUI camera
120        self.gui_camera.use()
121
122        # Draw our Score
123        self.score_text.draw()
124
125    def on_update(self, delta_time):
126        """Movement and Game Logic"""
127
128        # Move the player using our physics engine
129        self.physics_engine.update()
130
131        # See if we hit any coins
132        coin_hit_list = arcade.check_for_collision_with_list(
133            self.player_sprite, self.scene["Coins"]
134        )
135
136        # Loop through each coin we hit (if any) and remove it
137        for coin in coin_hit_list:
138            # Remove the coin
139            coin.remove_from_sprite_lists()
140            arcade.play_sound(self.collect_coin_sound)
141            self.score += 75
142            self.score_text.text = f"Score: {self.score}"
143
144        # Center our camera on the player
145        self.camera.position = self.player_sprite.position
146
147    def on_key_press(self, key, modifiers):
148        """Called whenever a key is pressed."""
149
150        if key == arcade.key.ESCAPE:
151            self.setup()
152
153        if key == arcade.key.UP or key == arcade.key.W:
154            if self.physics_engine.can_jump():
155                self.player_sprite.change_y = PLAYER_JUMP_SPEED
156                arcade.play_sound(self.jump_sound)
157
158        if key == arcade.key.LEFT or key == arcade.key.A:
159            self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
160        elif key == arcade.key.RIGHT or key == arcade.key.D:
161            self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
162
163    def on_key_release(self, key, modifiers):
164        """Called whenever a key is released."""
165
166        if key == arcade.key.LEFT or key == arcade.key.A:
167            self.player_sprite.change_x = 0
168        elif key == arcade.key.RIGHT or key == arcade.key.D:
169            self.player_sprite.change_x = 0
170
171
172def main():
173    """Main function"""
174    window = MyGame()
175    window.setup()
176    arcade.run()
177
178
179if __name__ == "__main__":
180    main()