The Game Loop and the Structure of a Game

Game Development with Pygame Course

Felipe A. Moreno-Vera

In this module you will learn

  • What the Game Loop is and why it is the heart of every game
  • The three phases that repeat every frame
  • How to control FPS with Pygame’s Clock
  • How to handle input events (keyboard and mouse)
  • The minimal structure of a functional game

1. What is the Game Loop?

Every game, no matter how simple or complex, runs on a cycle that repeats endlessly while the game is active. This cycle is called the Game Loop.

1. What is the Game Loop?

Every game, no matter how simple or complex, runs on a cycle that repeats endlessly while the game is active. This cycle is called the Game Loop.

┌────────────────────────────────────────────────────┐
│                   GAME LOOP                        │
│                                                    │
│   ┌──────────┐   ┌──────────┐   ┌──────────┐       │
│   │  INPUT   │ → │  UPDATE  │ → │  RENDER  │ ──┐   │
│   └──────────┘   └──────────┘   └──────────┘   │   │
│         ↑                                      │   │
│         └──────────────────────────────────────┘   │
│                                                    │
│   Repeats ~60 times per second (60 FPS)            │
└────────────────────────────────────────────────────┘

1. What is the Game Loop?

Every game, no matter how simple or complex, runs on a cycle that repeats endlessly while the game is active. This cycle is called the Game Loop.

┌────────────────────────────────────────────────────┐
│                   GAME LOOP                        │
│                                                    │
│   ┌──────────┐   ┌──────────┐   ┌──────────┐       │
│   │  INPUT   │ → │  UPDATE  │ → │  RENDER  │ ──┐   │
│   └──────────┘   └──────────┘   └──────────┘   │   │
│         ↑                                      │   │
│         └──────────────────────────────────────┘   │
│                                                    │
│   Repeats ~60 times per second (60 FPS)            │
└────────────────────────────────────────────────────┘

Each iteration of the loop is called a frame and produces one image on screen. At 60 FPS, it creates the illusion of smooth motion — just like a flipbook.

2. The three phases of the Game Loop

Phase 1 — INPUT

Detects what the player is doing:

  • Keys pressed or released
  • Mouse movement and clicks
  • The window close event (QUIT)

Warning

Critical rule: If you don’t process events every frame, the queue fills up and the window freezes.

2. The three phases of the Game Loop

Phase 2 — UPDATE

All game logic happens here:

  • Moving characters and enemies
  • Detecting collisions
  • Updating score and lives
  • Applying basic physics (gravity, velocity)

2. The three phases of the Game Loop

Phase 3 — RENDER

Draws the current state on screen:

  1. Clear the screen with fill()
  2. Draw all objects
  3. Display the frame with pygame.display.flip()

Note

Why clear before drawing? Without clearing, objects leave “trails” on screen because previous frames remain visible.

3. Key technical concepts — FPS

FPS indicates how many times per second the Game Loop executes.

FPS Perception
< 15 Noticeably choppy
30 Playable, standard on older consoles
60 Smooth, current standard
120+ Very smooth, for high-refresh-rate monitors

Without FPS control, the game runs as fast as the CPU allows — the same game would run at completely different speeds on different hardware.

Double Buffering

Pygame uses two image buffers:

  • Back buffer: where you draw (not visible)
  • Front buffer: what the user sees

pygame.display.flip() swaps both buffers. This prevents tearing (seeing a half-drawn frame).

Two ways to capture input

Method When it fires Typical use
pygame.event.get() Once when pressed Jump, shoot, open menu
pygame.key.get_pressed() Every frame while held Move character

Two ways to capture input

Common mistake: using KEYDOWN for movement. The character moves one step, stops, and only then starts moving smoothly (the delay comes from the OS). The solution is get_pressed() for continuous movement.

4. Pygame’s coordinate system

The origin (0, 0) is at the top-left corner, and the Y axis grows downward.

(0,0) ──────────────────→ X+
  │
  │     (100, 50) ●
  │
  │                (400, 300) ●
  ↓
 Y+

4. Pygame’s coordinate system

The origin (0, 0) is at the top-left corner, and the Y axis grows downward.

(0,0) ──────────────────→ X+
  │
  │     (100, 50) ●
  │
  │                (400, 300) ●
  ↓
 Y+

Important

To move an object upward on screen, you must subtract from Y. This differs from conventional math where Y grows upward.

5. Code structure

Every Pygame game follows this structure:

