ProPython
Pygame - Créez votre premier jeu
20 Apr, 2021

Pygame - Créez votre premier jeu

Pygame est une librairie très populaire en Python, utilisée afin de créer des jeux 2D relativement simplement. Pour ceux qui ont déjà fait du C, vous connaissez sûrement la SDL, en fait Pygame est simplement une adaptation de cette librairie en Python.

Allez c'est parti ! Mais avant de commencer, nous allons voir ce qu'est une interface graphique, puisque c'est sur cela que repose Pygame.

Les interfaces graphiques

Souvent, lorsque vous apprenez un nouveau langage de programmation, vous commencez par réaliser des programmes en console. Cela permet de travailler réellement sur le code, sans se préoccuper du design, de l'audio, etc... Un tel programme est utile lorsque vous n'avez pas besoin d'utiliser un design spécifique pour votre programme.

Mais lorsque vous souhaitez réaliser un jeu, l'expérience utilisateur est très importante, il est donc nécessaire de gérer le design. C'est pour cela qu'on utilise des interfaces graphiques. Cela a pour conséquence de faire perdre des performances contrairement à un programme en console, mais ça a pour avantage de procurer un affichage intuitif, avec la possibilité d'ajouter des images et de réaliser un vrai design.

Un fonctionnement différent

Un programme qui tourne via en interface graphique a un fonctionnement bien différent par rapport à un programme qui tourne en console.

En effet, un programme en console consiste la plupart du temps en une suite d'actions successives et bien séquencées. Par exemple : on demande à l'utilisateur d'entrer des données => on attend ces données => on demande d'autres données => on attend => etc... Ce qu'il se passe dans le programme est en fait contrôlé par le programme lui-même.

En interface graphique, on va par exemple afficher un personnage, et le programme va réagir lorsqu'un utilisateur appuiera sur une touche, ou fera un clic de souris. Ici, ce sont les actions effectuées par l'utilisateur qui contrôlent le programme. On parle d'évènements. Il faut donc faire un programme qui va écouter différents évènements et réagir à ces évènements, plutôt qu'on programme qui va créer ses propres évènements.

Démarrer avec Pygame

Installation

Comme toute librairie que vous souhaiteriez utiliser, il faut d'abord l'installer. Pour cela, on utilise la commande classique :

pip install pygame

Egalement, si vous souhaitez utiliser les mêmes ressources que moi lors de cet article, vous pouvez télécharger les ressources en cliquant ici. C'est un fichier zip que vous pouvez simplement extraire dans le projet que vous avez créé pour suivre cette article.

Structure de Pygame

Pygame est divisée en différents modules répondant chacun à un rôle bien précis :

  • display : affichage graphique
  • mixer : gestion de l'audio
  • draw : gestion du dessin
  • event : gestion des évènements
  • image : chargement d'images
  • mouse : gestion de la souris
  • time : gestion du temps

Finalement, Pygame dispose aussi de constantes contenues dans pygame.locals.

Notre première fenêtre

La première chose à faire lorsque vous faites un programme en interface graphique, c'est évidemment de créer cette interface graphique. Nous allons donc commencer par là. Mais avant toute chose, il est nécessaire d'initialiser Pygame, via la fonction init().

import pygame
pygame.init()

On peut maintenant créer notre fenêtre. Pour créer une fenêtre avec Pygame, on utilise la fonction set_mode du module display. Cette fonction prend en paramètre la taille de votre fenêtre sous forme d'un tuple, et éventuellement des paramètres pour définir certaines propriétés de la fenêtre (la rendre redimensionnable, la mettre en plein écran, etc...)

import pygame.locals
window = pygame.display.set_mode((640, 360), pygame.locals.RESIZABLE)

Ici, on crée une fenêtre de largeur 640 et de hauteur 360, redimensionnable, qu'on stocke dans une variable. L'objet renvoyé par la fonction est une Surface, c'est un type particulier de Pygame, nous permettant d'afficher des images. Si vous avez exécuté le code, vous avez dû constater que votre fenêtre s'ouvre et se ferme quasiment instantanément. C'est normal, puisque vous n'avez pas d'instructions après la déclaration de votre fenêtre, donc votre programme s'arrête.

