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
9SCREEN_WIDTH = 800
10SCREEN_HEIGHT = 600
11SCREEN_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 MyGame(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__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
29
30 # Variable to hold our texture for our player
31 self.player_texture = arcade.load_texture(":resources:images/animated_characters/female_adventurer/femaleAdventurer_idle.png")
32
33 # Separate variable that holds the player sprite
34 self.player_sprite = arcade.Sprite(self.player_texture)
35 self.player_sprite.center_x = 64
36 self.player_sprite.center_y = 128
37
38 # SpriteList for our player
39 self.player_list = arcade.SpriteList()
40 self.player_list.append(self.player_sprite)
41
42 # SpriteList for our boxes and ground
43 # Putting our ground and box Sprites in the same SpriteList
44 # will make it easier to perform collision detection against
45 # them later on. Setting the spatial hash to True will make
46 # collision detection much faster if the objects in this
47 # SpriteList do not move.
48 self.wall_list = arcade.SpriteList(use_spatial_hash=True)
49
50 # Create the ground
51 # This shows using a loop to place multiple sprites horizontally
52 for x in range(0, 1250, 64):
53 wall = arcade.Sprite(":resources:images/tiles/grassMid.png", scale=TILE_SCALING)
54 wall.center_x = x
55 wall.center_y = 32
56 self.wall_list.append(wall)
57
58 # Put some crates on the ground
59 # This shows using a coordinate list to place sprites
60 coordinate_list = [[512, 96], [256, 96], [768, 96]]
61
62 for coordinate in coordinate_list:
63 # Add a crate on the ground
64 wall = arcade.Sprite(
65 ":resources:images/tiles/boxCrate_double.png", scale=TILE_SCALING
66 )
67 wall.position = coordinate
68 self.wall_list.append(wall)
69
70 # Create a Simple Physics Engine, this will handle moving our
71 # player as well as collisions between the player sprite and
72 # whatever SpriteList we specify for the walls.
73 self.physics_engine = arcade.PhysicsEngineSimple(
74 self.player_sprite, self.wall_list
75 )
76
77 self.background_color = arcade.csscolor.CORNFLOWER_BLUE
78
79 def setup(self):
80 """Set up the game here. Call this function to restart the game."""
81 pass
82
83 def on_draw(self):
84 """Render the screen."""
85
86 # Clear the screen to the background color
87 self.clear()
88
89 # Draw our sprites
90 self.player_list.draw()
91 self.wall_list.draw()
92
93 def on_update(self, delta_time):
94 """Movement and Game Logic"""
95
96 # Move the player using our physics engine
97 self.physics_engine.update()
98
99 def on_key_press(self, key, modifiers):
100 """Called whenever a key is pressed."""
101
102 if key == arcade.key.UP or key == arcade.key.W:
103 self.player_sprite.change_y = PLAYER_MOVEMENT_SPEED
104 elif key == arcade.key.DOWN or key == arcade.key.S:
105 self.player_sprite.change_y = -PLAYER_MOVEMENT_SPEED
106 elif key == arcade.key.LEFT or key == arcade.key.A:
107 self.player_sprite.change_x = -PLAYER_MOVEMENT_SPEED
108 elif key == arcade.key.RIGHT or key == arcade.key.D:
109 self.player_sprite.change_x = PLAYER_MOVEMENT_SPEED
110
111 def on_key_release(self, key, modifiers):
112 """Called whenever a key is released."""
113
114 if key == arcade.key.UP or key == arcade.key.W:
115 self.player_sprite.change_y = 0
116 elif key == arcade.key.DOWN or key == arcade.key.S:
117 self.player_sprite.change_y = 0
118 elif key == arcade.key.LEFT or key == arcade.key.A:
119 self.player_sprite.change_x = 0
120 elif key == arcade.key.RIGHT or key == arcade.key.D:
121 self.player_sprite.change_x = 0
122
123
124def main():
125 """Main function"""
126 window = MyGame()
127 window.setup()
128 arcade.run()
129
130
131if __name__ == "__main__":
132 main()
Run This Chapter#
python -m arcade.examples.platform_tutorial.04_user_control