Adding Player Movement

Introduction: From Watching to Playing

Welcome back to Pygame Foundations for your first game! We have reached the fourth and final unit of this course, and our scene is already alive: a dark blue 800 × 600 display redraws itself 60 times per second, while a red obstacle Rect slides down the screen and wraps back to the top as soon as it is fully below the bottom edge.

Something important is still missing, though. Everything on the screen moves on its own schedule, and the person sitting at the keyboard is just a spectator. A game becomes a game the moment input changes what happens on the screen.

By the end of this lesson, we will have a green player rectangle that glides left, right, up, and down while the arrow keys are held down, and firmly refuses to walk off the edge of the display. To keep the final scene focused on its two gameplay objects, we will intentionally remove the decorative yellow circle introduced in Unit 2. Three ingredients get us to the interactive result:

  1. A pygame.Rect that represents the player;
  2. A per-frame reading of which keys are currently held;
  3. A boundary check that keeps the player inside the display.

Giving the Player a Rect of Its Own

Two Kinds of Keyboard Input: Events vs. State

Pygame offers two different ways to learn about the keyboard, and picking the right one matters a lot here. The first is the event queue we already use for pygame.QUIT: a list of things that happened since the last frame. Pressing a key adds a pygame.KEYDOWN event to that queue, and releasing it adds a pygame.KEYUP.

That model is perfect for one-off actions, but it is not the best fit for continuous movement. What we actually want is state: not "was a key pressed?" but "is a key down right now?" That is what pygame.key.get_pressed() gives us. It returns a snapshot of every key on the keyboard, telling us which ones are currently held. We call it fresh each frame, right after the event loop and before any drawing code.

Turning Key State into Movement

The Problem: The Player Can Escape

Clamping the Player Inside the Display

The Full Interactive Scene

Putting every piece together, here is the complete program. Notice that YELLOW and the circle draw call are no longer present; the circle was decorative, and this final scene intentionally focuses on the controllable player and falling obstacle.

Python
import asyncio
import pygame

WIDTH, HEIGHT = 800, 600
FPS = 60
DARK_BLUE = (20, 28, 48)
GREEN = (87, 215, 170)
RED = (220, 50, 50)


async def main():
    pygame.init()
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption("Dodge Game")
    clock = pygame.time.Clock()

    player = pygame.Rect(375, 540, 50, 40)
    player_speed = 5

    obstacle = pygame.Rect(375, 0, 40, 40)
    obstacle_speed = 4

    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            player.x -= player_speed
        if keys[pygame.K_RIGHT]:
            player.x += player_speed
        if keys[pygame.K_UP]:
            player.y -= player_speed
        if keys[pygame.K_DOWN]:
            player.y += player_speed
        player.clamp_ip(screen.get_rect())

        obstacle.y += obstacle_speed
        if obstacle.top >= HEIGHT:
            obstacle.y = 0

        screen.fill(DARK_BLUE)
        pygame.draw.rect(screen, GREEN, player)
        pygame.draw.rect(screen, RED, obstacle)
        pygame.display.flip()
        clock.tick(FPS)
        await asyncio.sleep(0)

    pygame.quit()


asyncio.run(main())

Each trip through the loop follows the same rhythm: handle events, read the keys, move and clamp the player, move and wrap the obstacle, then fill the background, draw both rectangles, flip the display, tick the clock, and yield control with await asyncio.sleep(0). Notice the clean split that has emerged: all state updates happen first, all drawing happens second. One consequence is worth calling out: the green and red rectangles can now slide right through each other because nothing in this code is watching for that overlap yet.

Conclusion and Next Steps

Excellent work! Four ideas carried this lesson: a Rect is the natural home for any movable game object, pygame.key.get_pressed() reports continuous key state instead of one-time events, four separate if blocks unlock diagonal movement, and clamp_ip() enforces all four boundaries in a single line.

That also wraps up this course. In four short units, we went from an empty display to a smooth, browser-ready, fully interactive scene: a game loop running at a steady 60 FPS, shapes drawn from colors and coordinates, an obstacle animating and wrapping on its own, and now a player under our direct control. In the next course, that harmless pass-through between the player and the obstacle becomes the heart of the game: many obstacles at once, collision detection, a score, and sound effects.

Now let's put the arrow keys to work: the upcoming practice tasks will have us turn the green rectangle into a real player Rect, wire up movement in all four directions, and lock it inside the display, one step at a time.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal