Start here
Get ready
You need three things: Python 3, somewhere to write code, and pygame-ce. This only takes a few minutes, and you only do it once.
- Python 3. You probably have it already. Python 3.10 or newer is a safe choice. If not, get it from python.org.
- A code editor. IDLE comes with Python and works fine. Thonny and VS Code are good too.
-
Install pygame-ce. Open a terminal (on Windows: Command Prompt or PowerShell; on a Mac: Terminal) and type:
Terminal
pip install pygame-ceIf it says pip is not recognized, try
py -m pip install pygame-ceon Windows orpython3 -m pip install pygame-ceon a Mac. Using Thonny? Go to Tools → Manage packages, search for pygame-ce and click Install. -
Check it works. This runs a little space game that comes with pygame-ce:
Terminal
python -m pygame.examples.aliensA window with aliens and a tank opens? You're ready. (On Windows you may need
pyinstead ofpython; on a Mac,python3.)
What does "ce" mean? It stands for Community Edition. pygame-ce is an up-to-date version of pygame, kept going by a big team of volunteers. You still write import pygame in your code.
Already have the old pygame? Remove it first with pip uninstall pygame. Both are called pygame inside Python, so having both at once causes strange problems.
For grown-ups helping out Every program on this page was run and tested with pygame-ce 2.5.8 on Python 3.13 before it was published. Most kids manage one level per sitting; Level 6 may take two. The Try this boxes are where the learning sticks, so let them experiment and break things.
Level 1
Open a window
Every game starts with a window. In this level you'll make one, fill it with sky blue, and learn the one idea that every game in the world uses: the game loop.
Every game is a flipbook
Have you ever drawn a flipbook? Each page is a slightly different picture, and when you flip fast, things look like they move. A game works the same way. Each picture is called a frame, and your program makes them over and over in a loop:
- Step 1Check eventsDid the player press a key or click X?
- Step 2UpdateMove things, check for hits, add points.
- Step 3DrawPaint the next picture.
- Step 4ShowFlip the new picture onto the screen.
Then back to step 1. A game does this about 60 times every second.
Level 1 skips step 2 for now, because nothing moves yet. You'll add it in Level 3.
Type this in
Make a new file called level1_window.py and type this in. Typing it yourself, instead of copying, helps you remember it.
import pygame
pygame.init()
# Make a window 800 pixels wide and 600 pixels tall
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("My First Game")
running = True
while running:
# Check events: what did the player do?
for event in pygame.event.get():
if event.type == pygame.QUIT: # the X button was clicked
running = False
# Draw
screen.fill((135, 206, 235)) # sky blue
# Show the new picture
pygame.display.flip()
pygame.quit()
Run it
Press F5 in IDLE or Thonny (or the Run button in your editor). You should see this. Click the X to close it.
How it works
pygame.init()- Switches pygame on. Every pygame program starts with this.
pygame.display.set_mode((800, 600))- Makes a window 800 pixels wide and 600 pixels tall. See the double brackets? The size is one thing (a pair of numbers), so it gets its own brackets.
while running:- This is the game loop. Everything indented under it happens again and again, until
runningbecomesFalse. pygame.event.get()- Gives you everything that happened since the last frame: key presses, mouse clicks, and clicks on the X button. These are called events.
pygame.QUIT- The event you get when someone clicks X. We set
running = False, so the loop stops. screen.fill((135, 206, 235))- Paints the whole window one color. The three numbers say how much red, green and blue to mix. You'll play with them in Level 2.
pygame.display.flip()- pygame draws on a hidden page first.
flip()puts that page on the screen, like turning a page in the flipbook. Forget it and you'll see nothing! pygame.quit()- After the loop ends, this closes pygame neatly.
Try this
- Change the color to
(255, 105, 180). What color do you get? - Change the window title to your own name.
- Make a square window with
(500, 500).
Level 2
Draw a picture
Now let's put things in the window: grass, a sun and a house. You'll learn how pygame finds places on the screen, how colors work, and you'll use your own function to draw the house.
Where things go
Every spot in the window has two numbers: x (how far across) and y (how far down). The top-left corner is (0, 0). Watch out: in math class y goes up, but on a screen y goes down.
Mix a color
Colors are three numbers from 0 to 255: how much red, green and blue light to mix. (0, 0, 0) is black and (255, 255, 255) is white. Slide these to find a color you like, then copy it into your code.
(135, 206, 235)
Type this in
Save this one as level2_drawing.py. The top part is the same as Level 1.
import pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("My Drawing")
# Colors are (red, green, blue). Each number goes from 0 to 255.
SKY = (135, 206, 235)
GRASS = (34, 139, 34)
SUN = (255, 215, 0)
WALL = (200, 60, 60)
ROOF = (120, 60, 30)
DOOR = (90, 50, 20)
def draw_house(x, y):
pygame.draw.rect(screen, WALL, (x, y, 200, 170))
pygame.draw.polygon(screen, ROOF, [(x - 20, y), (x + 100, y - 100), (x + 220, y)])
pygame.draw.rect(screen, DOOR, (x + 70, y + 80, 60, 90))
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill(SKY)
pygame.draw.rect(screen, GRASS, (0, 450, 800, 150))
pygame.draw.circle(screen, SUN, (680, 110), 60)
draw_house(250, 280)
pygame.display.flip()
pygame.quit()
Run it
How it works
SKY = (135, 206, 235)- Giving colors names makes your code easier to read. Names in CAPITALS are a Python habit for values that never change.
pygame.draw.rect(screen, color, (x, y, width, height))- Draws a rectangle.
(x, y)is its top-left corner. pygame.draw.circle(screen, color, (x, y), radius)- Draws a circle. Here
(x, y)is the middle, and the radius is how far it is from the middle to the edge. pygame.draw.polygon(screen, color, [point, point, point])- Joins up a list of dots and fills the shape. Three dots make the triangle roof.
def draw_house(x, y):- Your own function! Every measurement in it is worked out from
xandy, so you can draw the same house anywhere just by callingdraw_housewith different numbers. - Order matters
- Things drawn later go on top. That's why the sky is painted first, then the grass, then the house.
Try this
- Build a street: call
draw_housetwo more times with different x numbers.Show a hint
under draw_house(250, 280)draw_house(10, 280) draw_house(530, 280) - Give the house a window. Add a line inside
draw_house, so every house gets one.Show a hint
inside draw_housepygame.draw.rect(screen, (170, 220, 255), (x + 20, y + 30, 40, 40)) - Write your own
draw_tree(x)function: a brown rectangle for the trunk and a green circle on top.Show a hint
next to draw_housedef draw_tree(x): pygame.draw.rect(screen, (100, 60, 20), (x, 370, 30, 80)) pygame.draw.circle(screen, (20, 120, 40), (x + 15, 350), 45)Then call
draw_tree(100)in the loop, after the grass. - Make it night: a dark blue sky and a white moon instead of the sun.
Level 3
Make it move
Here's the secret of movement: draw the ball in a slightly different place every frame. That's it. You'll make a ball that bounces around the window forever.
Type this in
import pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Bouncing Ball")
clock = pygame.time.Clock()
ball_x = 400
ball_y = 300
speed_x = 5
speed_y = 4
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Move the ball a little bit
ball_x = ball_x + speed_x
ball_y = ball_y + speed_y
# Hit a wall? Turn around! (30 is the ball's radius)
if ball_x < 30 or ball_x > 770:
speed_x = -speed_x
if ball_y < 30 or ball_y > 570:
speed_y = -speed_y
screen.fill((30, 30, 60))
pygame.draw.circle(screen, (255, 80, 80), (ball_x, ball_y), 30)
pygame.display.flip()
clock.tick(60) # 60 frames every second
pygame.quit()
Run it
How it works
ball_xandball_y- Where the ball is. They're ordinary variables. Changing them is what makes the ball move.
speed_xandspeed_y- How many pixels the ball moves each frame.
5means 5 pixels to the right;-5means 5 pixels to the left. speed_x = -speed_x- The minus sign flips the direction: 5 becomes -5, and -5 becomes 5. That one line is the whole bounce!
ball_x < 30 or ball_x > 770- A circle's
(x, y)is its middle, and this ball's radius is 30. So its left edge touches the wall whenball_xis 30, and its right edge touches whenball_xis 800 − 30 = 770. clock.tick(60)- Waits just long enough that the loop runs 60 times a second. Without it, the ball moves as fast as your computer can go, which is way too fast to see.
Try this
- Change
speed_xandspeed_y. What happens with10and1? - Put a
#in front of thescreen.fillline to switch it off, then run it. What happens? Why?Show the answer
You get a long snake-like trail! Nothing paints over the old pictures anymore, so every ball you ever drew stays on the screen. That's why games fill the screen at the start of every frame.
- Make the ball bigger. Change the radius from 30 to 50. Which other numbers do you need to change so it still bounces at the edges?
- Make the ball change to a random color every time it bounces. Write a
random_color()function that returns a color.Show a hint
level3_bounce.pyimport random # at the very top def random_color(): # after clock = ... return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) ball_color = (255, 80, 80) # before the loopThen add
ball_color = random_color()inside both bounceifs, and draw the circle withball_colorinstead of(255, 80, 80).
Level 4
Take control
A game needs a player! This time a green square moves when you press the arrow keys. You'll also write a function that stops it from escaping off the screen.
Type this in
import pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Arrow Keys")
clock = pygame.time.Clock()
player_x = 375
player_y = 275
SPEED = 6
def keep_inside(value, smallest, biggest):
if value < smallest:
return smallest
if value > biggest:
return biggest
return value
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Which keys are being held down right now?
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
player_x = player_x - SPEED
if keys[pygame.K_RIGHT]:
player_x = player_x + SPEED
if keys[pygame.K_UP]:
player_y = player_y - SPEED
if keys[pygame.K_DOWN]:
player_y = player_y + SPEED
# Don't let the player leave the screen (the square is 50 wide)
player_x = keep_inside(player_x, 0, 800 - 50)
player_y = keep_inside(player_y, 0, 600 - 50)
screen.fill((30, 30, 60))
pygame.draw.rect(screen, (80, 220, 120), (player_x, player_y, 50, 50))
pygame.display.flip()
clock.tick(60)
pygame.quit()
Run it
Hold the arrow keys. Try holding two at once to move diagonally.
How it works
keys = pygame.key.get_pressed()- Asks pygame: which keys are being held down right now? You look up a key with square brackets, and
keys[pygame.K_LEFT]isTruewhile the left arrow is held. - Four separate
ifs - We use
iffour times, notelif, so two keys can work at the same time. That's how diagonal moves happen. player_y - SPEEDfor up- Remember, y goes down the screen. So going up means making y smaller.
def keep_inside(value, smallest, biggest):- Gives back the same number, unless it's too small or too big. Then it gives back the edge instead. We store the answer straight back in
player_x. 800 - 50player_xis the square's left side. Its right side is 50 pixels further along, so the biggestplayer_xallowed is 750.
Try this
- Make the W, A, S and D keys work too.
Show a hint
level4_keys.pyif keys[pygame.K_LEFT] or keys[pygame.K_a]:Do the same for the others with
pygame.K_d,pygame.K_wandpygame.K_s. - Turbo! Go twice as fast while SPACE is held.
Show a hint
right after keys = ...speed = SPEED if keys[pygame.K_SPACE]: speed = SPEED * 2Then use
speedinstead ofSPEEDin the four lines that move the player. - Turn the square into a circle. Its middle is at
(player_x + 25, player_y + 25).
Level 5
Collect coins
Now it's a real game. Move the square to grab the gold coin. Each time you touch it, you score a point and a new coin appears somewhere else.
Type this in
import pygame
import random
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Coin Collector")
clock = pygame.time.Clock()
font = pygame.font.Font(None, 48) # None = pygame's built-in font
SPEED = 6
GOLD = (255, 200, 0)
def keep_inside(value, smallest, biggest):
if value < smallest:
return smallest
if value > biggest:
return biggest
return value
def new_coin():
x = random.randint(20, 750)
y = random.randint(80, 550)
return pygame.Rect(x, y, 30, 30)
# A Rect is a box that knows where it is: (x, y, width, height)
player = pygame.Rect(375, 275, 50, 50)
coin = new_coin()
score = 0
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.x - SPEED
if keys[pygame.K_RIGHT]:
player.x = player.x + SPEED
if keys[pygame.K_UP]:
player.y = player.y - SPEED
if keys[pygame.K_DOWN]:
player.y = player.y + SPEED
player.x = keep_inside(player.x, 0, 800 - player.width)
player.y = keep_inside(player.y, 0, 600 - player.height)
# Are the player and the coin touching?
if player.colliderect(coin):
score = score + 1
coin = new_coin()
screen.fill((30, 30, 60))
pygame.draw.circle(screen, GOLD, coin.center, 15)
pygame.draw.rect(screen, (80, 220, 120), player)
words = font.render(f"Score: {score}", True, (255, 255, 255))
screen.blit(words, (20, 20))
pygame.display.flip()
clock.tick(60)
pygame.quit()
Run it
How it works
pygame.Rect(375, 275, 50, 50)- A Rect (short for rectangle) is a box that remembers its x, y, width and height. You can read or change its parts, like
player.x,player.widthorcoin.center, and you can hand it straight topygame.draw.rect. player.colliderect(coin)- Is
Truewhen the two boxes overlap. Almost every 2D game checks for hits like this. def new_coin():- Returns a brand new coin in a random place. When the player grabs a coin, we just replace it:
coin = new_coin(). random.randint(20, 750)- Picks a random whole number from 20 to 750. That's why we
import randomat the top. font.render(...)thenscreen.blit(...)- Writing text takes two steps.
rendermakes a picture of the words.blitstamps that picture onto the screen at(20, 20). Think of blit as a rubber stamp. f"Score: {score}"- An f-string. Python swaps
{score}for the real number, so it says Score: 4.
Try this
- Make each coin worth 10 points.
- Make the player grow a little bigger with every coin.
Show a hint
inside if player.colliderect(coin):player.width = player.width + 5 player.height = player.height + 5 - Add a second coin called
coin2. It needs three things: create it, check if the player touches it, and draw it.Show a hint
level5_coins.pycoin2 = new_coin() # next to coin = new_coin() if player.colliderect(coin2): # next to the other coin check score = score + 1 coin2 = new_coin() pygame.draw.circle(screen, GOLD, coin2.center, 15) # next to the other coin - Make the score text bigger by changing the
48inpygame.font.Font(None, 48).
Level 6 · Final level
Apple Catcher
Time to build a whole game. Move the basket with the left and right arrows. Catch apples to score. Missing an apple or getting hit by a rock costs a life. Lose all three and it's game over. The more you catch, the faster everything falls.
This program is longer, but you've already met almost every part of it:
| Part of the game | You learned it in |
|---|---|
The game loop, events and flip() | Level 1 |
| Drawing the grass, apple, rock and basket | Level 2 |
| Things falling down the screen | Level 3 |
Moving the basket and keep_inside | Level 4 |
Rects, colliderect, random and text | Level 5 |
Type this in
import pygame
import random
pygame.init()
WIDTH = 800
HEIGHT = 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Apple Catcher")
clock = pygame.time.Clock()
font = pygame.font.Font(None, 48)
SKY = (135, 206, 235)
GRASS = (34, 139, 34)
RED = (220, 30, 30)
BROWN = (139, 90, 43)
LEAF = (50, 180, 50)
GREY = (110, 110, 110)
WHITE = (255, 255, 255)
def keep_inside(value, smallest, biggest):
if value < smallest:
return smallest
if value > biggest:
return biggest
return value
def new_apple():
x = random.randint(20, WIDTH - 60)
return pygame.Rect(x, -40, 36, 36)
def new_rock():
x = random.randint(20, WIDTH - 60)
y = random.randint(-600, -100) # start high up, so rocks arrive at surprise times
return pygame.Rect(x, y, 40, 40)
def draw_apple(apple):
pygame.draw.circle(screen, RED, apple.center, 18)
pygame.draw.rect(screen, BROWN, (apple.centerx - 2, apple.y - 8, 4, 10))
pygame.draw.ellipse(screen, LEAF, (apple.centerx + 2, apple.y - 8, 14, 8))
def draw_rock(rock):
pygame.draw.circle(screen, GREY, rock.center, 20)
def draw_text(words, x, y):
picture = font.render(words, True, WHITE)
screen.blit(picture, (x, y))
basket = pygame.Rect(350, 520, 100, 40)
apple = new_apple()
rock = new_rock()
score = 0
lives = 3
game_over = False
running = True
while running:
# Check events
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
keys = pygame.key.get_pressed()
# Update the game
if not game_over:
if keys[pygame.K_LEFT]:
basket.x = basket.x - 10
if keys[pygame.K_RIGHT]:
basket.x = basket.x + 10
basket.x = keep_inside(basket.x, 0, WIDTH - basket.width)
# Things fall faster as your score goes up
fall_speed = 3 + score // 5
apple.y = apple.y + fall_speed
rock.y = rock.y + fall_speed
if basket.colliderect(apple):
score = score + 1
apple = new_apple()
elif apple.top > HEIGHT: # the apple hit the ground
lives = lives - 1
apple = new_apple()
if basket.colliderect(rock):
lives = lives - 1
rock = new_rock()
elif rock.top > HEIGHT:
rock = new_rock()
if lives == 0:
game_over = True
elif keys[pygame.K_SPACE]:
# Start again
score = 0
lives = 3
apple = new_apple()
rock = new_rock()
game_over = False
# Draw everything
screen.fill(SKY)
pygame.draw.rect(screen, GRASS, (0, 560, WIDTH, 40))
draw_apple(apple)
draw_rock(rock)
pygame.draw.rect(screen, BROWN, basket)
draw_text(f"Score: {score}", 20, 20)
draw_text(f"Lives: {lives}", WIDTH - 150, 20)
if game_over:
draw_text("GAME OVER! Press SPACE to play again", 78, 270)
pygame.display.flip()
clock.tick(60)
pygame.quit()
Run it
When you lose your last life, you'll see this. Press SPACE to play again.
The new ideas
game_over = False- A True/False switch. While it's
False, the game runs as normal. When it'sTrue, nothing moves and pressing SPACE resets everything. if not game_over:…elif keys[pygame.K_SPACE]:- This splits the Update step in two: play the game, or wait for SPACE. The Draw step always runs, so you can still see the screen when the game is over.
score // 5//divides and throws away the leftover, so7 // 5is 1. Every 5 apples, things fall 1 pixel per frame faster.apple.top > HEIGHT- The apple's top edge has gone past the bottom of the window, so it hit the ground. You lose a life and a new apple starts at the top.
y = random.randint(-600, -100)- A negative y is above the window, where you can't see it. Rocks start at different heights up there, so they arrive at surprise times.
WIDTHandHEIGHT- Naming the window size means you only have to change it in one place.
Boss challenges
- Make the game easier or harder. Try changing the basket speed (
10), the starting fall speed (3) and the number of lives (3, in two places). - Remember the best score, even after you press SPACE.
Show a hint
level6_apple_catcher.pyhigh_score = 0 # before the loop, next to score = 0 if lives == 0: # this part is already there game_over = True if score > high_score: high_score = score draw_text(f"Best: {high_score}", 20, 60) # with the other draw_text lines - Add a golden apple worth 5 points. It works a lot like the rock, but it's good to catch!
Show a hint
new_rock()already makes a box that starts high up at a random time, so the golden apple can use it too.level6_apple_catcher.pyGOLD = (255, 200, 0) # with the other colors golden = new_rock() # before the loop, next to rock = new_rock() golden.y = golden.y + fall_speed # under rock.y = ... if basket.colliderect(golden): score = score + 5 golden = new_rock() elif golden.top > HEIGHT: golden = new_rock() pygame.draw.circle(screen, GOLD, golden.center, 16) # after draw_rock(rock)Also add
golden = new_rock()in the part that starts the game again. - Write a
draw_basket(basket)function that draws a nicer basket, with stripes or a handle. - Turn the sky dark once you reach 20 points. Replace
screen.fill(SKY)with anifandelse.
Help
When something goes wrong
Everyone gets errors, even people who make games for a living. When Python shows an error, read the last line first. It usually tells you what's wrong, and the line number shows you where.
No module named 'pygame'- pygame-ce isn't installed for the Python you're using. Go back to Get ready and try the
py -m piporpython3 -m pipcommand. module 'pygame' has no attribute 'init'- You probably named your own file
pygame.py. Python finds your file instead of the real pygame. Rename it to something likemy_game.py. IndentationError- Everything inside the
whileloop needs 4 spaces in front, and everything inside aniforforneeds 4 more. Check the line the error points to. - The window says "Not Responding"
- The
for event in pygame.event.get():part is missing. pygame has to check events every frame, or your computer thinks the game froze. - The window is black, or doesn't change
- Check that
pygame.display.flip()is there, at the end of the loop, and indented inside it. - The window opens and closes straight away
- The drawing code isn't inside the
while running:loop. Check your indenting. - Things move way too fast
- You forgot
clock.tick(60)at the end of the loop. - A shape is missing
- It might be hidden under something drawn later, or placed outside the window. Check its x and y against 800 and 600.
Where to go next
- Use pictures
cat = pygame.image.load("cat.png")loads a picture file from the same folder as your code. Thenscreen.blit(cat, (100, 200))stamps it on the screen, just like the score text.- Add sounds
boing = pygame.mixer.Sound("boing.wav")loads a sound, andboing.play()plays it. Try playing one when an apple is caught.- Lots of apples
- Once you learn lists, put many apple Rects in a list and use a
forloop to move and draw each one. - Read the docs
- The official pygame-ce documentation lists everything pygame can do: pyga.me/docs.