Step 8 - Use a Map Editor¶
Create a Map File¶
For this part, we’ll restart with a new program. Instead of placing our tiles by code, we’ll use a map editor.
Download and install the Tiled Map Editor. (Think about donating, as it is a wonderful project.)
Open a new file with options similar to these:
Orthogonal - This is a normal square-grid layout. It is the only version that Arcade supports very well at this time.
Tile layer format - This selects how the data is stored inside the file. Any option works, but Base64 zlib compressed is the smallest.
Tile render order - Any of these should work. It simply specifies what order the tiles are added. Right-down has tiles added left->right and top->down.
Map size - You can change this later, but this is your total grid size.
Tile size - the size, in pixels, of your tiles. Your tiles all need to be the same size. Also, rendering works better if the tile size is a power of 2, such as 16, 32, 64, 128, and 256.
Save it as map.tmx
.
Rename the layer “Platforms”. We’ll use layer names to load our data later. Eventually you might have layers for:
Platforms that you run into (or you can think of them as walls)
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 (like lava)
Ladders
Note
Once you get multiple layers it is VERY easy to add items to the wrong layer.
Create a Tileset¶
Before we can add anything to the layer we need to create a set of tiles. This isn’t as obvious or intuitive as it should be. To create a new tileset click “New Tileset” in the window on the lower right:
Right now, Arcade only supports a “collection of images” for a tileset. I find it convenient to embed the tileset in the map.
Once you create a new tile, the button to add tiles to the tileset is hard to find. Click the wrench:
Then click the ‘plus’ and add in your tiles
Draw a Level¶
At this point you should be able to “paint” a level. At the very least, put in a floor and then see if you can get this program working. (Don’t put in a lot of time designing a level until you are sure you can get it to load.)
The program below assumes there are layers created by the tiled map editor for for “Platforms” and “Coins”.
Test the Level¶
1"""
2Platformer Game
3"""
4import arcade
5
6# Constants
7SCREEN_WIDTH = 1000
8SCREEN_HEIGHT = 650
9SCREEN_TITLE = "Platformer"
10
11# Constants used to scale our sprites from their original size
12CHARACTER_SCALING = 1
13TILE_SCALING = 0.5
14COIN_SCALING = 0.5
15SPRITE_PIXEL_SIZE = 128
16GRID_PIXEL_SIZE = (SPRITE_PIXEL_SIZE * TILE_SCALING)
17
18# Movement speed of player, in pixels per frame
19PLAYER_MOVEMENT_SPEED = 10
20GRAVITY = 1
21PLAYER_JUMP_SPEED = 20
22
23# How many pixels to keep as a minimum margin between the character
24# and the edge of the screen.
25LEFT_VIEWPORT_MARGIN = 250
26RIGHT_VIEWPORT_MARGIN = 250
27BOTTOM_VIEWPORT_MARGIN = 100
28TOP_VIEWPORT_MARGIN = 100
29
30
31class MyGame(arcade.Window):
32 """
33 Main application class.
34 """
35
36 def __init__(self):
37
38 # Call the parent class and set up the window
39 super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
40
41 # These are 'lists' that keep track of our sprites. Each sprite should
42 # go into a list.
43 self.coin_list = None
44 self.wall_list = None
45 self.player_list = None
46
47 # Separate variable that holds the player sprite
48 self.player_sprite = None
49
50 # Our physics engine
51 self.physics_engine = None
52
53 # Used to keep track of our scrolling
54 self.view_bottom = 0
55 self.view_left = 0
56
57 # Keep track of the score
58 self.score = 0
59
60 # Load sounds
61 self.collect_coin_sound = arcade.load_sound(":resources:sounds/coin1.wav")
62 self.jump_sound = arcade.load_sound(":resources:sounds/jump1.wav")
63
64 arcade.set_background_color(arcade.csscolor.CORNFLOWER_BLUE)
65
66 def setup(self):
67 """ Set up the game here. Call this function to restart the game. """
68
69 # Used to keep track of our scrolling
70 self.view_bottom = 0
71 self.view_left = 0
72
73 # Keep track of the score
74 self.score = 0
75
76 # Create the Sprite lists
77 self.player_list = arcade.SpriteList()
78 self.wall_list = arcade.SpriteList()
79 self.coin_list = arcade.SpriteList()
80
81 # Set up the player, specifically placing it at these coordinates.
82 image_source = ":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png"
83 self.player_sprite = arcade.Sprite(image_source, CHARACTER_SCALING)
84 self.player_sprite.center_x = 128
85 self.player_sprite.center_y = 128
86 self.player_list.append(self.player_sprite)
87
88 # --- Load in a map from the tiled editor ---
89
90 # Name of map file to load
91 map_name = ":resources:tmx_maps/map.tmx"
92 # Name of the layer in the file that has our platforms/walls
93 platforms_layer_name = 'Platforms'
94 # Name of the layer that has items for pick-up
95 coins_layer_name = 'Coins'
96
97 # Read in the tiled map
98 my_map = arcade.tilemap.read_tmx(map_name)
99
100 # -- Platforms
101 self.wall_list = arcade.tilemap.process_layer(map_object=my_map,
102 layer_name=platforms_layer_name,
103 scaling=TILE_SCALING,
104 use_spatial_hash=True)
105
106 # -- Coins
107 self.coin_list = arcade.tilemap.process_layer(my_map, coins_layer_name, TILE_SCALING)
108
109 # --- Other stuff
110 # Set the background color
111 if my_map.background_color:
112 arcade.set_background_color(my_map.background_color)
113
114 # Create the 'physics engine'
115 self.physics_engine = arcade.PhysicsEnginePlatformer(self.player_sprite,
116 self.wall_list,
117 GRAVITY)
118
119 def on_draw(self):
120 """ Render the screen. """
121
122 # Clear the screen to the background color
123 arcade.start_render()
124
125 # Draw our sprites
126 self.wall_list.draw()
127 self.coin_list.draw()
128 self.player_list.draw()
129
130 # Draw our score on the screen, scrolling it with the viewport
131 score_text = f"Score: {self.score}"
132 arcade.draw_text(score_text, 10 + self.view_left, 10 + self.view_bottom,
133 arcade.csscolor.WHITE, 18)
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 on_update(self, delta_time):
156 """ Movement and game logic """
157
158 # Move the player with the physics engine
159 self.physics_engine.update()
160
161 # See if we hit any coins
162 coin_hit_list = arcade.check_for_collision_with_list(self.player_sprite,
163 self.coin_list)
164
165 # Loop through each coin we hit (if any) and remove it
166 for coin in coin_hit_list:
167 # Remove the coin
168 coin.remove_from_sprite_lists()
169 # Play a sound
170 arcade.play_sound(self.collect_coin_sound)
171 # Add one to the score
172 self.score += 1
173
174 # --- Manage Scrolling ---
175
176 # Track if we need to change the viewport
177
178 changed = False
179
180 # Scroll left
181 left_boundary = self.view_left + LEFT_VIEWPORT_MARGIN
182 if self.player_sprite.left < left_boundary:
183 self.view_left -= left_boundary - self.player_sprite.left
184 changed = True
185
186 # Scroll right
187 right_boundary = self.view_left + SCREEN_WIDTH - RIGHT_VIEWPORT_MARGIN
188 if self.player_sprite.right > right_boundary:
189 self.view_left += self.player_sprite.right - right_boundary
190 changed = True
191
192 # Scroll up
193 top_boundary = self.view_bottom + SCREEN_HEIGHT - TOP_VIEWPORT_MARGIN
194 if self.player_sprite.top > top_boundary:
195 self.view_bottom += self.player_sprite.top - top_boundary
196 changed = True
197
198 # Scroll down
199 bottom_boundary = self.view_bottom + BOTTOM_VIEWPORT_MARGIN
200 if self.player_sprite.bottom < bottom_boundary:
201 self.view_bottom -= bottom_boundary - self.player_sprite.bottom
202 changed = True
203
204 if changed:
205 # Only scroll to integers. Otherwise we end up with pixels that
206 # don't line up on the screen
207 self.view_bottom = int(self.view_bottom)
208 self.view_left = int(self.view_left)
209
210 # Do the scrolling
211 arcade.set_viewport(self.view_left,
212 SCREEN_WIDTH + self.view_left,
213 self.view_bottom,
214 SCREEN_HEIGHT + self.view_bottom)
215
216
217def main():
218 """ Main method """
219 window = MyGame()
220 window.setup()
221 arcade.run()
222
223
224if __name__ == "__main__":
225 main()
Note
You can set the background color of the map by selecting “Map…Map Properties”. Then click on the three dots to pull up a color picker.
You can edit the hitbox of a tile to make ramps or platforms that only cover a portion of the rectangle in the grid.
To edit the hitbox, use the polygon tool (only) and draw a polygon around the item. You can hold down “CTRL” when positioning a point to get the exact corner of an item.