import pygame
import sys

# ── BLOCK 1: Initialization ───────────────────────────────
pygame.init()

WIDTH, HEIGHT = 800, 600
FPS   = 60
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
BLUE  = (50, 120, 220)

screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Module 1 — Game Loop")
clock = pygame.time.Clock()

Code structure (cont.)

# ── BLOCK 2: Initial state ────────────────────────────────
x, y = WIDTH // 2, HEIGHT // 2
speed = 4

# ── BLOCK 3: Game Loop ────────────────────────────────────
while True:

    # PHASE 1 — INPUT
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:  x -= speed
    if keys[pygame.K_RIGHT]: x += speed
    if keys[pygame.K_UP]:    y -= speed
    if keys[pygame.K_DOWN]:  y += speed

    # PHASE 2 — UPDATE
    x = max(0, min(x, WIDTH - 40))
    y = max(0, min(y, HEIGHT - 40))

    # PHASE 3 — RENDER
    screen.fill(BLACK)
    pygame.draw.rect(screen, BLUE, (x, y, 40, 40))
    pygame.display.flip()

    clock.tick(FPS)

6. Available events in Pygame

Event Description
pygame.QUIT Click on the window’s X button
pygame.KEYDOWN Key just pressed
pygame.KEYUP Key released
pygame.MOUSEBUTTONDOWN Mouse button pressed
pygame.MOUSEBUTTONUP Mouse button released
pygame.MOUSEMOTION Mouse moved (includes position and delta)
pygame.MOUSEWHEEL Mouse scroll

Most commonly used keys in games

Category Keys
Movement K_LEFT, K_RIGHT, K_UP, K_DOWN
Actions K_SPACE, K_RETURN, K_ESCAPE
Letters K_aK_z
Numbers K_0K_9
Modifiers K_LSHIFT, K_RSHIFT, K_LCTRL, K_RCTRL

All are constants from the pygame module.

7. Input: event vs get_pressed

KEYDOWN (event): fires only once when pressed

Frame 0: KEYDOWN detected → character moves 1 time
Frame 1: no event (key still held but nothing happens)
Frame 2: no event
...

get_pressed(): fires EVERY frame while the key is held

Frame 0: get_pressed → character moves (smooth movement)
Frame 1: get_pressed → character moves (smooth movement)
Frame 2: get_pressed → character moves (smooth movement)
...

Tip

Use KEYDOWN for single actions (jump, shoot) and get_pressed() for continuous movement.

8. The Clock and FPS

Pygame’s Clock limits how fast the game loop runs.

import time

def simulate_clock_tick(target_fps, frames=5):
    """
    Simulates how clock.tick(fps) controls the loop speed.
    In Pygame, clock.tick(60) blocks until 1/60 seconds have passed.
    """
    frame_duration = 1.0 / target_fps  # seconds per frame

    for i in range(frames):
        start = time.time()

        time.sleep(0.002)  # Simulates 2ms of game work

        work_time = time.time() - start
        wait = frame_duration - work_time
        if wait > 0:
            time.sleep(wait)

        total_time = time.time() - start
        real_fps = 1.0 / total_time
        print(f"Frame {i+1}: work={work_time*1000:.1f}ms, "
              f"wait={wait*1000:.1f}ms, real FPS≈{real_fps:.0f}")

simulate_clock_tick(target_fps=30, frames=4)

9. Coordinate system — exercise

Calculate the coordinates to place objects at different positions on screen.

WIDTH  = 800
HEIGHT = 600
SIZE   = 40   # Object size

positions = {
    'Top-left corner':      (0, 0),
    'Top-right corner':     (WIDTH - SIZE, 0),
    'Bottom-left corner':   (0, HEIGHT - SIZE),
    'Bottom-right corner':  (WIDTH - SIZE, HEIGHT - SIZE),
    'Exact center':         (WIDTH // 2 - SIZE // 2, HEIGHT // 2 - SIZE // 2),
    'Horizontal center, top': (WIDTH // 2 - SIZE // 2, 20),
}

for name, coords in positions.items():
    print(f"  {name:<35}{coords}")

Summary

Key concepts:

  • The Game Loop is the heart of every game
  • Three phases: INPUT → UPDATE → RENDER
  • FPS controls the loop speed
  • Double Buffering prevents tearing

Input:

  • event.get() → single actions
  • get_pressed() → continuous movement

Coordinates:

  • Origin at the top-left corner
  • Y axis grows downward