Pour bloquer le programme et donc maintenir la fenêtre, il faut utiliser une boucle infinie. Voici donc le code minimal pour ouvrir et maintenir une fenêtre :

import pygame.display
import pygame.locals

pygame.init()
window = pygame.display.set_mode((640, 360), pygame.locals.RESIZABLE)

stop = 0
while not stop:
    continue

Mais de cette façon, vous ne pouvez pas fermer votre fenêtre proprement. Pour l'instant, le seul moyen de la fermer est d'arrêter votre programme (Ctrl+C).

Ajouter des images

Maintenant que nous avons notre fenêtre, nous aimerions bien la décorer un peu. On va commencer par ajouter une image de fond. Pour cela, il faut d'abord charger notre image. On utilise la fonction pygame.image.load à laquelle on passe en paramètre le chemin vers notre image. En plus de cela, on va également convertir notre image. On utilise donc la méthode convert de notre image. Ce qui donne ceci :

import pygame.image
bg = pygame.image.load("ressources/bg.jpg").convert()

Maintenant, il faut coller cette image sur notre fenêtre. On utilise pour cela la méthode blit de notre fenêtre à laquelle on passe l'image que l'on souhaite coller, ainsi que les coordonnées de collage sous forme de tuple. Si vous ne savez pas quelles coordonnées mettre, voici le repère associé à notre fenêtre :

Ce dessin vous permet aussi de comprendre comment Pygame gère les coordonnées. Ainsi, l'origine du repère est située dans le coin supérieur gauche de votre fenêtre, et les valeurs positives sont situées vers la gauche et le bas pour les axes.

On souhaite que notre fond recouvre toute la fenêtre, on va donc le coller en (0, 0).

window.blit(bg, (0, 0))

Mais si vous essayez d'afficher votre fenêtre, vous ne verrez pas votre image apparaître. En effet, à chaque fois que vous effectuez une modification graphique sur la fenêtre, il faut l'actualiser afin que cette modification soit visible. Pour cela on utilise pygame.display.flip() après avoir collé notre image. Voici donc le code comprenant l'ajout de l'image :

import pygame.display
import pygame.image
import pygame.locals

window = pygame.display.set_mode((640, 360), pygame.locals.RESIZABLE)

bg = pygame.image.load("ressources/bg.jpg").convert()

window.blit(bg, (0, 0))
pygame.display.flip()

stop = 0
while not stop:
    continue

Bien ! Maintenant si vous exécutez ce code, vous voyez votre image remplir toute la fenêtre. Maintenant, nous allons rajouter un personnage qui sera contrôlé par le joueur. De la même manière, on importe notre image et on la colle.

mario = pygame.image.load("ressources/mario.png").convert()

window.blit(bg, (0, 0))

Cependant, si vous affichez votre fenêtre, vous verrez que la transparence de notre personnage n'est pas conservée. Pour conserver notre transparence, on remplace convert par convert_alpha.

mario = pygame.image.load("ressources/mario.png").convert_alpha()

Puis, juste pour que ce soit un peu cohérent, on va coller notre personnage en (0, 251) histoire qu'il touche le sol, modifiez donc la position dans blit.

Bon, on a une fenêtre, un fond, un personnage, maintenant on voudrait déplacer ce personnage. Nous allons donc découvrir les évènements.

Les évènements

J'ai évoqué brièvement ce que sont les évènements au début de cet article, je vais donc revenir dessus rapidement. Un évènement, c'est quelque chose que le programme peut "capter", venant de l'utilisateur. Par exemple, un clic de souris, un mouvement de souris, ou l'appui sur une touche de clavier.

Nous allons commencer avec un évènement basique afin de mieux contrôler notre fenêtre.

Fermer la fenêtre

