Step 9 - Use Tiled Map Editor#

../../_images/use_tileset.png

Create a Map File#

For this part, instead of placing the tiles through code using specific points, we’ll use a map editor that we can build maps with and then load in the map files.

To start off with, download and install the Tiled Map Editor. (Think about donating, as it is a wonderful project provided for free.)

Tiled already has excellent documentation available at https://doc.mapeditor.org/, so for this tutorial we’ll assume that you’re already familiar with how to create maps using Tiled. If you’re not, you can check out the Tiled documentation and come back to here.

From this point on in the tutorial, every chapter will be working with a Tiled map. If you don’t want to create your own yet, Arcade ships a few examples in it’s included resources folder, which is what these examples pull from, so you don’t have to create your own maps yet if you don’t want to.

We’ll start with a basic map.json file provided by Arcade. You can open this file in Tiled and look at how it’s setup, but we’ll go over some of the basics now. You can save files in either the “JSON” or “TMX” format.

In this map we have two layers named “Platforms” and “Coins”. On the platforms layer are all of the blocks which a player will collide with using the physics engine, and on the coins layer are all the coins the player can pickup to increase their score. That’s pretty much it for this map.

../../_images/layer_names.png

These layers will be automatically loaded by Arcade as SpriteLists that we can access and draw with our scene. Let’s look at how we load in the map, first we’ll create a tile_map object in our init function:

Load a map - Create the object#
        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)

Then we will do the actual loading in the setup function Our new setup function will look like this:

Load a map - Setup the map#
 1    def setup(self):
 2        """Set up the game here. Call this function to restart the game."""
 3
 4        # Set up the Cameras
 5        viewport = (0, 0, self.width, self.height)
 6        self.camera = arcade.SimpleCamera(viewport=viewport)
 7        self.gui_camera = arcade.SimpleCamera(viewport=viewport)
 8
 9        # Name of map file to load
10        map_name = ":resources:tiled_maps/map.json"
11
12        # Layer specific options are defined based on Layer names in a dictionary
13        # Doing this will make the SpriteList for the platforms layer
14        # use spatial hashing for detection.
15        layer_options = {
16            "Platforms": {
17                "use_spatial_hash": True,
18            },
19        }
20
21        # Read in the tiled map
22        self.tile_map = arcade.load_tilemap(map_name, TILE_SCALING, layer_options)
23
24        # Initialize Scene with our TileMap, this will automatically add all layers
25        # from the map as SpriteLists in the scene in the proper order.
26        self.scene = arcade.Scene.from_tilemap(self.tile_map)
27
28        # Keep track of the score
29        self.score = 0
30
31        # Set up the player, specifically placing it at these coordinates.
32        image_source = ":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png"
33        self.player_sprite = arcade.Sprite(image_source, CHARACTER_SCALING)
34        self.player_sprite.center_x = 128
35        self.player_sprite.center_y = 128
36        self.scene.add_sprite("Player", self.player_sprite)
37
38        # --- Other stuff
39        # Set the background color
40        if self.tile_map.background_color:
41            arcade.set_background_color(self.tile_map.background_color)
42
43        # Create the 'physics engine'
44        self.physics_engine = arcade.PhysicsEnginePlatformer(
45            self.player_sprite, gravity_constant=GRAVITY, walls=self.scene["Platforms"]
46        )

This is pretty much all that needs done to load in the Tilemap, we get a Scene created from it and can use it just like we have been up until now. But let’s go through this setup function and look at all the updates.

In the first piece we define the name of map file we want to load, that one is pretty simple.

Next we have a layer_options variable. This is a dictionary which let’s you assign special options to specific layers in the map. In this example, we’re just adding spatial hashing to the “Platforms” layer, but we can do a few other things here.

The available options you can set for a layer are:

  • use_spatial_hash - Make a Layer’s SpriteList use spatial hashing

  • scaling - Set per layer scaling of Sprites

  • hit_box_algorithm - Change the hit box algorithm used when doing collision detection with this SpriteList

  • hit_box_detail - Change the hit box detail used when doing collision detection with this SpriteList

Then we actually load in the Tilemap using the arcade.load_tilemap function. This will return us back an instance of the arcade.TileMap class. For now, we don’t actually need to interact with this object much, but later we will do some more advanced things like setting enemy spawn points and movement paths from within the map editor.

Finally we use a new way to create our Scene, with the arcade.Scene.from_tilemap function. This let’s you specify a TileMap object, and will automatically construct a scene with all of the layers in your map, arranged in the proper render order. Then you can work with the scene exactly like we have up until this point.

The last small piece we changed is when we create the physics engine, we’ve now have to use “Platforms” as the sprite list name since that is the name of our Layer in the map file.

And that’s all! You should now have a full game loading from a map file created with Tiled.

Some things we will use Tiled for in upcoming chapters are:

  • Platforms that you run into (or you can think of them as walls)

  • Moving platforms

  • Coins or objects to pick up

  • Background objects that you don’t interact with, but appear behind the player

  • Foreground objects that you don’t interact with, but appear in front of the player

  • Insta-death blocks and zones (like lava)

  • Ladders

  • Enemy spawn positions

  • Enemy movement paths

Source Code#

