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