Day 4 — Pygame (Day 1)
Game loop · Coordinates · Drawing · Keyboard events · Movement
⬇ Today's slides (PDF)Schedule
| Time | Activity |
|---|---|
| 0:00 – 0:45 | Lecture: game loop, coordinate system, events, drawing |
| 0:45 – 1:30 | Guided: add a player sprite that moves with arrow keys |
| 1:30 – 1:40 | Break |
| 1:40 – 3:00 | Open project: start building your game |
Install
pip install pygame
The Starter Template
Copy this into a new file called game.py. It opens a window, handles the quit button, and runs at 60 frames per second. You build the rest.
Notice the main loop stays tiny — it only calls three functions: handle_events(), update(), and draw(). Each one has a single job, so when something breaks you know exactly where to look: nothing moving? check update(). Nothing showing up? check draw().
import pygame
import sys
import random
pygame.init()
# --- Window setup ---
WIDTH = 800
HEIGHT = 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("My Game")
clock = pygame.time.Clock()
# --- Colors (Red, Green, Blue) ---
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (220, 50, 50)
GREEN = (50, 200, 50)
BLUE = (50, 100, 220)
YELLOW = (240, 200, 0)
# --- Font ---
font = pygame.font.SysFont(None, 36)
# --- Your variables here ---
player_x = WIDTH // 2
player_y = HEIGHT - 80
player_speed = 5
score = 0
# ============================================================
# FUNCTIONS — the main loop below only calls these three.
# Splitting the work up keeps each job in one place, so a bug
# is easy to track down.
# ============================================================
def handle_events():
"""Deal with one-time events like the quit button.
Returns False when the player closes the window, and True
otherwise. The main loop uses this to know when to stop.
"""
for event in pygame.event.get():
if event.type == pygame.QUIT: # the window's X was clicked
return False
return True
def update():
"""Move things and change the score — the game's 'thinking'.
Runs once every frame. Reads which keys are held down and
moves the player. Add your collision checks and scoring here.
"""
# List every outside variable you CHANGE here.
# (Add 'score' to this line once you start scoring points.)
global player_x
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and player_x > 0:
player_x -= player_speed
if keys[pygame.K_RIGHT] and player_x < WIDTH - 50:
player_x += player_speed
def draw():
"""Draw everything for this frame, then show it on screen.
Always fill the background first (this erases the last frame),
then draw your objects, then flip the display to show it.
"""
screen.fill(BLACK) # erase the previous frame
# Draw player (a rectangle for now — swap in a sprite later)
pygame.draw.rect(screen, GREEN, (player_x, player_y, 50, 50))
# Draw score
score_text = font.render(f"Score: {score}", True, WHITE)
screen.blit(score_text, (10, 10))
pygame.display.flip() # show the frame we just drew
# ============================================================
# MAIN GAME LOOP — small on purpose: it just calls functions.
# Runs about 60 times per second.
# ============================================================
running = True
while running:
running = handle_events() # 1. input & quit button
update() # 2. move things, update the score
draw() # 3. put everything on the screen
clock.tick(60) # wait so the game runs at 60 FPS
pygame.quit()
sys.exit()
Using Sprites (Images)
The sprites folder on your computer has free images from Kenney.nl. Ask your instructor where it's located.
# Load an image — the file must be in the same folder as game.py (or give the full path)
player_img = pygame.image.load("sprites/player.png")
# scale() resizes the image to exactly (width, height) in pixels
player_img = pygame.transform.scale(player_img, (50, 50))
# blit() "stamps" the image onto the screen at position (x, y)
# Do this inside draw(), after screen.fill()
screen.blit(player_img, (player_x, player_y))
Default Project — Catch the Falling Objects
If you have your own game idea, go for it! If you want a starting point, build this:
- A player moves left and right at the bottom of the screen
- Objects fall from the top at random x positions
- Catching an object scores a point and spawns a new one
- Missing an object ends the game
score or obj_y), add that variable to a global line at the top of the function. Just reading a variable needs no global. Forgetting this is the #1 cause of "my score won't change." You're filling in the same update() and draw() from the template — not writing new ones.
Adding a Falling Object
# --- With your other variables, up near the top (before the functions) ---
obj_x = random.randint(0, WIDTH - 30) # random x so it doesn't always fall in the same spot
obj_y = 0 # start at the top (y=0 is the top of the window in pygame)
obj_speed = 4 # how many pixels it moves down per frame
# --- Inside update(): the object moves, and we check for a miss ---
def update():
global player_x, obj_y # every outside variable we CHANGE goes on this line
# ...your player movement from the template stays here...
obj_y += obj_speed # move the object down each frame
if obj_y > HEIGHT: # fell past the bottom of the window?
print("You missed it!") # replace with a lives / game-over system later
# --- Inside draw(): stamp the object on the screen (after screen.fill) ---
def draw():
# ...fill the screen and draw the player first...
pygame.draw.rect(screen, RED, (obj_x, obj_y, 30, 30)) # a 30x30 red square
Collision Detection
# This all goes inside update(). pygame.Rect makes a rectangle from
# (left x, top y, width, height) so we can test whether two overlap.
def update():
global score, obj_x, obj_y # we change all three below, so list them here
player_rect = pygame.Rect(player_x, player_y, 50, 50)
obj_rect = pygame.Rect(obj_x, obj_y, 30, 30)
# colliderect() is True when the two rectangles overlap — a catch!
if player_rect.colliderect(obj_rect):
score += 1 # award a point
obj_x = random.randint(0, WIDTH - 30) # send the object back up...
obj_y = 0 # ...at a new random x
Choose Your Game — Pick Any
"Catch the Falling Objects" is just the default. Build any of these instead — they all reuse the starter template's game loop, drawing, and movement. Variety makes the Day 5 gallery walk way more fun. The Boss games need collision, a grid, or an enemy.
Starter — one moving thing + one goal
Dodger
The opposite of Catch: obstacles fall and you avoid them. Survive as long as you can — score goes up over time.
Whack-a-Mole / Aim Trainer
Targets pop up at random spots; click them for points before they vanish. Uses the mouse instead of the keyboard (pygame.MOUSEBUTTONDOWN).
Standard — collision or bouncing
Pong
A ball bounces between two paddles. Teaches ball velocity and wall/paddle collision. Play vs. a friend, or vs. a simple computer paddle.
Flappy-Style Tap Game
Gravity pulls the player down; tap a key to flap upward through gaps in pipes. One button, surprisingly addictive.
Boss Challenge — grids and AI
Snake
Move on a grid, grow a tail when you eat food, and lose if you hit yourself. Store the tail as a list of positions. Huge "wow" for the line count.
Maze / Top-Down Collector
Walls you can't walk through and coins to grab. Draw the level from a grid, and block movement when the player would hit a wall.
Stretch Goals
- Add multiple falling objects at the same time — store them in a list
- Make objects fall faster as your score increases
- Add a second type of object to avoid — hitting it loses a life
- Add sound effects:
pygame.mixer.Sound("sound.wav").play() - Add a lives counter — show it on screen, end game when lives reach 0
- Boss challenge: Add a high score that persists across rounds