0%
Pygame: Build Your First Game in Python

Pygame: Build Your First Game in Python

Build your first video game in Python with Pygame: game loop, sprites, collisions, sound, text, and score, with the complete code for a catch-the-objects game.

I

InSkillCoach

· min

Pygame: Build Your First Game in Python

Pygame is the most accessible library for creating 2D games in Python. It handles display, sound, keyboard, mouse, and collisions, while staying simple enough for a beginner to build a complete game in a few hours. In this guide, you’ll learn the fundamental building blocks of Pygame, then put them together in a small, complete catch-the-objects game whose full code is provided and commented.

Installation

Pygame is installed with pip:

pip install pygame

# Verification: launches an example game shipped with the library
python -m pygame.examples.aliens

If the example game window opens, you’re all set.

The Window and the Game Loop

Every game relies on the same structure: an infinite loop that, on each iteration, handles events (keyboard, mouse, closing the window), updates the game state, then redraws the screen.

import pygame

# Initialize all Pygame modules
pygame.init()

# Create the window (width, height)
ecran = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Mon premier jeu")

# Clock to control the game speed
horloge = pygame.time.Clock()

en_cours = True
while en_cours:
    # 1. EVENTS: handle user input
    for event in pygame.event.get():
        if event.type == pygame.QUIT:  # click on the window's close button
            en_cours = False

    # 2. UPDATE: update the game logic (positions, score...)

    # 3. DRAW: redraw everything
    ecran.fill((30, 30, 60))   # midnight blue background (R, G, B)
    pygame.display.flip()       # show the result on screen

    # 4. Limit the game to 60 frames per second
    horloge.tick(60)

pygame.quit()

Two key points:

  • pygame.display.flip() pushes everything drawn during the loop iteration to the screen. Without this call, nothing is displayed.
  • horloge.tick(60) waits just long enough for the loop to run 60 times per second. Without it, the game would run at a different speed on each machine.

Drawing Shapes and Images

Pygame provides drawing primitives and image loading:

# Geometric shapes
pygame.draw.rect(ecran, (255, 0, 0), (100, 100, 50, 80))      # red rectangle
pygame.draw.circle(ecran, (0, 255, 0), (400, 300), 40)        # green circle
pygame.draw.line(ecran, (255, 255, 255), (0, 0), (800, 600), 3)  # white line

# Loading and displaying an image
# convert_alpha() speeds up rendering and preserves PNG transparency
joueur_img = pygame.image.load("joueur.png").convert_alpha()
ecran.blit(joueur_img, (200, 150))   # blit = copy the image at a position

# Resize an image
joueur_img = pygame.transform.scale(joueur_img, (64, 64))

The coordinate system starts at the top-left corner: x increases to the right, y increases downward.

Handling the Keyboard and Mouse

There are two complementary approaches:

# Approach 1: events (perfect for one-off actions)
for event in pygame.event.get():
    if event.type == pygame.KEYDOWN:
        if event.key == pygame.K_SPACE:
            print("Saut !")           # triggered only once per key press
    if event.type == pygame.MOUSEBUTTONDOWN:
        print(f"Clic en {event.pos}")  # (x, y) position of the click

# Approach 2: key state (perfect for continuous movement)
touches = pygame.key.get_pressed()
if touches[pygame.K_LEFT]:
    x -= 5   # as long as the left arrow is held down
if touches[pygame.K_RIGHT]:
    x += 5

# Current mouse position
souris_x, souris_y = pygame.mouse.get_pos()

Remember the rule: use events for instantaneous actions (shooting, jumping, pausing) and get_pressed() for smooth movement.

Sprites and Groups

A sprite is a game object that combines an image (self.image) and a position (self.rect). Groups let you update and draw dozens of sprites in a single line:

import pygame

