Platformer Template
Quickly get started creating your own platformer!

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:
Create a folder to hold the code. Copy this example to that folder, and call it
main.py
.Make sure the example code runs fine.
Copy the tile images you want to use to a subdirectory of the folder that holds your code.
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.
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.
Update the code to load your file instead of the map.
Test and make sure it works.
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
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()