Platformer Template

Quickly get started creating your own platformer!

Screenshot of platformer template

About This Template

This is a template to get you started coding a side-scrolling platformer as quickly as possible. I recommend the following steps:

  1. Create a folder to hold the code. Copy this example to that folder, and call it main.py.

  2. Make sure the example code runs fine.

  3. Copy the tile images you want to use to a subdirectory of the folder that holds your code.

  4. Create a very simple Tiled Map using the Tiled Map Editor. I suggest just creating a floor and nothing else. For more detail on how to create a tiled map, see Step 9 - Adding Sound.

  5. Save the file to the same directory as your source code. If you create a separate tileset, also save it to the same directory as your code.

  6. Update the code to load your file instead of the map.

  7. Test and make sure it works.

  8. Now that you are sure things work, make your own platformer!

Warning

Watch the directories!

One of the most frequent mistakes is to save maps and tile sets to a directory that isn’t the same directory as your code. Or to not have the tile images in that folder. If everything isn’t in the same folder (or a subfolder of that) it is hard to package it up later.

For more detailed instructions, see the tutorial Simple Platformer.

Source Code

template_platformer.py
  1"""
  2Platformer Template
  3
  4If Python and Arcade are installed, this example can be run from the command line with:
  5python -m arcade.examples.template_platformer
  6"""
  7import arcade
  8from arcade.types import Color
  9
 10# --- Constants
 11WINDOW_TITLE = "Platformer"
 12WINDOW_WIDTH = 1280
 13WINDOW_HEIGHT = 720
 14
 15# Constants used to scale our sprites from their original size
 16CHARACTER_SCALING = 1
 17TILE_SCALING = 0.5
 18COIN_SCALING = 0.5
 19SPRITE_PIXEL_SIZE = 128
 20GRID_PIXEL_SIZE = SPRITE_PIXEL_SIZE * TILE_SCALING
 21
 22# Movement speed of player, in pixels per frame
 23PLAYER_MOVEMENT_SPEED = 10
 24GRAVITY = 1
 25PLAYER_JUMP_SPEED = 20
 26
 27# Camera constants
 28FOLLOW_DECAY_CONST = 0.3  # get within 1% of the target position within 2 seconds
 29
 30
 31class GameView(arcade.View):
 32    """
 33    Main application class.
 34    """
 35
 36    def __init__(self):
 37        super().__init__()
 38
 39        # A Camera that can be used for scrolling the screen
 40        self.camera_sprites = arcade.camera.Camera2D()
 41
 42        # A rectangle that is used to constrain the camera's position.
 43        # we update it when we load the tilemap
 44        self.camera_bounds = self.window.rect
 45
 46        # A non-scrolling camera that can be used to draw GUI elements
 47        self.camera_gui = arcade.camera.Camera2D()
 48
 49        # The scene which helps draw multiple spritelists in order.
 50        self.scene = self.create_scene()
 51
 52        # Set up the player, specifically placing it at these coordinates.
 53        self.player_sprite = arcade.Sprite(
 54            ":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png",
 55            scale=CHARACTER_SCALING,
 56        )
 57
 58        # Our physics engine
 59        self.physics_engine = arcade.PhysicsEnginePlatformer(
 60            self.player_sprite, gravity_constant=GRAVITY, walls=self.scene["Platforms"]
 61        )
 62
 63        # Keep track of the score
 64        self.score = 0
 65
 66        # What key is pressed down?
 67        self.left_key_down = False
 68        self.right_key_down = False
 69
 70        # Text object to display the score
 71        self.score_display = arcade.Text(
 72            "Score: 0",
 73            x=10,
 74            y=10,
 75            color=arcade.csscolor.WHITE,
 76            font_size=18,
 77        )
 78
 79    def create_scene(self) -> arcade.Scene:
 80        """Load the tilemap and create the scene object."""
 81        # Our TileMap Object
 82        # Layer specific options are defined based on Layer names in a dictionary
 83        # Doing this will make the SpriteList for the platforms layer
 84        # use spatial hashing for collision detection.
 85        layer_options = {
 86            "Platforms": {
 87                "use_spatial_hash": True,
 88            },
 89        }
 90        tile_map = arcade.load_tilemap(
 91            ":resources:tiled_maps/map.json",
 92            scaling=TILE_SCALING,
 93            layer_options=layer_options,
 94        )
 95
 96        # Set the window background color to the same as the map if it has one
 97        if tile_map.background_color:
 98            self.window.background_color = Color.from_iterable(tile_map.background_color)
 99