class Joueur(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        # Image: here a simple 50x50-pixel blue square
        self.image = pygame.Surface((50, 50))
        self.image.fill((50, 150, 255))
        # The rect stores the position and is used for collisions
        self.rect = self.image.get_rect(center=(400, 550))
        self.vitesse = 7

    def update(self):
        """Called on every frame: moves the player with the keyboard."""
        touches = pygame.key.get_pressed()
        if touches[pygame.K_LEFT]:
            self.rect.x -= self.vitesse
        if touches[pygame.K_RIGHT]:
            self.rect.x += self.vitesse
        # Prevent leaving the screen
        self.rect.clamp_ip(pygame.Rect(0, 0, 800, 600))


# Usage with a group
tous_les_sprites = pygame.sprite.Group()
tous_les_sprites.add(Joueur())

# In the game loop:
tous_les_sprites.update()          # calls update() on each sprite
tous_les_sprites.draw(ecran)       # draws each sprite at its rect

Collisions

Pygame offers several levels of detection:

# Between two rectangles
if joueur.rect.colliderect(ennemi.rect):
    print("Touché !")

# Between a sprite and a group
# dokill=True: the sprites that were hit are removed from the group
objets_touches = pygame.sprite.spritecollide(joueur, groupe_objets, dokill=True)
for objet in objets_touches:
    score += 1

# Between two groups (returns a dictionary)
collisions = pygame.sprite.groupcollide(groupe_tirs, groupe_ennemis, True, True)

Sound

# The mixer must be initialized (pygame.init() does this automatically)
son_bonus = pygame.mixer.Sound("bonus.wav")   # short effect, loaded into memory
son_bonus.set_volume(0.5)                      # volume from 0.0 to 1.0
son_bonus.play()

# Background music (streamed continuously from the file)
pygame.mixer.music.load("musique.ogg")
pygame.mixer.music.play(-1)   # -1 = loop forever

Text and Score

Text is drawn in three steps: create a font, generate an image of the text (render), then display it (blit):

# None = default font, 36 = size in points
police = pygame.font.Font(None, 36)

score = 12
# render(text, antialiasing, color)
texte_score = police.render(f"Score : {score}", True, (255, 255, 255))
ecran.blit(texte_score, (10, 10))

A Complete Game: Catch the Objects

Let’s put everything together in a playable game: apples fall from the top of the screen, and you move a basket with the arrow keys to catch them. Each apple caught earns one point, and each apple missed costs one life. The code is self-contained (no external images: everything is drawn with shapes).

import random
import pygame

# ---------- Étape 1 : constantes et initialisation ----------
LARGEUR, HAUTEUR = 800, 600
FPS = 60

pygame.init()
ecran = pygame.display.set_mode((LARGEUR, HAUTEUR))
pygame.display.set_caption("Attrape-Pommes")
horloge = pygame.time.Clock()
police = pygame.font.Font(None, 36)


# ---------- Étape 2 : les sprites ----------
class Panier(pygame.sprite.Sprite):
    """Le panier contrôlé par le joueur, en bas de l'écran."""

    def __init__(self):
        super().__init__()
        self.image = pygame.Surface((100, 25))
        self.image.fill((160, 100, 40))  # marron
        self.rect = self.image.get_rect(midbottom=(LARGEUR // 2, HAUTEUR - 10))
        self.vitesse = 9

    def update(self):
        touches = pygame.key.get_pressed()
        if touches[pygame.K_LEFT]:
            self.rect.x -= self.vitesse
        if touches[pygame.K_RIGHT]:
            self.rect.x += self.vitesse
        self.rect.clamp_ip(ecran.get_rect())  # rester dans l'écran


class Pomme(pygame.sprite.Sprite):
    """Une pomme qui tombe du haut de l'écran à vitesse aléatoire."""

    def __init__(self):
        super().__init__()
        self.image = pygame.Surface((28, 28), pygame.SRCALPHA)
        pygame.draw.circle(self.image, (220, 40, 40), (14, 14), 14)
        self.rect = self.image.get_rect(
            midtop=(random.randint(20, LARGEUR - 20), -30)
        )
        self.vitesse = random.randint(3, 7)

    def update(self):
        self.rect.y += self.vitesse


# ---------- Étape 3 : état du jeu ----------
panier = Panier()
tous_les_sprites = pygame.sprite.Group(panier)
pommes = pygame.sprite.Group()

score = 0
vies = 3

# Événement personnalisé : faire apparaître une pomme toutes les 800 ms
APPARITION_POMME = pygame.USEREVENT + 1
pygame.time.set_timer(APPARITION_POMME, 800)

# ---------- Étape 4 : boucle de jeu ----------
en_cours = True
while en_cours:
    # Événements
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            en_cours = False
        if event.type == APPARITION_POMME and vies > 0:
            pomme = Pomme()
            pommes.add(pomme)
            tous_les_sprites.add(pomme)

    if vies > 0:
        # Update
        tous_les_sprites.update()

        # Pommes attrapées : +1 point chacune
        attrapees = pygame.sprite.spritecollide(panier, pommes, dokill=True)
        score += len(attrapees)

        # Pommes manquées (sorties de l'écran) : -1 vie chacune
        for pomme in list(pommes):
            if pomme.rect.top > HAUTEUR:
                pomme.kill()   # retire le sprite de tous ses groupes
                vies -= 1

    # Draw
    ecran.fill((25, 35, 55))
    tous_les_sprites.draw(ecran)

    texte = police.render(f"Score : {score}   Vies : {vies}", True, (255, 255, 255))
    ecran.blit(texte, (10, 10))

    if vies <= 0:
        fin = police.render("PARTIE TERMINÉE - Fermez la fenêtre", True, (255, 80, 80))
        rect_fin = fin.get_rect(center=(LARGEUR // 2, HAUTEUR // 2))
        ecran.blit(fin, rect_fin)

    pygame.display.flip()
    horloge.tick(FPS)

pygame.quit()

Save this code as attrape_pommes.py and run it: the game is immediately playable. Here are a few ideas for improvements to practice with: make the apples speed up over time, add a sound for each apple caught, save the high score to a file, or replace the shapes with real images.

Best Practices

  • Stick to the events / update / draw structure: never draw during the logic phase, and don’t modify state during the drawing phase.
  • Always use clock.tick(FPS) for identical speed on all machines.
  • Call convert() or convert_alpha() after every pygame.image.load(): rendering is significantly faster.
  • One sprite class per object type: the code stays readable and groups handle updating and drawing.
  • Use sprite.kill() to cleanly remove a sprite from all its groups, rather than managing lists by hand.
  • Group your constants (dimensions, colors, speeds) at the top of the file: balancing the game becomes trivial.

Conclusion

You now know all of Pygame’s fundamental mechanisms: the game loop paced by the clock, drawing shapes and images, keyboard handling, sprites organized into groups, collisions, sound, and score display. The catch-the-objects game brings all these elements together in fewer than 120 lines; it’s a solid foundation you can expand as you like.

To keep going, explore the official Pygame documentation and take on a classic challenge: recreate Pong, Snake, or a brick breaker. Building small, complete games is the fastest way to improve.

InSkillCoach

About InSkillCoach

Training and technology expert

Coach specializing in advanced technologies and AI, backed by GNeurone Inc.

Certifications:

  • AWS Certified Solutions Architect – Professional
  • Google Cloud certifications
  • Microsoft Certified: DevOps Engineer Expert
  • Certified Kubernetes Administrator (CKA)
  • CompTIA Security+
755
212

Comments

Comments are powered by GitHub Discussions

Sign in with GitHub to join the discussion

Link copied!