Introduction
Dans ce projet, nous allons décomposer le code pour créer un jeu Flappy Bird simple en utilisant la bibliothèque Pygame en étapes gérables. En suivant ces étapes, vous apprendrez à construire le jeu progressivement. Chaque étape comprendra une brève explication, des blocs de code et des commentaires pour vous aider à comprendre et à implémenter le jeu. Commençons!
👀 Aperçu

🎯 Tâches
Dans ce projet, vous apprendrez :
- Comment configurer les fichiers du projet pour le jeu Flappy Bird
- Comment afficher l'animation d'accueil du jeu
- Comment implémenter la logique principale du jeu Flappy Bird
- Comment afficher l'écran de fin de partie lorsque le joueur perd
- Comment définir des fonctions d'aide pour le jeu
🏆 Réalisations
Après avoir terminé ce projet, vous serez capable de :
- Utiliser la bibliothèque Pygame pour créer des jeux
- Comprendre les concepts de développement de jeux tels que les boucles de jeu, les collisions et l'animation
Créer les fichiers du projet
Tout d'abord, nous allons créer les fichiers du projet pour le jeu Flappy Bird.
cd ~/projet
touch flappy.py
sudo pip install pygame
Dans cette étape, nous allons configurer la structure de base du projet et importer les bibliothèques nécessaires. Nous allons également définir quelques constantes et charger les actifs de jeu initiaux.
from itertools import cycle
import random
import sys
import pygame
from pygame.locals import *
FPS = 30
SCREENWIDTH = 288
SCREENHEIGHT = 512
PIPEGAPSIZE = 100 ## espace entre la partie supérieure et la partie inférieure du tuyau
BASEY = SCREENHEIGHT * 0.79
## dictionnaires d'images et de hitmask
IMAGES, HITMASKS = {}, {}
## liste de tous les joueurs possibles (tuple de 3 positions du battement d'ailes)
PLAYERS_LIST = (
## oiseau rouge
(
"data/sprites/redbird-upflap.png",
"data/sprites/redbird-midflap.png",
"data/sprites/redbird-downflap.png",
),
## oiseau bleu
(
"data/sprites/bluebird-upflap.png",
"data/sprites/bluebird-midflap.png",
"data/sprites/bluebird-downflap.png",
),
## oiseau jaune
(
"data/sprites/yellowbird-upflap.png",
"data/sprites/yellowbird-midflap.png",
"data/sprites/yellowbird-downflap.png",
),
)
## liste d'arrière-plans
BACKGROUNDS_LIST = (
"data/sprites/background-day.png",
"data/sprites/background-night.png",
)
## liste de tuyaux
PIPES_LIST = (
"data/sprites/pipe-green.png",
"data/sprites/pipe-red.png",
)
- Nous importons les bibliothèques nécessaires pour le jeu, y compris
pygamepour créer le jeu,randompour générer des éléments aléatoires,syspour les fonctions liées au système etpygame.localspour les constantes de touches. - Nous initialisons Pygame avec
pygame.init(). - Des constantes telles que
FPS,SCREENWIDTH,SCREENHEIGHT,PIPEGAPSIZEetBASEYsont définies pour configurer les dimensions et la vitesse du jeu. - Nous créons des dictionnaires vides (
IMAGESetHITMASKS) pour stocker les actifs de jeu. - Des listes d'actifs de joueurs, d'arrière-plans et de tuyaux sont définies à l'aide de chemins de fichiers.
Afficher l'animation d'accueil
Dans cette étape, nous allons créer l'animation de l'écran d'accueil du jeu Flappy Bird.
def showWelcomeAnimation():
"""Affiche l'animation de l'écran d'accueil du Flappy Bird"""
## indice du joueur à afficher à l'écran
playerIndex = 0
playerIndexGen = cycle([0, 1, 2, 1])
## itérateur utilisé pour changer playerIndex après chaque 5ème itération
loopIter = 0
playerx = int(SCREENWIDTH * 0.2)
playery = int((SCREENHEIGHT - IMAGES["player"][0].get_height()) / 2)
messagex = int((SCREENWIDTH - IMAGES["message"].get_width()) / 2)
messagey = int(SCREENHEIGHT * 0.12)
basex = 0
## montant par lequel la base peut maximum se déplacer vers la gauche
baseShift = IMAGES["base"].get_width() - IMAGES["background"].get_width()
## mouvement vertical du joueur sur l'écran d'accueil
playerShmVals = {"val": 0, "dir": 1}
while True:
for event in pygame.event.get():
if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
pygame.quit()
sys.exit()
if event.type == KEYDOWN and (event.key == K_SPACE or event.key == K_UP):
## jouer le premier son de battement d'ailes et retourner les valeurs pour mainGame
return {
"playery": playery + playerShmVals["val"],
"basex": basex,
"playerIndexGen": playerIndexGen,
}
## ajuster playery, playerIndex, basex
if (loopIter + 1) % 5 == 0:
playerIndex = next(playerIndexGen)
loopIter = (loopIter + 1) % 30
basex = -((-basex + 4) % baseShift)
playerShm(playerShmVals)
## dessiner les sprites
SCREEN.blit(IMAGES["background"], (0, 0))
SCREEN.blit(
IMAGES["player"][playerIndex], (playerx, playery + playerShmVals["val"])
)
SCREEN.blit(IMAGES["message"], (messagex, messagey))
SCREEN.blit(IMAGES["base"], (basex, BASEY))
pygame.display.update()
FPSCLOCK.tick(FPS)
- Nous définissons la fonction
showWelcomeAnimationresponsable de l'affichage de l'animation de l'écran d'accueil. - La fonction configure des variables pour l'animation et gère l'entrée utilisateur pour démarrer le jeu.
- Elle utilise une boucle pour mettre à jour les images de l'animation et vérifier l'entrée utilisateur pour commencer le jeu.
- L'animation comprend l'oiseau battant des ailes et un message affiché à l'écran.
- La fonction
pygame.display.update()est utilisée pour mettre à jour l'affichage, etFPSCLOCK.tick(FPS)contrôle le taux de rafraîchissement.
Logique principale du jeu
Dans cette étape, nous allons implémenter la logique principale du jeu Flappy Bird.
def mainGame(movementInfo):
score = playerIndex = loopIter = 0
playerIndexGen = movementInfo["playerIndexGen"]
playerx, playery = int(SCREENWIDTH * 0.2), movementInfo["playery"]
basex = movementInfo["basex"]
baseShift = IMAGES["base"].get_width() - IMAGES["background"].get_width()
## obtenir 2 nouveaux tuyaux pour ajouter à la liste upperPipes lowerPipes
newPipe1 = getRandomPipe()
newPipe2 = getRandomPipe()
## liste des tuyaux supérieurs
upperPipes = [
{"x": SCREENWIDTH + 200, "y": newPipe1[0]["y"]},
{"x": SCREENWIDTH + 200 + (SCREENWIDTH / 2), "y": newPipe2[0]["y"]},
]
## liste des tuyaux inférieurs
lowerPipes = [
{"x": SCREENWIDTH + 200, "y": newPipe1[1]["y"]},
{"x": SCREENWIDTH + 200 + (SCREENWIDTH / 2), "y": newPipe2[1]["y"]},
]
dt = FPSCLOCK.tick(FPS) / 1000
pipeVelX = -128 * dt
## vitesse du joueur, vitesse maximale, accélération vers le bas, accélération lors du battement d'ailes
playerVelY = -9 ## vitesse du joueur le long de Y, par défaut la même que playerFlapped
playerMaxVelY = 10 ## vitesse maximale le long de Y, vitesse maximale de descente
playerMinVelY = -8 ## vitesse minimale le long de Y, vitesse maximale d'ascension
playerAccY = 1 ## accélération vers le bas du joueur
playerRot = 45 ## rotation du joueur
playerVelRot = 3 ## vitesse angulaire
playerRotThr = 20 ## seuil de rotation
playerFlapAcc = -9 ## vitesse du joueur lors du battement d'ailes
playerFlapped = False ## True lorsque le joueur bat des ailes
while True:
for event in pygame.event.get():
if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
pygame.quit()
sys.exit()
if event.type == KEYDOWN and (event.key == K_SPACE or event.key == K_UP):
if playery > -2 * IMAGES["player"][0].get_height():
playerVelY = playerFlapAcc
playerFlapped = True
## vérifier s'il y a eu un crash ici
crashTest = checkCrash(
{"x": playerx, "y": playery, "index": playerIndex}, upperPipes, lowerPipes
)
if crashTest[0]:
return {
"y": playery,
"groundCrash": crashTest[1],
"basex": basex,
"upperPipes": upperPipes,
"lowerPipes": lowerPipes,
"score": score,
"playerVelY": playerVelY,
"playerRot": playerRot,
}
## vérifier le score
playerMidPos = playerx + IMAGES["player"][0].get_width() / 2
for pipe in upperPipes:
pipeMidPos = pipe["x"] + IMAGES["pipe"][0].get_width() / 2
if pipeMidPos <= playerMidPos < pipeMidPos + 4:
score += 1
## changement de playerIndex basex
if (loopIter + 1) % 3 == 0:
playerIndex = next(playerIndexGen)
loopIter = (loopIter + 1) % 30
basex = -((-basex + 100) % baseShift)
## tourner le joueur
if playerRot > -90:
playerRot -= playerVelRot
## mouvement du joueur
if playerVelY < playerMaxVelY and not playerFlapped:
playerVelY += playerAccY
if playerFlapped:
playerFlapped = False
## plus de rotation pour couvrir le seuil (calculé dans la rotation visible)
playerRot = 45
playerHeight = IMAGES["player"][playerIndex].get_height()
playery += min(playerVelY, BASEY - playery - playerHeight)
## déplacer les tuyaux vers la gauche
for uPipe, lPipe in zip(upperPipes, lowerPipes):
uPipe["x"] += pipeVelX
lPipe["x"] += pipeVelX
## ajouter un nouveau tuyau lorsque le premier tuyau est sur le point de toucher la gauche de l'écran
if 3 > len(upperPipes) > 0 and 0 < upperPipes[0]["x"] < 5:
newPipe = getRandomPipe()
upperPipes.append(newPipe[0])
lowerPipes.append(newPipe[1])
## supprimer le premier tuyau s'il est hors de l'écran
if len(upperPipes) > 0 and upperPipes[0]["x"] < -IMAGES["pipe"][0].get_width():
upperPipes.pop(0)
lowerPipes.pop(0)
## dessiner les sprites
SCREEN.blit(IMAGES["background"], (0, 0))
for uPipe, lPipe in zip(upperPipes, lowerPipes):
SCREEN.blit(IMAGES["pipe"][0], (uPipe["x"], uPipe["y"]))
SCREEN.blit(IMAGES["pipe"][1], (lPipe["x"], lPipe["y"]))
SCREEN.blit(IMAGES["base"], (basex, BASEY))
## afficher le score pour que le joueur recouvre le score
showScore(score)
## La rotation du joueur a un seuil
visibleRot = playerRotThr
if playerRot <= playerRotThr:
visibleRot = playerRot
playerSurface = pygame.transform.rotate(
IMAGES["player"][playerIndex], visibleRot
)
SCREEN.blit(playerSurface, (playerx, playery))
pygame.display.update()
FPSCLOCK.tick(FPS)
- Nous définissons la fonction
mainGame, qui contient la logique principale du jeu Flappy Bird. - La fonction gère l'entrée utilisateur, met à jour l'état du jeu, vérifie les collisions et suit le score.
- La boucle de jeu tourne en continu, mettant à jour l'affichage et la logique du jeu.
- Les contrôles du joueur sont gérés via des événements clavier (espace ou flèche du haut).
- La fonction vérifie également les collisions avec les tuyaux et le sol, met à jour le score et gère l'animation de l'oiseau.
- La boucle de jeu continue jusqu'à ce que le joueur ait un crash ou quitte le jeu.
Afficher l'écran de fin de partie
Dans cette étape, nous allons créer l'écran de fin de partie qui apparaît lorsque le joueur perd.
def showGameOverScreen(crashInfo):
"""Fait tomber le joueur et affiche l'image de fin de partie"""
score = crashInfo["score"]
playerx = SCREENWIDTH * 0.2
playery = crashInfo["y"]
playerHeight = IMAGES["player"][0].get_height()
playerVelY = crashInfo["playerVelY"]
playerAccY = 2
playerRot = crashInfo["playerRot"]
playerVelRot = 7
basex = crashInfo["basex"]
upperPipes, lowerPipes = crashInfo["upperPipes"], crashInfo["lowerPipes"]
while True:
for event in pygame.event.get():
if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
pygame.quit()
sys.exit()
if event.type == KEYDOWN and (event.key == K_SPACE or event.key == K_UP):
if playery + playerHeight >= BASEY - 1:
return
## Décalage vertical du joueur
if playery + playerHeight < BASEY - 1:
playery += min(playerVelY, BASEY - playery - playerHeight)
## Changement de vitesse du joueur
if playerVelY < 15:
playerVelY += playerAccY
## Tourner seulement lorsqu'il y a eu une collision avec un tuyau
if not crashInfo["groundCrash"]:
if playerRot > -90:
playerRot -= playerVelRot
## Dessiner les sprites
SCREEN.blit(IMAGES["background"], (0, 0))
for uPipe, lPipe in zip(upperPipes, lowerPipes):
SCREEN.blit(IMAGES["pipe"][0], (uPipe["x"], uPipe["y"]))
SCREEN.blit(IMAGES["pipe"][1], (lPipe["x"], lPipe["y"]))
SCREEN.blit(IMAGES["base"], (basex, BASEY))
showScore(score)
playerSurface = pygame.transform.rotate(IMAGES["player"][1], playerRot)
SCREEN.blit(playerSurface, (playerx, playery))
SCREEN.blit(IMAGES["gameover"], (50, 180))
FPSCLOCK.tick(FPS)
pygame.display.update()
- La fonction
showGameOverScreenaffiche l'écran de fin de partie lorsque le joueur perd. - Elle affiche le score final du joueur, joue des effets sonores et attend que le joueur appuie sur la barre d'espace ou la flèche du haut pour redémarrer le jeu.
- L'animation comprend l'oiseau tombant sur le sol et le message de fin de partie affiché à l'écran.
Définir des fonctions d'aide
Dans cette étape, nous définissons des fonctions d'aide utilisées dans le jeu.
def playerShm(playerShm):
"""Fait osciller la valeur de playerShm['val'] entre 8 et -8"""
if abs(playerShm["val"]) == 8:
playerShm["dir"] *= -1
if playerShm["dir"] == 1:
playerShm["val"] += 1
else:
playerShm["val"] -= 1
def getRandomPipe():
"""Renvoie un tuyau généré aléatoirement"""
## y de l'espace entre le tuyau supérieur et le tuyau inférieur
gapY = random.randrange(0, int(BASEY * 0.6 - PIPEGAPSIZE))
gapY += int(BASEY * 0.2)
pipeHeight = IMAGES["pipe"][0].get_height()
pipeX = SCREENWIDTH + 10
return [
{"x": pipeX, "y": gapY - pipeHeight}, ## tuyau supérieur
{"x": pipeX, "y": gapY + PIPEGAPSIZE}, ## tuyau inférieur
]
def showScore(score):
"""Affiche le score au centre de l'écran"""
scoreDigits = [int(x) for x in list(str(score))]
totalWidth = 0 ## largeur totale de tous les chiffres à afficher
for digit in scoreDigits:
totalWidth += IMAGES["numbers"][digit].get_width()
Xoffset = (SCREENWIDTH - totalWidth) / 2
for digit in scoreDigits:
SCREEN.blit(IMAGES["numbers"][digit], (Xoffset, SCREENHEIGHT * 0.1))
Xoffset += IMAGES["numbers"][digit].get_width()
def checkCrash(player, upperPipes, lowerPipes):
"""Renvoie True si le joueur entre en collision avec la base ou les tuyaux."""
pi = player["index"]
player["w"] = IMAGES["player"][0].get_width()
player["h"] = IMAGES["player"][0].get_height()
## si le joueur heurte le sol
if player["y"] + player["h"] >= BASEY - 1:
return [True, True]
else:
playerRect = pygame.Rect(player["x"], player["y"], player["w"], player["h"])
pipeW = IMAGES["pipe"][0].get_width()
pipeH = IMAGES["pipe"][0].get_height()
for uPipe, lPipe in zip(upperPipes, lowerPipes):
## rectangles des tuyaux supérieur et inférieur
uPipeRect = pygame.Rect(uPipe["x"], uPipe["y"], pipeW, pipeH)
lPipeRect = pygame.Rect(lPipe["x"], lPipe["y"], pipeW, pipeH)
## masques de collision du joueur et des tuyaux supérieur/inferieur
pHitMask = HITMASKS["player"][pi]
uHitmask = HITMASKS["pipe"][0]
lHitmask = HITMASKS["pipe"][1]
## si l'oiseau a heurté le tuyau supérieur ou inférieur
uCollide = pixelCollision(playerRect, uPipeRect, pHitMask, uHitmask)
lCollide = pixelCollision(playerRect, lPipeRect, pHitMask, lHitmask)
if uCollide or lCollide:
return [True, False]
return [False, False]
def pixelCollision(rect1, rect2, hitmask1, hitmask2):
"""Vérifie si deux objets entrent en collision et pas seulement leurs rectangles"""
rect = rect1.clip(rect2)
if rect.width == 0 or rect.height == 0:
return False
x1, y1 = rect.x - rect1.x, rect.y - rect1.y
x2, y2 = rect.x - rect2.x, rect.y - rect2.y
for x in range(rect.width):
for y in range(rect.height):
if hitmask1[x1 + x][y1 + y] and hitmask2[x2 + x][y2 + y]:
return True
return False
def getHitmask(image):
"""Renvoie un masque de collision à partir de l'alpha d'une image."""
mask = []
for x in range(image.get_width()):
mask.append([])
for y in range(image.get_height()):
mask[x].append(bool(image.get_at((x, y))[3]))
return mask
- La fonction
playerShmfait osciller la valeur deplayerShm["val"]entre 8 et -8. Elle est utilisée pour déplacer l'oiseau vers le haut et le bas sur l'écran d'accueil. - La fonction
getRandomPiperenvoie un tuyau généré aléatoirement. Elle génère un espace aléatoire entre le tuyau supérieur et le tuyau inférieur. - La fonction
showScoreaffiche le score au centre de l'écran. Elle utilise la listeIMAGES["numbers"]pour afficher le score. - La fonction
checkCrashrenvoieTruesi le joueur entre en collision avec le sol ou les tuyaux. Elle utilise la fonctionpixelCollisionpour vérifier les collisions. - La fonction
pixelCollisionvérifie si deux objets entrent en collision et pas seulement leurs rectangles. Elle utilise la fonctiongetHitmaskpour obtenir le masque de collision du joueur et des tuyaux. - La fonction
getHitmaskrenvoie un masque de collision à partir de l'alpha d'une image. Elle utilise la fonctionimage.get_atpour obtenir la valeur alpha de chaque pixel de l'image.
La fonction principale
Dans cette étape, nous définissons la fonction principale qui initialise le jeu et démarre la boucle de jeu.
def main():
global SCREEN, FPSCLOCK
pygame.init()
FPSCLOCK = pygame.time.Clock()
SCREEN = pygame.display.set_mode((SCREENWIDTH, SCREENHEIGHT))
pygame.display.set_caption("Flappy Bird")
## sprites pour afficher le score
IMAGES["numbers"] = (
pygame.image.load("data/sprites/0.png").convert_alpha(),
pygame.image.load("data/sprites/1.png").convert_alpha(),
pygame.image.load("data/sprites/2.png").convert_alpha(),
pygame.image.load("data/sprites/3.png").convert_alpha(),
pygame.image.load("data/sprites/4.png").convert_alpha(),
pygame.image.load("data/sprites/5.png").convert_alpha(),
pygame.image.load("data/sprites/6.png").convert_alpha(),
pygame.image.load("data/sprites/7.png").convert_alpha(),
pygame.image.load("data/sprites/8.png").convert_alpha(),
pygame.image.load("data/sprites/9.png").convert_alpha(),
)
## sprite pour l'écran de fin de partie
IMAGES["gameover"] = pygame.image.load("data/sprites/gameover.png").convert_alpha()
## sprite pour le message de l'écran d'accueil
IMAGES["message"] = pygame.image.load("data/sprites/message.png").convert_alpha()
## sprite pour la base (sol)
IMAGES["base"] = pygame.image.load("data/sprites/base.png").convert_alpha()
while True:
## sélectionner des sprites d'arrière-plan aléatoires
randBg = random.randint(0, len(BACKGROUNDS_LIST) - 1)
IMAGES["background"] = pygame.image.load(BACKGROUNDS_LIST[randBg]).convert()
## sélectionner des sprites de joueur aléatoires
randPlayer = random.randint(0, len(PLAYERS_LIST) - 1)
IMAGES["player"] = (
pygame.image.load(PLAYERS_LIST[randPlayer][0]).convert_alpha(),
pygame.image.load(PLAYERS_LIST[randPlayer][1]).convert_alpha(),
pygame.image.load(PLAYERS_LIST[randPlayer][2]).convert_alpha(),
)
## sélectionner des sprites de tuyau aléatoires
pipeindex = random.randint(0, len(PIPES_LIST) - 1)
IMAGES["pipe"] = (
pygame.transform.flip(
pygame.image.load(PIPES_LIST[pipeindex]).convert_alpha(), False, True
),
pygame.image.load(PIPES_LIST[pipeindex]).convert_alpha(),
)
## masque de collision pour les tuyaux
HITMASKS["pipe"] = (
getHitmask(IMAGES["pipe"][0]),
getHitmask(IMAGES["pipe"][1]),
)
## masque de collision pour le joueur
HITMASKS["player"] = (
getHitmask(IMAGES["player"][0]),
getHitmask(IMAGES["player"][1]),
getHitmask(IMAGES["player"][2]),
)
movementInfo = showWelcomeAnimation()
crashInfo = mainGame(movementInfo)
showGameOverScreen(crashInfo)
- La fonction
maininitialise le jeu, configure l'affichage et démarre la boucle de jeu. - Elle charge les ressources du jeu, y compris les images, et sélectionne aléatoirement les sprites d'arrière-plan, de joueur et de tuyau.
- La boucle de jeu gère l'animation d'accueil, le jeu principal et l'écran de fin de partie.
- Le jeu continue de boucler jusqu'à ce que le joueur quitte le jeu ou ferme la fenêtre.
Lancer le jeu
Dans cette étape, nous allons exécuter le jeu Flappy Bird.
if __name__ == "__main__":
main()
- La condition
__name__ == "__main__"vérifie si le module actuel est exécuté seul ou importé par un autre module. - Si le module actuel est exécuté seul, la fonction
mainest appelée pour démarrer le jeu.
Une fois que vous avez terminé toutes les étapes, vous pouvez exécuter le jeu Flappy Bird en utilisant la commande suivante :
cd ~/projet
python flappy.py

Résumé
Dans ce projet, nous avons divisé le code du jeu Flappy Bird en plusieurs étapes et fourni des explications pour chaque étape. Vous avez appris à créer une structure de base de jeu, à gérer l'entrée du joueur, à mettre à jour l'état du jeu, à vérifier les collisions et à afficher les écrans de jeu. Vous pouvez maintenant exécuter et jouer au jeu Flappy Bird à l'aide du code fourni. Amusez-vous bien dans votre parcours de développement de jeux!



