Learn
08.3 | PYGAME: THE GAME LOOP AND INPUT
01 | WHY A GAME NEEDS A LOOP
A game repeatedly accepts input, changes its world and draws a new picture. One iteration is commonly called a frame. Unlike a console question sequence, the world may continue moving while the player holds a key.
Pygame supplies tools for windows, graphics, input, timing and sound. It is a third-party package, so install it into your chosen environment. It is not a complete game: your code still defines rules, state and outcomes.
Tkinter usually owns the event loop while you provide callbacks. In a basic Pygame program, you explicitly write the loop. Understanding that difference helps you avoid trying to mix two independently blocking loops into one beginner application.
02 | PREPARE A PLAYABLE DESKTOP ENVIRONMENT
Create and activate a .venv as in Topic 7, then install the course’s Pygame range. Check the actual selected version; this is a compatibility policy, not a promise that every machine has identical setup.
Pygame needs display support for an ordinary interactive window. A normal local desktop is the intended student environment. The package does not turn an SSH console into a display.
Save your program with a descriptive name such as move_rectangle.py. Avoid pygame.py, which can shadow the real package. Installation commands belong in the terminal; import statements belong in your Python source.
TERMINAL | ACTIVE ENVIRONMENT
python -m pip install "pygame>=2.6,<3" python -m pip show pygame
03 | FOLLOW A COMPLETE SMALL GAME LOOP
This program moves a green rectangle horizontally. The window is 640 by 480 pixels. The rectangle is 40 pixels wide, so its left edge is constrained between zero and 600 to keep it visible.
Each iteration limits the target frame rate, collects pending events, reads held keys, updates position, clears and draws the scene, then presents the frame. Escape or the window close button exits.
The finally block calls pygame.quit() even if the loop raises an exception. Closing Pygame releases its resources; it does not save game state or magically exit every surrounding Python operation.
import pygame
pygame.init()
try:
screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption("Move the rectangle")
clock = pygame.time.Clock()
x, y = 300.0, 220.0
speed = 240.0
running = True
while running:
dt = min(clock.tick(60) / 1000.0, 0.05)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
running = False
if not running:
break
keys = pygame.key.get_pressed()
direction = int(keys[pygame.K_RIGHT]) - int(keys[pygame.K_LEFT])
x = max(0.0, min(600.0, x + direction * speed * dt))
screen.fill((18, 28, 38))
pygame.draw.rect(screen, (36, 152, 143), (round(x), round(y), 40, 40))
pygame.display.flip()
finally:
pygame.quit()04 | SURFACES AND SCREEN COORDINATES
A Surface is a drawable image buffer. display.set_mode creates the display surface, and fill replaces its contents with a colour. Colours here are RGB tuples with red, green and blue components from zero to 255.
The origin is at the top-left. x increases to the right; y increases downwards. A rectangle at (100, 50, 40, 40) starts one hundred pixels right and fifty pixels down, then spans forty pixels in each dimension.
Drawing changes the buffer; display.flip presents the updated frame. If you draw without presenting, the visible window may not show those changes. Clearing each frame prevents old positions leaving unwanted trails.
05 | QUEUED EVENTS AND HELD INPUT
pygame.event.get() retrieves pending events from the event queue. A KEYDOWN event represents a press occurrence; QUIT represents a close request. Process the queue regularly so the window remains responsive.
pygame.key.get_pressed() gives the current pressed-key state, useful for continuous movement while a key is held. It is a snapshot, not a record of every brief press between checks.
Use KEYDOWN for actions such as start or pause, where holding the key should not trigger a new transition every frame. Use pressed-key state for left/right movement. A quick tap may be better handled as an event if every press matters.
06 | DELTA TIME MAKES SPEED MEAN SOMETHING
Delta time, dt, is the elapsed duration used for one update. Clock.tick returns milliseconds; divide by 1000 to obtain seconds. If speed is 240 pixels per second and dt is 0.02 seconds, the displacement is 4.8 pixels.
Moving by a fixed four pixels each frame would be faster at sixty frames per second than at thirty. Moving by speed * dt aims for the same distance over the same elapsed update time.
Store position as a float so fractional movement is retained, then round when drawing. If you store only integer positions, small per-frame motion can disappear or become biased by repeated rounding.
speed = 240.0
x = 100.0
dt = 0.02
x += speed * dt
print(f"New x: {x:.1f}")EXPECTED OUTPUT
New x: 104.8
07 | LIMIT A FRAME WITHOUT CLAIMING PERFECT TIMING
clock.tick(60) attempts to limit the loop to around sixty frames per second. It does not guarantee sixty on a busy machine. Read the returned duration instead of treating the target as measured reality.
The example caps dt at 0.05 seconds to avoid one huge movement after a stall. This reduces jumps, but discards some elapsed time: a severely lagging simulation can run slowly rather than catching up.
For fast objects and exact simulation timing, more advanced games may use fixed update steps and swept collision detection. A capped step is a beginner tradeoff, not proof that every possible obstacle collision is detected.
08 | EXPLORE A MOVEMENT FRAME
Choose left, stationary or right, then a duration. This fixed model updates x from its current floating-point position at 240 pixels per second and clamps it to a small illustrated field. It does not execute Pygame.
09 | ONE FRAME, CLEAR RESPONSIBILITIES
Separate input collection, model update and drawing into functions as the game grows. Updating determines where the player is; drawing shows that result. Do not increase the score simply because a label is redrawn.
Keep a single intentional clock.tick call for the frame, rather than independently delaying each moving object. Every object in the same update should receive the intended duration.
Window close and Escape must be handled in title, pause and game-over states too. A player should not need to lose all lives before being allowed to leave.
10 | TEST TIME, INPUT AND BOUNDARIES
Run left, right and both together. In this example opposite held inputs cancel. Test the exact left and right edges, a quick Escape press and closing through the operating system.
Compare two updates of 0.01 seconds with one update of 0.02 seconds for simple unconstrained movement. They should cover the same distance. Collisions and boundaries can introduce additional ordering decisions, which belong in the rules.
Predict a movement calculation on paper, then check the printed or displayed position. Testing the arithmetic helps you distinguish timing mistakes from drawing mistakes.
OFFICIAL REFERENCE
Use the worked examples first, then consult the official documentation to check widget, input or drawing options. Match it to the version selected in your project environment.
PUT YOUR SKILLS TO WORK
Use the Section 8 challenges to practise these ideas independently. Work in your Python editor, predict results and keep a short test record.
Section 8 challenges Python course navigation