Step 8 - Use a Map Editor

../../_images/use_tileset.png

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.

../../_images/new_file.png

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.

../../_images/platforms.png

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:

../../_images/new_tileset.png

Right now, Arcade only supports a “collection of images” for a tileset. I find it convenient to embed the tileset in the map.

../../_images/new_tileset_02.png

Once you create a new tile, the button to add tiles to the tileset is hard to find. Click the wrench:

../../_images/new_tileset_03.png

Then click the ‘plus’ and add in your tiles

../../_images/new_tileset_04.png

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

Load a .tmx file from Tiled Map Editor
  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.

../../_images/collision_editor.png