Step 4 - Add User Control
Now we’ve got a character and a world for them to exist in, but what fun is a game if you can’t control the character and move around? In this Chapter we’ll explore adding keyboard input in Arcade.
First, at the top of our program, we’ll want to add a new constant that controls how many pixels per update our character travels:
PLAYER_MOVEMENT_SPEED = 5
In order to handle the keyboard input, we need to add to add two new functions to our
Window class, on_key_press
and on_key_release
. These functions will automatically
be called by Arcade whenever a key on the keyboard is pressed or released. Inside these
functions, based on the key that was pressed or released, we will move our character.
def on_key_press(self, key, modifiers):
"""Called whenever a key is pressed."""
if key == arcade.key.UP or key == arcade.key.W:
self.player_sprite.change_y = PLAYER_MOVEMENT_SPEED
elif key == arcade.key.DOWN or key == arcade.key.S:
self.player_sprite.change_y = -PLAYER_MOVEMENT_SPEED
elif key == arcade.key.LEFT or key == arcade.key.A:
self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
elif key == arcade.key.RIGHT or key == arcade.key.D:
self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
def on_key_release(self, key, modifiers):
"""Called whenever a key is released."""
if key == arcade.key.UP or key == arcade.key.W:
self.player_sprite.change_y = 0
elif key == arcade.key.DOWN or key == arcade.key.S:
self.player_sprite.change_y = 0
elif key == arcade.key.LEFT or key == arcade.key.A:
self.player_sprite.change_x = 0
elif key == arcade.key.RIGHT or key == arcade.key.D:
self.player_sprite.change_x = 0
In these boxes, we are modifying the change_x
and change_y
attributes on our
player Sprite. Changing these values will not actually perform the move on the Sprite.
In order to apply this change, we need to create a physics engine with our Sprite,
and update the physics engine every frame. The physics engine will then be responsible
for actually moving the sprite.
The reason we give the physics engine this responsibility instead of doing it ourselves, is so that we can let the physics engine do collision detections, and allow/disallow a movement based on the result. In later chapters, we’ll use more advanced physics engines which can do things like allow jumping with gravity, or climbing on ladders for example.
Note
This method of tracking the speed to the key the player presses is simple, but isn’t perfect. If the player hits both left and right keys at the same time, then lets off the left one, we expect the player to move right. This method won’t support that. If you want a slightly more complex method that does, see Better Move By Keyboard.
Let’s create a simple physics engine in our __init__
function. We will do this by passing
it our player sprite, and the SpriteList containing our walls.
self.physics_engine = arcade.PhysicsEngineSimple(
self.player_sprite, self.wall_list
)
Now we have a physics engine, but we still need to update it every frame. In order to do this
we will add a new function to our Window class, called on_update
. This function is similar to
on_draw
, it will be called by Arcade at a default of 60 times per second. It will also give
us a delta_time
parameter that tells the amount of time between the last call and the current one.
This value will be used in some calculations in future chapters. Within this function, we will update
our physics engine. Which will process collision detections and move our player based on it’s change_x
and change_y
values.
def on_update(self, delta_time):
"""Movement and Game Logic"""
self.physics_engine.update()
At this point you should be able to run the game, and move the character around with the keyboard. If the physics engine is working properly, the character should not be able to move through the ground or the boxes.
For more information about the physics engine we are using in this tutorial,
see arcade.PhysicsEngineSimple
.
Note
It is possible to have multiple physics engines, one per moving sprite. These are very simple, but easy physics engines. See Pymunk Platformer for a more advanced physics engine.
Note
If you want to see how the collisions are checked, try using the draw_hit_boxes()
function
on the player and wall SpriteLists inside the on_draw
function. This will show you what the
hitboxes that the physics engine uses look like.
Source Code
1"""
2Platformer Game
3
4python -m arcade.examples.platform_tutorial.04_user_control
5"""
6import arcade
7
8# Constants
9WINDOW_WIDTH = 1280
10WINDOW_HEIGHT = 720
11WINDOW_TITLE = "Platformer"
12
13# Constants used to scale our sprites from their original size
14TILE_SCALING = 0.5
15
16# Movement speed of player, in pixels per frame
17PLAYER_MOVEMENT_SPEED = 5
18
19
20class GameView(arcade.Window):
21 """
22 Main application class.
23 """
24
25 def __init__(self):
26
27 # Call the parent class and set up the window
28 super().__init__(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE)
29
30 # Variable to hold our texture for our player
31 self.player_texture = arcade.load_texture(
32 ":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png"
33 )
34
35 # Separate variable that holds the player sprite
36 self.player_sprite = arcade.Sprite(self.player_texture)
37 self.player_sprite.center_x = 64
38 self.player_sprite.center_y = 128
39
40 # SpriteList for our player
41 self.player_list = arcade.SpriteList()
42 self.player_list.append(self.player_sprite)
43
44 # SpriteList for our boxes and ground
45 # Putting our ground and box Sprites in the same SpriteList
46 # will make it easier to perform collision detection against
47 # them later on. Setting the spatial hash to True will make
48 # collision detection much faster if the objects in this
49 # SpriteList do not move.
50 self.wall_list = arcade.SpriteList(use_spatial_hash=True)
51
52 # Create the ground
53 # This shows using a loop to place multiple sprites horizontally
54 for x in range(0, 1250, 64):
55 wall = arcade.Sprite(":resources:images/tiles/grassMid.png", scale=TILE_SCALING)
56 wall.center_x = x
57 wall.center_y = 32
58 self.wall_list.append(wall)
59
60 # Put some crates on the ground
61 # This shows using a coordinate list to place sprites
62 coordinate_list = [[512, 96], [256, 96], [768, 96]]
63
64 for coordinate in coordinate_list:
65 # Add a crate on the ground
66 wall = arcade.Sprite(
67 ":resources:images/tiles/boxCrate_double.png", scale=TILE_SCALING
68 )
69 wall.position = coordinate
70 self.wall_list.append(wall)
71
72 # Create a Simple Physics Engine, this will handle moving our
73 # player as well as collisions between the player sprite and
74 # whatever SpriteList we specify for the walls.
75 self.physics_engine = arcade.PhysicsEngineSimple(
76 self.player_sprite, self.wall_list
77 )
78
79 self.background_color = arcade.csscolor.CORNFLOWER_BLUE
80
81 def setup(self):
82 """Set up the game here. Call this function to restart the game."""
83 pass
84
85 def on_draw(self):
86 """Render the screen."""
87
88 # Clear the screen to the background color
89 self.clear()
90
91 # Draw our sprites
92 self.player_list.draw()
93 self.wall_list.draw()
94
95 def on_update(self, delta_time):
96 """Movement and Game Logic"""
97
98 # Move the player using our physics engine
99 self.physics_engine.update()
100
101 def on_key_press(self, key, modifiers):
102 """Called whenever a key is pressed."""
103
104 if key == arcade.key.UP or key == arcade.key.W:
105 self.player_sprite.change_y = PLAYER_MOVEMENT_SPEED
106 elif key == arcade.key.DOWN or key == arcade.key.S:
107 self.player_sprite.change_y = -PLAYER_MOVEMENT_SPEED
108 elif key == arcade.key.LEFT or key == arcade.key.A:
109 self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
110 elif key == arcade.key.RIGHT or key == arcade.key.D:
111 self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
112
113 def on_key_release(self, key, modifiers):
114 """Called whenever a key is released."""
115
116 if key == arcade.key.UP or key == arcade.key.W:
117 self.player_sprite.change_y = 0
118 elif key == arcade.key.DOWN or key == arcade.key.S:
119 self.player_sprite.change_y = 0
120 elif key == arcade.key.LEFT or key == arcade.key.A:
121 self.player_sprite.change_x = 0
122 elif key == arcade.key.RIGHT or key == arcade.key.D:
123 self.player_sprite.change_x = 0
124
125
126def main():
127 """Main function"""
128 window = GameView()
129 window.setup()
130 arcade.run()
131
132
133if __name__ == "__main__":
134 main()
Run This Chapter
python -m arcade.examples.platform_tutorial.04_user_control