Pour fermer notre fenêtre, il va falloir détecter le clic d'un utilisateur sur le bouton de fermeture. Afin de gérer les évènements, Pygame utilise la fonction pygame.event.get() qui détecte et renvoie une liste de tous les évènements se produisant à l'instant où elle est appelée. Il nous suffit donc de vérifier cette liste à tout instant, et de voir si elle contient l'évènement de clic sur le bouton de fermeture de la fenêtre. Pour cela, on va vérifier un par un chaque évènement contenu dans la liste. Et ceci à chaque fois qu'on fait un tour dans notre boucle infinie. Voici le code :

stop = 0
while not stop:

    for event in pygame.event.get():  # On récupère tous les évènements
        if event.type == pygame.locals.QUIT:  # Si on détecte un clic sur le bouton "Quitter", on passe "stop" à 1
            stop = 1
C'est quoi pygame.locals.QUIT ?

C'est une constante qui indique que l'évènement est de type QUIT (c'est à dire que l'utilisateur a cliqué sur la petite croix en haut à droite de la fenêtre). Si on détecte cet évènement, alors on passe stop à 1, donc la condition dans le while n'est plus valide, et le programme s'arrête. Vous pouvez tester, vous verrez qu'en cliquant sur la croix, votre fenêtre se ferme proprement.

Evènements de clavier

Les évènements de clavier sont un type d'évènement correspondant à l'appui d'une touche sur votre clavier. Ces évènements sont identifiés par le type pygame.locals.KEYDOWN. Et pour accéder à la touche appuyée, on utilise event.key qui nous renvoie une constante. event.key peut prendre les valeurs suivantes, qui représentent donc tout votre clavier :

  • Lettres : K_a, K_b, ..., k_z
  • Nombres : K_0, ..., K_9
  • Touches spécifiques : K_TAB, K_RETURN, K_ESCAPE, ...

La liste complète se trouve ici : Pygame Keys Doc.

Essayez maintenant de créer un nouvel évènement : lorsque l'utilisateur appuie sur la touche E, la fenêtre se ferme. Il suffit en fait de vérifier le type de l'évènement comme toute à l'heure, et si c'est un évènement KEYDOWN, alors on vérifie la touche appuyée avec event.key. Si cette touche est le E, alors on ferme la fenêtre.

Correction

Maintenant, qu'on sait gérer les évènements de clavier, on va pouvoir définir des touches pour contrôler notre personnage !

Contrôle du personnage

On va supposer que notre personnage ne peut pas aller vers le haut, ni vers le bas, puisqu'il ne sait pas sauter et qu'il y a un sol en dessous de lui. Il ne peut donc se déplacer que vers la gauche ou vers la droite. Pour le contrôler, nous allons utiliser deux touches, la touche Q (pour aller à gauche) et la touche D (pour aller à droite). D'un point de vue procédural, il faut que lorsque l'on appuie sur Q, on diminue la position de notre image sur l'axe horizontal, et lorsque l'on appuie sur D on doit l'augmenter.

Il faut donc d'abord récupérer les coordonnées de notre personnage. Elles sont stockées dans une classe appelée Rect. On va donc récupérer cet objet dès le chargement de notre personnage, avec la méthode get_rect d'une image. Voici donc le chargement de notre personnage en récupérant ses coordonnées.

mario = pygame.image.load("ressources/mario.png").convert_alpha()
position_mario = mario.get_rect()

La position par défaut est (0, 0), on doit donc changer cette position initiale. On utilise pour cela la méthode update de Rect qui prend en paramètres la position x, position y, la largeur, et la hauteur. On ne veut pas changer la taille de notre image, donc on va simplement utiliser les deux premiers paramètres qu'on va mettre à (0, 251).

position_mario.update(0, 251, 0, 0)

Maintenant qu'on a un objet stockant la position de notre image, on peut blit notre image en utilisant cet objet à la place d'un tuple classique pour la position.

window.blit(mario, position_mario)

Voici donc le code à ce stade :

import pygame.display
import pygame.image
import pygame.locals

window = pygame.display.set_mode((640, 360), pygame.locals.RESIZABLE)

