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#

04_user_control.py - User Control#
  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