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 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
  9WINDOW_WIDTH = 1280
 10WINDOW_HEIGHT = 720
 11WINDOW_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 GameView(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__(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_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(
 71            ":resources:tiled_maps/map.json",
 72            scaling=TILE_SCALING,
 73            layer_options=layer_options,
 74        )
 75
 76        # Create our Scene Based on the TileMap
 77        self.scene = arcade.Scene.from_tilemap(self.tile_map)
 78
 79        self.player_texture = arcade.load_texture(
 80            ":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png"
 81        )
 82
 83        self.player_sprite = arcade.Sprite(self.player_texture)
 84        self.player_sprite.center_x = 128
 85        self.player_sprite.center_y = 128
 86        self.scene.add_sprite("Player", self.player_sprite)
 87
 88        # Create a Platformer Physics Engine, this will handle moving our
 89        # player as well as collisions between the player sprite and
 90        # whatever SpriteList we specify for the walls.
 91        # It is important to supply static to the walls parameter. There is a
 92        # platforms parameter that is intended for moving platforms.
 93        # If a platform is supposed to move, and is added to the walls list,
 94        # it will not be moved.
 95        self.physics_engine = arcade.PhysicsEnginePlatformer(
 96            self.player_sprite, walls=self.scene["Platforms"], gravity_constant=GRAVITY
 97        )
 98
 99        # Initialize our camera, setting a viewport the size of our window.
100        self.camera = arcade.camera.Camera2D()
101
102        # Initialize our gui camera, initial settings are the same as our world camera.
103        self.gui_camera = arcade.camera.Camera2D()
104
105        # Reset our score to 0
106        self.score = 0
107
108        # Initialize our arcade.Text object for score
109        self.score_text = arcade.Text(f"Score: {self.score}", x=0, y=5)
110
111        self.background_color = arcade.csscolor.CORNFLOWER_BLUE
112
113    def on_draw(self):
114        """Render the screen."""
115
116        # Clear the screen to the background color
117        self.clear()
118
119        # Activate our camera before drawing
120        self.camera.use()
121
122        # Draw our Scene
123        self.scene.draw()
124
125        # Activate our GUI camera
126        self.gui_camera.use()
127
128        # Draw our Score
129        self.score_text.draw()
130
131    def on_update(self, delta_time):
132        """Movement and Game Logic"""
133
134        # Move the player using our physics engine
135        self.physics_engine.update()
136
137        # See if we hit any coins
138        coin_hit_list = arcade.check_for_collision_with_list(
139            self.player_sprite, self.scene["Coins"]
140        )
141
142        # Loop through each coin we hit (if any) and remove it
143        for coin in coin_hit_list:
144            # Remove the coin
145            coin.remove_from_sprite_lists()
146            arcade.play_sound(self.collect_coin_sound)
147            self.score += 75
148            self.score_text.text = f"Score: {self.score}"
149
150        # Center our camera on the player
151        self.camera.position = self.player_sprite.position
152
153    def on_key_press(self, key, modifiers):
154        """Called whenever a key is pressed."""
155
156        if key == arcade.key.ESCAPE:
157            self.setup()
158
159        if key == arcade.key.UP or key == arcade.key.W:
160            if self.physics_engine.can_jump():
161                self.player_sprite.change_y = PLAYER_JUMP_SPEED
162                arcade.play_sound(self.jump_sound)
163
164        if key == arcade.key.LEFT or key == arcade.key.A:
165            self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
166        elif key == arcade.key.RIGHT or key == arcade.key.D:
167            self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
168
169    def on_key_release(self, key, modifiers):
170        """Called whenever a key is released."""
171
172        if key == arcade.key.LEFT or key == arcade.key.A:
173            self.player_sprite.change_x = 0
174        elif key == arcade.key.RIGHT or key == arcade.key.D:
175            self.player_sprite.change_x = 0
176
177
178def main():
179    """Main function"""
180    window = GameView()
181    window.setup()
182    arcade.run()
183
184
185if __name__ == "__main__":
186    main()