pygame-ce for Python beginners

Pygame Quest

Six levels, one real game. You start with an empty window and finish with Apple Catcher: catch the falling apples, dodge the rocks, and beat your best score.

You already know enough. If you can use variables, if, while and for loops, and write your own functions with def, you're ready.

Apple Catcher
The finished Apple Catcher game: a red apple and a grey rock falling from a blue sky toward a brown basket on green grass. Score 3, Lives 3.

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.

  1. Python 3. You probably have it already. Python 3.10 or newer is a safe choice. If not, get it from python.org.
  2. A code editor. IDLE comes with Python and works fine. Thonny and VS Code are good too.
  3. Install pygame-ce. Open a terminal (on Windows: Command Prompt or PowerShell; on a Mac: Terminal) and type:
    Terminal
    pip install pygame-ce

    If it says pip is not recognized, try py -m pip install pygame-ce on Windows or python3 -m pip install pygame-ce on a Mac. Using Thonny? Go to Tools → Manage packages, search for pygame-ce and click Install.

  4. Check it works. This runs a little space game that comes with pygame-ce:
    Terminal
    python -m pygame.examples.aliens

    A window with aliens and a tank opens? You're ready. (On Windows you may need py instead of python; 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:

  1. Step 1Check eventsDid the player press a key or click X?
  2. Step 2UpdateMove things, check for hits, add points.
  3. Step 3DrawPaint the next picture.
  4. 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.

level1_window.py
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.

My First Game
An empty window filled with sky blue.

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 running becomes False.
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

  1. Change the color to (255, 105, 180). What color do you get?
  2. Change the window title to your own name.
  3. 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.

A game window. (0, 0) is the top-left corner. x gets bigger going right, y gets bigger going down. The sun is at (680, 110) and the bottom-right corner is (800, 600). x gets bigger → y gets bigger → (0, 0) (680, 110) (800, 600) 800 wide, 600 tall

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
Your color
(135, 206, 235)

Type this in

Save this one as level2_drawing.py. The top part is the same as Level 1.

level2_drawing.py
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

My Drawing
A red house with a brown roof and door on green grass, under a blue sky with a yellow sun.

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 x and y, so you can draw the same house anywhere just by calling draw_house with 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

  1. Build a street: call draw_house two more times with different x numbers.
    Show a hint
    under draw_house(250, 280)
        draw_house(10, 280)
        draw_house(530, 280)
  2. Give the house a window. Add a line inside draw_house, so every house gets one.
    Show a hint
    inside draw_house
        pygame.draw.rect(screen, (170, 220, 255), (x + 20, y + 30, 40, 40))
  3. 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_house
    def 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.

  4. 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

level3_bounce.py
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

Bouncing Ball
A red ball on a dark blue background. When you run it, the ball bounces off all four edges.
In your window the ball keeps bouncing off the edges.

How it works

ball_x and ball_y
Where the ball is. They're ordinary variables. Changing them is what makes the ball move.
speed_x and speed_y
How many pixels the ball moves each frame. 5 means 5 pixels to the right; -5 means 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 when ball_x is 30, and its right edge touches when ball_x is 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

  1. Change speed_x and speed_y. What happens with 10 and 1?
  2. Put a # in front of the screen.fill line 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.

  3. 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?
  4. 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.py
    import 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 loop

    Then add ball_color = random_color() inside both bounce ifs, and draw the circle with ball_color instead 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

level4_keys.py
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.

Arrow Keys
A green square on a dark blue background.

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] is True while the left arrow is held.
Four separate ifs
We use if four times, not elif, so two keys can work at the same time. That's how diagonal moves happen.
player_y - SPEED for 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 - 50
player_x is the square's left side. Its right side is 50 pixels further along, so the biggest player_x allowed is 750.

Try this

  1. Make the W, A, S and D keys work too.
    Show a hint
    level4_keys.py
        if keys[pygame.K_LEFT] or keys[pygame.K_a]:

    Do the same for the others with pygame.K_d, pygame.K_w and pygame.K_s.

  2. Turbo! Go twice as fast while SPACE is held.
    Show a hint
    right after keys = ...
        speed = SPEED
        if keys[pygame.K_SPACE]:
            speed = SPEED * 2

    Then use speed instead of SPEED in the four lines that move the player.

  3. 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

level5_coins.py
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

Coin Collector
A green square, a gold coin, and the words Score: 4 in the top-left corner.

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.width or coin.center, and you can hand it straight to pygame.draw.rect.
player.colliderect(coin)
Is True when 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 random at the top.
font.render(...) then screen.blit(...)
Writing text takes two steps. render makes a picture of the words. blit stamps 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

  1. Make each coin worth 10 points.
  2. 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
  3. 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.py
    coin2 = 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
  4. Make the score text bigger by changing the 48 in pygame.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 gameYou learned it in
The game loop, events and flip()Level 1
Drawing the grass, apple, rock and basketLevel 2
Things falling down the screenLevel 3
Moving the basket and keep_insideLevel 4
Rects, colliderect, random and textLevel 5

Type this in

level6_apple_catcher.py
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.

Apple Catcher
The Apple Catcher game over screen: GAME OVER! Press SPACE to play again, with Score: 1 and Lives: 0.

The new ideas

game_over = False
A True/False switch. While it's False, the game runs as normal. When it's True, 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, so 7 // 5 is 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.
WIDTH and HEIGHT
Naming the window size means you only have to change it in one place.

Boss challenges

  1. 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).
  2. Remember the best score, even after you press SPACE.
    Show a hint
    level6_apple_catcher.py
    high_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
  3. 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.py
    GOLD = (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.

  4. Write a draw_basket(basket) function that draws a nicer basket, with stripes or a handle.
  5. Turn the sky dark once you reach 20 points. Replace screen.fill(SKY) with an if and else.

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 pip or python3 -m pip command.
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 like my_game.py.
IndentationError
Everything inside the while loop needs 4 spaces in front, and everything inside an if or for needs 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. Then screen.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, and boing.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 for loop to move and draw each one.
Read the docs
The official pygame-ce documentation lists everything pygame can do: pyga.me/docs.