100        # Use the tilemap's size to correctly set the camera's bounds.
101        # Because how how shallow the map is we don't offset the bounds height
102        self.camera_bounds = arcade.LRBT(
103            self.window.width/2.0,
104            tile_map.width * GRID_PIXEL_SIZE - self.window.width/2.0,
105            self.window.height/2.0,
106            tile_map.height * GRID_PIXEL_SIZE
107        )
108
109
110        # Our Scene Object
111        # Initialize Scene with our TileMap, this will automatically add all layers
112        # from the map as SpriteLists in the scene in the proper order.
113        return arcade.Scene.from_tilemap(tile_map)
114
115    def reset(self):
116        """Reset the game to the initial state."""
117        self.score = 0
118        # Load a fresh scene to get the coins back
119        self.scene = self.create_scene()
120
121        # Move the player to start position
122        self.player_sprite.position = (128, 128)
123        # Add the player to the scene
124        self.scene.add_sprite("Player", self.player_sprite)
125
126    def on_draw(self):
127        """Render the screen."""
128
129        # Clear the screen to the background color
130        self.clear()
131
132        # Draw the map with the sprite camera
133        with self.camera_sprites.activate():
134            # Draw our Scene
135            # Note, if you a want pixelated look, add pixelated=True to the parameters
136            self.scene.draw()
137
138        # Draw the score with the gui camera
139        with self.camera_gui.activate():
140            # Draw our score on the screen. The camera keeps it in place.
141            self.score_display.text = f"Score: {self.score}"
142            self.score_display.draw()
143
144    def update_player_speed(self):
145        # Calculate speed based on the keys pressed
146        self.player_sprite.change_x = 0
147
148        if self.left_key_down and not self.right_key_down:
149            self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
150        elif self.right_key_down and not self.left_key_down:
151            self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
152
153    def on_key_press(self, key, modifiers):
154        """Called whenever a key is pressed."""
155
156        # Jump
157        if key == arcade.key.UP or key == arcade.key.W:
158            if self.physics_engine.can_jump():
159                self.player_sprite.change_y = PLAYER_JUMP_SPEED
160
161        # Left
162        elif key == arcade.key.LEFT or key == arcade.key.A:
163            self.left_key_down = True
164            self.update_player_speed()
165
166        # Right
167        elif key == arcade.key.RIGHT or key == arcade.key.D:
168            self.right_key_down = True
169            self.update_player_speed()
170
171    def on_key_release(self, key, modifiers):
172        """Called when the user releases a key."""
173        if key == arcade.key.LEFT or key == arcade.key.A:
174            self.left_key_down = False
175            self.update_player_speed()
176        elif key == arcade.key.RIGHT or key == arcade.key.D:
177            self.right_key_down = False
178            self.update_player_speed()
179
180    def center_camera_to_player(self):
181        # Move the camera to center on the player
182        self.camera_sprites.position = arcade.math.smerp_2d(
183            self.camera_sprites.position,
184            self.player_sprite.position,
185            self.window.delta_time,
186            FOLLOW_DECAY_CONST,
187        )
188
189        # Constrain the camera's position to the camera bounds.
190        self.camera_sprites.view_data.position = arcade.camera.grips.constrain_xy(
191            self.camera_sprites.view_data, self.camera_bounds
192        )
193
194    def on_update(self, delta_time: float):
195        """Movement and game logic"""
196
197        # Move the player with the physics engine
198        self.physics_engine.update()
199
200        # See if we hit any coins
201        coin_hit_list = arcade.check_for_collision_with_list(
202            self.player_sprite, self.scene["Coins"]
203        )
204
205        # Loop through each coin we hit (if any) and remove it
206        for coin in coin_hit_list:
207            # Remove the coin
208            coin.remove_from_sprite_lists()
209            # Add one to the score
210            self.score += 1
211
212        # Position the camera
213        self.center_camera_to_player()
214
215    def on_resize(self, width: int, height: int):
216        """ Resize window """
217        super().on_resize(width, height)
218        # Update the cameras to match the new window size
219        self.camera_sprites.match_window()
220        # The position argument keeps `0, 0` in the bottom left corner.
221        self.camera_gui.match_window(position=True)
222
223
224def main():
225    """Main function"""
226    window = arcade.Window(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE)
227    game = GameView()
228    game.reset()
229
230    window.show_view(game)
231    arcade.run()
232
233
234if __name__ == "__main__":
235    main()