Load the Map#
  1"""
  2Platformer Game
  3
  4python -m arcade.examples.platform_tutorial.09_load_map
  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
 17
 18# Movement speed of player, in pixels per frame
 19PLAYER_MOVEMENT_SPEED = 10
 20GRAVITY = 1
 21PLAYER_JUMP_SPEED = 20
 22
 23
 24class MyGame(arcade.Window):
 25    """
 26    Main application class.
 27    """
 28
 29    def __init__(self):
 30
 31        # Call the parent class and set up the window
 32        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
 33
 34        # Our TileMap Object
 35        self.tile_map = None
 36
 37        # Our Scene Object
 38        self.scene = None
 39
 40        # Separate variable that holds the player sprite
 41        self.player_sprite = None
 42
 43        # Our physics engine
 44        self.physics_engine = None
 45
 46        # A Camera that can be used for scrolling the screen
 47        self.camera = None
 48
 49        # A Camera that can be used to draw GUI elements
 50        self.gui_camera = None
 51
 52        # Keep track of the score
 53        self.score = 0
 54
 55        # Load sounds
 56        self.collect_coin_sound = arcade.load_sound(":resources:sounds/coin1.wav")
 57        self.jump_sound = arcade.load_sound(":resources:sounds/jump1.wav")
 58
 59        arcade.set_background_color(arcade.csscolor.CORNFLOWER_BLUE)
 60
 61    def setup(self):
 62        """Set up the game here. Call this function to restart the game."""
 63
 64        # Set up the Cameras
 65        viewport = (0, 0, self.width, self.height)
 66        self.camera = arcade.SimpleCamera(viewport=viewport)
 67        self.gui_camera = arcade.SimpleCamera(viewport=viewport)
 68
 69        # Name of map file to load
 70        map_name = ":resources:tiled_maps/map.json"
 71
 72        # Layer specific options are defined based on Layer names in a dictionary
 73        # Doing this will make the SpriteList for the platforms layer
 74        # use spatial hashing for detection.
 75        layer_options = {
 76            "Platforms": {
 77                "use_spatial_hash": True,
 78            },
 79        }
 80
 81        # Read in the tiled map
 82        self.tile_map = arcade.load_tilemap(map_name, TILE_SCALING, layer_options)
 83
 84        # Initialize Scene with our TileMap, this will automatically add all layers
 85        # from the map as SpriteLists in the scene in the proper order.
 86        self.scene = arcade.Scene.from_tilemap(self.tile_map)
 87
 88        # Keep track of the score
 89        self.score = 0
 90
 91        # Set up the player, specifically placing it at these coordinates.
 92        image_source = ":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png"
 93        self.player_sprite = arcade.Sprite(image_source, CHARACTER_SCALING)
 94        self.player_sprite.center_x = 128
 95        self.player_sprite.center_y = 128
 96        self.scene.add_sprite("Player", self.player_sprite)
 97
 98        # --- Other stuff
 99        # Set the background color
100        if self.tile_map.background_color:
101            arcade.set_background_color(self.tile_map.background_color)
102
103        # Create the 'physics engine'
104        self.physics_engine = arcade.PhysicsEnginePlatformer(
105            self.player_sprite, gravity_constant=GRAVITY, walls=self.scene["Platforms"]
106        )
107
108    def on_draw(self):
109        """Render the screen."""
110
111        # Clear the screen to the background color
112        self.clear()
113
114        # Activate the game camera
115        self.camera.use()
116
117        # Draw our Scene
118        self.scene.draw()
119
120        # Activate the GUI camera before drawing GUI elements
121        self.gui_camera.use()
122
123        # Draw our score on the screen, scrolling it with the viewport
124        score_text = f"Score: {self.score}"
125        arcade.draw_text(
126            score_text,
127            10,
128            10,
129            arcade.csscolor.WHITE,
130            18,
131        )
132
133    def on_key_press(self, key, modifiers):
134        """Called whenever a key is pressed."""
135
136        if key == arcade.key.UP or key == arcade.key.W:
137            if self.physics_engine.can_jump():
138                self.player_sprite.change_y = PLAYER_JUMP_SPEED
139                arcade.play_sound(self.jump_sound)
140        elif key == arcade.key.LEFT or key == arcade.key.A:
141            self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
142        elif key == arcade.key.RIGHT or key == arcade.key.D:
143            self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
144
145    def on_key_release(self, key, modifiers):
146        """Called when the user releases a key."""
147
148        if key == arcade.key.LEFT or key == arcade.key.A:
149            self.player_sprite.change_x = 0
150        elif key == arcade.key.RIGHT or key == arcade.key.D:
151            self.player_sprite.change_x = 0
152
153    def center_camera_to_player(self):
154        screen_center_x = self.player_sprite.center_x - (self.camera.viewport_width / 2)
155        screen_center_y = self.player_sprite.center_y - (
156            self.camera.viewport_height / 2
157        )
158        if screen_center_x < 0:
159            screen_center_x = 0
160        if screen_center_y < 0:
161            screen_center_y = 0
162        player_centered = screen_center_x, screen_center_y
163
164        self.camera.move_to(player_centered)
165
166    def on_update(self, delta_time):
167        """Movement and game logic"""
168
169        # Move the player with the physics engine
170        self.physics_engine.update()
171
172        # See if we hit any coins
173        coin_hit_list = arcade.check_for_collision_with_list(
174            self.player_sprite, self.scene["Coins"]
175        )
176
177        # Loop through each coin we hit (if any) and remove it
178        for coin in coin_hit_list:
179            # Remove the coin
180            coin.remove_from_sprite_lists()
181            # Play a sound
182            arcade.play_sound(self.collect_coin_sound)
183            # Add one to the score
184            self.score += 1
185
186        # Position the camera
187        self.center_camera_to_player()
188
189
190def main():
191    """Main function"""
192    window = MyGame()
193    window.setup()
194    arcade.run()
195
196
197if __name__ == "__main__":
198    main()