bg = pygame.image.load("ressources/bg.jpg").convert()
mario = pygame.image.load("ressources/mario.png").convert_alpha()
position_mario = mario.get_rect()
position_mario.update(0, 251, 0, 0)

window.blit(bg, (0, 0))
window.blit(mario, position_mario)
pygame.display.flip()

stop = 0
while not stop:

    for event in pygame.event.get():
        if event.type == pygame.locals.QUIT:
            stop = 1
        if event.type == pygame.locals.KEYDOWN:
            if event.key == pygame.locals.K_q:
                stop = 1

Si nous nous sommes embêtés à modifier notre code pour intégrer notre objet position_mario, c'est pour une simple raison : cet objet dispose d'une méthode move prenant en paramètres deux entiers représentant respectivement un déplacement selon x, et un déplacement selon y., et renvoyant la position de notre image déplacée. On a maintenant tous les outils pour déplacer notre personnage, allons-y !

On commence par intégrer les évènements :

            if event.key == pygame.locals.K_q:
                position_mario = position_mario.move(-3, 0)
            if event.key == pygame.locals.K_d:
                position_mario = position_mario.move(3, 0)

On actualise simplement la position lorsqu'on détecte un appui sur les touches définies plus haut.

Maintenant, il faut à nouveau coller notre image. Mais attendez, car si vous collez votre personnage directement, vous verrez toujours en plus l'ancien personnage. Testez et vous verrez.

Il faut donc nettoyer la fenêtre avant de bouger notre personnage. Pour cela, on recolle simplement notre fond de base.

window.blit(bg, (0, 0)) 

Ensuite, on peut coller notre personnage :

window.blit(mario, position_mario)

Finalement, il faut actualiser la fenêtre :

pygame.display.flip()

On obtient le code final suivant :

import pygame.display
import pygame.image
import pygame.locals

window = pygame.display.set_mode((640, 360), pygame.locals.RESIZABLE)

bg = pygame.image.load("ressources/bg.jpg").convert()
mario = pygame.image.load("ressources/mario.png").convert_alpha()
position_mario = mario.get_rect()
position_mario.update(0, 251, 0, 0)

window.blit(bg, (0, 0))
window.blit(mario, position_mario)
pygame.display.flip()

stop = 0
while not stop:

    for event in pygame.event.get():
        if event.type == pygame.locals.QUIT:
            stop = 1
        if event.type == pygame.locals.KEYDOWN:
            if event.key == pygame.locals.K_e:
                stop = 1
            if event.key == pygame.locals.K_q:
                position_mario = position_mario.move(-3, 0)
            if event.key == pygame.locals.K_d:
                position_mario = position_mario.move(3, 0)

    window.blit(bg, (0, 0))
    window.blit(mario, position_mario)
    pygame.display.flip()

Vous pouvez le tester, et vous verrez qu'on peut déplacer notre personnage. On peut même le déplacer en dehors de la fenêtre.

Par contre, il faut bourriner sur notre clavier pour le déplacer. On préfèrerait laisser appuyez sur la touche D par exemple pour que notre personnage avance plutôt que d'appuyer 50 fois. Pour cela, on utilise pygame.key.set_repeat. Cette fonction prend en premier paramètre la temps avant de commencer la répétition de touches, et en second paramètre le délai entre chaque répétition. Placez la n'importe où, par exemple avant la boucle while :

pygame.key.set_repeat(1, 30)

Testez avec d'autres valeurs pour les paramètres si vous ne comprenez pas bien ce que ça change.

Evènements à la souris

