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
  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