Pour finir, on va voir les évènements à la souris. Si vous faites un clic de souris, le type d'évènement est MOUSEBUTTONDOW (ou MOUSEBUTTONUP lorsqu'on relâche le bouton). Un tel évènement possède deux attributs : le bouton cliqué (récupérable via event.button et la position du clic (event.pos). Voici les valeurs possibles pour event.button :

  • 1 : clic gauche
  • 2 : bouton du milieu ou gauche + droite
  • 3 : clic droit
  • 4 : molette haut
  • 5 : molette bas

On va par exemple créer un évènement pour faire sauter notre personnage lorsque l'on fait un clic gauche.

Voici l'évènement à rajouter :

        if event.type == pygame.locals.MOUSEBUTTONDOWN:
            if event.button == 1:
                for i in range(20):
                    side = -1
                    if i > 9:
                        side = 1
                    position_mario = position_mario.move(0, 3*side)
                    window.blit(bg, (0, 0))
                    window.blit(mario, position_mario)
                    pygame.display.flip()
                    pygame.time.delay(30)

Notez que j'ai utilisé pygame.time.delay qui permet de mettre en pause le programme pour x millisecondes, x étant donné en paramètre. Cela rend le saut mieux visible. Normalement le reste du code est compréhensible pour vous.

Voici donc tout le code de notre petit jeu :

import pygame.display
import pygame.image
import pygame.locals
import pygame.time

window = pygame.display.set_mode((640, 360), pygame.locals.RESIZABLE)

bg = pygame.image.load("ressources/bg.jpg").convert()
mario = pygame.image.load("ressources/mario.png").convert_alpha()
position_mario = mario.get_rect()
position_mario.update(0, 251, 0, 0)

window.blit(bg, (0, 0))
window.blit(mario, position_mario)
pygame.display.flip()

pygame.key.set_repeat(1, 30)
stop = 0
while not stop:

    for event in pygame.event.get():
        if event.type == pygame.locals.QUIT:
            stop = 1
        if event.type == pygame.locals.KEYDOWN:
            if event.key == pygame.locals.K_e:
                stop = 1
            if event.key == pygame.locals.K_q:
                position_mario = position_mario.move(-3, 0)
            if event.key == pygame.locals.K_d:
                position_mario = position_mario.move(3, 0)
        if event.type == pygame.locals.MOUSEBUTTONDOWN:
            if event.button == 1:
                for i in range(20):
                    side = -1
                    if i > 9:
                        side = 1
                    position_mario = position_mario.move(0, 3*side)
                    window.blit(bg, (0, 0))
                    window.blit(mario, position_mario)
                    pygame.display.flip()
                    pygame.time.delay(30)


    window.blit(bg, (0, 0))
    window.blit(mario, position_mario)
    pygame.display.flip()

Avant de finir, il reste un type d'évènement de souris dont nous n'avons pas parlé : le déplacement de souris.

Son type est MOUSEMOTION. Il possède 3 attributs :

  • pos : nouvelle position de la souris, sous forme de tuple
  • rel : nombre de pixels de déplacement depuis la dernière postion
  • buttons : les boutons pressés pendant le déplacement

Voilà, c'est tout pour les évènements de souris, et même pour les autres évènements d'ailleurs puisque c'est déjà la fin de l'article !

Le mot de la fin

Voilà, vous avez pu créer un petit jeu (bon, pas ouf, mais c'est un début) ! Nous verrons prochainement comment créer des jeux plus élaborés lors d'un petit projet, même si en soi vous avez déjà pas mal d'éléments pour réaliser des jeux.

Si vous avez des questions, ou des retours à me faire par rapport à l'article, n'hésitez pas à laisser un commentaire, ou à me contacter par mail ou via l'onglet disponible sur le site !

On se retrouve prochainement afin de voir tout ça en application dans un petit projet !

Laisser un commentaire

Premium - 15€/mois

L'accès à des articles inédits, à une multitude de ressources, à de nouveaux projets, mais également à des vidéos explicatives, découvrez ici pourquoi passer premium.

Articles liés

Catégories

Ressources

Retrouvez une collection de ressources (des scripts, des fiches résumé, des images...) liées aux articles du blog ou au Python.
Voir

Contact

contact@propython.fr
Se connecter pour envoyer un message directement depuis le site.

Navigation

AccueilSe connecterCréer un compteRessourcesPremium

Catégories

Pages légales

Politique de confidentialitéMentions légalesConditions générales de vente