Projets
Dans le cadre du projet libre de fin d’année en NSI, je vous présente mon premier jeux vidéo en python fait à l’aide de pygame, une librairie spécialisé pour la création de jeux vidéo.
Sommaire
Librairie Pygame
Pygame est une bibliothèque de jeu open-source pour Python, qui permet de créer des jeux et des applications multimédias. Elle fournit des outils pour gérer les graphismes, le son, les entrées utilisateur, le réseau et plus encore.
Pygame utilise la bibliothèque SDL (Simple DirectMedia Layer) pour accéder aux fonctions du système d’exploitation, ce qui signifie que les jeux créés avec Pygame peuvent fonctionner sur de nombreuses plateformes, notamment Windows, macOS et Linux.
Pygame est une excellente option pour les débutants qui cherchent à créer des jeux simples (des base en programmation sont cependant nécessaire), mais il peut également être utilisé pour créer des jeux professionnels et des applications multimédias complexes.
Le Projet
Mon objectif sur ce projet a été de faire mes premiers pas avec la bibliothèque Pygame en Python. Cela a été un véritable défi qui m’aura fait découvrir de nombreuses mécaniques en Python, telles que le système de classe ou la programmation orientée objet, qui ont représenté pour moi un pas dans l’inconnu. Mon défi a donc été de créer un jeu simple pour débuter. Le but est de contrôler un personnage avec sa souris et d’éviter des briques qui apparaissent aléatoirement sur le côté de l’écran, tout cela en ayant le choix entre trois niveaux de difficulté différents.
Structure du Script
Pour la réalisation de mon jeu, j’ai décidé de me lancer un défi supplémentaire : l’utilisation de la Programmation Orientée Objet. Mais qu’est-ce que la Programmation Orientée Objet ?
La Programmation Orientée Objet est un paradigme de programmation qui permet de structurer un programme en utilisant des objets, qui sont des entités possédant des caractéristiques et des comportements spécifiques. Cette approche permet de modéliser des concepts du monde réel de manière plus naturelle et intuitive, en regroupant des fonctionnalités liées dans des classes, et en instanciant ces classes pour créer des objets.
En résumé, cela consiste à organiser son code de manière à ce qu’il soit ordonné à l’aide de classes contenant des fonctions et des constantes générales, ce qui conduit à un code plus lisible, plus maintenable et plus ordonné. Cependant, cela peut être relativement perturbant au début.
Voici un simple scripte sans utilisation de Programmation Orienté Objet qui permet de faire bougé une image nommé « ball.png » à l’aide des flèches directionnelles :
import pygame
pygame.init()
screen = pygame.display.set_mode((400, 400))
running = True
image = pygame.image.load("ball.png")
x = 0
y = 0
clock = pygame.time.Clock()
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pressed = pygame.key.get_pressed()
if pressed[pygame.K_LEFT]:
x -= 1
if pressed[pygame.K_RIGHT]:
x += 1
if pressed[pygame.K_UP]:
y -= 1
if pressed[pygame.K_DOWN]:
y += 1
screen.fill((0, 0, 0))
screen.blit(image, (x, y))
pygame.display.flip()
clock.tick(60)
pygame.quit()code by TNtube
Le scripte est simple et efficace, pour autant si il venais à se complexifier cela deviendrait vite illisible.
voici maintenant le même scripte avec l’utilisation de la Programmation Orienté Objet :
player.py
import pygame
class Player:
def __init__(self, x, y):
self.image = pygame.image.load("player.png")
self.image
self.rect = self.image.get_rect(x=x, y=y)
self.speed = 5
self.velocity = [0, 0]
def move(self):
self.rect.move_ip(self.velocity[0] * self.speed, self.velocity[1] * self.speed)
def draw(self, screen):
screen.blit(self.image, self.rect)Main.py
import pygame
from player import Player
class Game:
def __init__(self, screen):
self.screen = screen
self.running = True
self.clock = pygame.time.Clock()
self.player = Player(0, 0)
def handling_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.player.velocity[0] = -1
elif keys[pygame.K_RIGHT]:
self.player.velocity[0] = 1
else:
self.player.velocity[0] = 0
if keys[pygame.K_UP]:
self.player.velocity[1] = -1
elif keys[pygame.K_DOWN]:
self.player.velocity[1] = 1
else:
self.player.velocity[1] = 0
def update(self):
pass
def display(self):
self.screen.fill("white")
pygame.display.flip()
def run(self):
while self.running:
self.handling_events()
self.update()
self.display()
self.clock.tick(60)
pygame.init()
screen = pygame.display.set_mode((1080, 720))
game = Game(screen)
game.run()
pygame.quit()code by TNtube
La particularité ici réside dans la clarté de chaque fonction et de leur utilité respective : « display » pour l’affichage, « handling_event » pour les interactions claviers-souris, « move » pour les mouvements du joueur… De plus, le programme est divisé en deux fichiers distincts : l’un pour le programme principal et l’autre pour le joueur. Ainsi, il est possible d’ajouter aisément de nouvelles fonctions pour des améliorations futures. Par exemple, la fonction « Update », actuellement inactive, pourrait être employée pour effectuer des vérifications, telles que les collisions.
Analyse du script
Passons maintenant à l’analyse du code.
Commençons par mon fichier « player.py », avec l’appel du module Pygame, la création de la classe et la fonction d’initialisation.
import pygame #On importe le module Pygame
class Player: #On crée la classe Player
# La fonction init est une fonction d'initialisation qui permet de déclarer toutes nos variables.
# Elle prend comme arguments self, qui est pris dans chaque fonction de la classe, x et y qui servent à définir la position initiale du joueur sur l'écran.
def init(self, x, y):
self.image = pygame.image.load("asset/Player.png")# On charge l'image du joueur
#Pour que vous puissiez être familiarisé avec le concept de rect, ce sont des rectangles que l'on ne voit pas, qui ont une taille que l'on leur définit et c'est grâce à ces rect, que l'on pourrait appeler hitbox, que l'on peut détecter des collisions.
self.rect = self.image.get_rect(center=(x, y))# On récupère le rectangle de l'image et on le centre en (x,y)
self.velocity = [0, 0]# On initialise la vitesse à 0 dans les deux directionsUne fois la mise en place de notre classe et de la fonction d’initialisation effectuée, nous créons la fonction de mouvement du joueur appelée « move », ainsi qu’une fonction « draw » qui permettra d’afficher notre personnage avec son rectangle.
def move(self, mouse_pos):# La fonction move permet de déplacer le joueur en fonction de la position de la souris, l'argument mouse_pos représente la position de la souris sur l'écran. Cette position est utilisée pour calculer la vitesse de déplacement du joueur, en soustrayant la position de la souris à la position actuelle du joueur.
# On calcule la différence entre la position de la souris et la position actuelle du joueur
self.velocity[0] = (mouse_pos[0] - self.rect.centerx)
self.velocity[1] = (mouse_pos[1] - self.rect.centery)
self.rect.move_ip(self.velocity)# On déplace le rectangle du joueur en fonction de la vitesse calculée
def draw(self, screen):# La fonction draw permet d'afficher le joueur sur l'écran
screen.blit(self.image, self.rect)# On blitte (st utilisée pour dessiner une surface sur une autre surface.) l'image du joueur sur le rectangle de celui-ciMaintenait que je vous ai introduis mon fichier joueur passons au fichier jeu.
import pygame import random from player import Player
On importe les modules nécessaires au fonctionnement du jeu : Pygame pour gérer l’affichage graphique et l’interaction avec l’utilisateur, random pour générer des nombres aléatoires, et Player qui est une classe définie dans un fichier séparé et qui représente le joueur.
class Game:
def __init__(self, screen):
self.screen = screen
self.running = True
self.clock = pygame.time.Clock()
self.player = Player(60, 60)
self.background = pygame.image.load("asset/background.jpg")
self.brique = pygame.image.load("asset/brique.jpg")
self.brique_rects = []
self.brique_positions = []
self.brique_speed = 3
self.brique_spawn_rate = 120
self.brique_spawn_counter = 0
self.brique_spawn_augmentation = 15
self.brique_spawn_increase_counter = 0
self.total_time = 0
self.total_time_max = 0
self.difficulté = 20
pygame.mouse.set_visible(False)
choix = input("_____________________________________________________________________________ \n choisissez votre difficulté : \n 1- Facile \n 2- Normal \n 3- Difficile \n _____________________________________________________________________________ \n Votre choix : ")
choix = int(choix)
if choix == 1:
self.difficulté = 1
elif choix == 2:
self.difficulté = 5
elif choix == 3:
self.difficulté = 10
else:
print("Error : s'il vous plait entré un entier positif entre 1 et 3")
self.running = FalseOn définit la classe Game, qui représente le jeu en lui-même. Son constructeur prend en paramètre l’objet screen qui représente la surface sur laquelle le jeu sera affiché.
On initialise différentes variables, comme self.running qui permet de savoir si le jeu est en cours d’exécution ou non, self.player qui représente le joueur, self.background qui est l’image de fond, self.brique qui est l’image représentant les obstacles à éviter, et plusieurs variables liées à ces obstacles (self.brique_positions, self.brique_speed, self.brique_spawn_rate, self.brique_spawn_counter, self.brique_spawn_augmentation, self.brique_spawn_increase_counter).
On définit également self.total_time et self.total_time_max pour gérer le temps de jeu, et self.difficulté qui est utilisé pour régler la difficulté du jeu en fonction de la réponse de l’utilisateur à une question posée par input.
Enfin, on masque le curseur de la souris avec pygame.mouse.set_visible(False).
def gestion_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
self.player.move(pygame.mouse.get_pos())La méthode gestion_events permet de gérer les événements liés à l’interaction de l’utilisateur avec la fenêtre Pygame.
Le code utilise une boucle for pour récupérer chaque événement dans la liste des événements de Pygame. Si l’un des événements est de type QUIT, cela signifie que l’utilisateur a cliqué sur le bouton de fermeture de la fenêtre. Dans ce cas, la variable self.running est définie sur False, ce qui interrompt la boucle de jeu.
La méthode appelle également la méthode move de l’objet Player pour mettre à jour sa position en fonction de la position de la souris de l’utilisateur.
def adaptative_brique_rects(self):
self.brique_rects = []
for pos in self.brique_positions:
rect = pygame.Rect(pos[0], pos[1], self.brique.get_width(), self.brique.get_height())
self.brique_rects.append(rect)La méthode adaptative_brique_rects est appelée pour mettre à jour les rects de collision des briques en fonction de leurs nouvelles positions.
def spawn_brique(self):
y = random.randint(0, self.screen.get_height() - self.brique.get_height())
self.brique_positions.append((-self.brique.get_width(), y))La méthode spawn_brique est appelée pour générer une nouvelle brique à une position aléatoire sur l’axe y et avec une position initiale en dehors de la fenêtre sur l’axe x.
def move_briques(self):
for i in range(len(self.brique_positions)):
pos = list(self.brique_positions[i])
pos[0] += self.brique_speed
self.brique_positions[i] = tuple(pos)
self.adaptative_brique_rects()La méthode move_briques met à jour la position des briques en ajoutant la valeur de self.brique_speed à la coordonnée x de chaque position dans self.brique_positions.
def update(self):
# Incrémenter le compteur de temps de spawn des briques par la difficulté du jeu
self.brique_spawn_counter += self.difficulté
# Augmenter la vitesse des briques si le compteur de spawn des briques est un multiple de 120
if self.brique_spawn_counter % 120 == 0:
self.brique_speed += 0.2
# Vérifier si le compteur d'augmentation de spawn des briques a atteint sa limite
self.brique_spawn_increase_counter += 1
if self.brique_spawn_increase_counter >= self.brique_spawn_augmentation * 60:
self.brique_spawn_increase_counter = 0
self.brique_spawn_rate -= 10
# Ajouter une nouvelle brique à la liste des positions de briques si le compteur de spawn des briques a atteint le taux de spawn des briques
if self.brique_spawn_counter >= self.brique_spawn_rate:
self.brique_spawn_counter = 0
self.spawn_brique()
# Déplacer toutes les briques dans la direction de la vitesse actuelle des briques
self.move_briques()
# Incrémenter le temps total de jeu de 1
self.total_time += 1
# Vérifier si le joueur a perdu en entrant en collision avec une brique
for rect in self.brique_rects:
if rect.colliderect(self.player.rect):
print("_____________________________________________________________________________\n")
print(" Vous avez perdu, Votre temps est de", self.total_time/100, "secondes, bien joué à vous ;)")
print("_____________________________________________________________________________\n")
self.running = False
break
else:
passLa méthode update est appelée à chaque boucle de jeu pour mettre à jour les positions des briques, vérifier si le joueur a perdu en entrant en collision avec une brique et pour augmenter la difficulté du jeu.
Elle commence par incrémenter le compteur de temps de spawn des briques par la difficulté du jeu, pour que les briques apparaissent plus rapidement avec des niveaux de difficulté plus élevés.
Ensuite, si le compteur de spawn des briques est un multiple de 120, la vitesse de déplacement des briques est augmentée de 0,2.
La méthode vérifie également si le compteur d’augmentation de spawn des briques a atteint sa limite, qui est définie par self.brique_spawn_augmentation * 60. Si c’est le cas, le compteur est réinitialisé et le taux de spawn des briques est diminué de 10.
Si le compteur de spawn des briques atteint le taux de spawn des briques défini par self.brique_spawn_rate, la méthode spawn_brique est appelée pour ajouter une nouvelle brique à la liste des positions de briques.
La méthode move_briques est ensuite appelée pour déplacer toutes les briques dans la direction de la vitesse actuelle des briques.
Le temps total de jeu est ensuite incrémenté de 1.
Enfin, pour chaque rect de brique dans la liste self.brique_rects, la méthode vérifie s’il y a une collision avec le rectangle de la zone de collision du joueur. Si c’est le cas, la méthode affiche le message de fin de jeu et met la variable self.running à False pour arrêter la boucle de jeu.
def display(self):
# Afficher le fond d'écran
self.screen.blit(self.background, (-200, -350))
# Parcourir toutes les positions des briques et les afficher
for pos in self.brique_positions:
self.screen.blit(self.brique, pos)
# Afficher le joueur
self.player.draw(self.screen)
# Mettre à jour l'affichage
pygame.display.flip()La méthode display(self) affiche les éléments du jeu sur l’écran. Elle utilise la méthode blit pour afficher le fond d’écran, chaque brique de la liste brique_positions et le joueur (player) sur l’écran. Ensuite, elle appelle la méthode flip de la classe pygame.display pour mettre à jour l’affichage.
def run(self):
# Boucle principale du jeu
while self.running:
# Gérer les événements (ex: appuyer sur une touche, quitter le jeu)
self.gestion_events()
# Mettre à jour le jeu (ex: déplacer les briques, vérifier les collisions)
self.update()
# Afficher le jeu
self.display()
# Limiter le nombre de frames par seconde à 60
self.clock.tick(60)
# Réafficher la souris et quitter Pygame
pygame.mouse.set_visible(True)
pygame.quit()
La méthode run(self) contient la boucle principale du jeu. Elle s’exécute tant que l’attribut running est vrai. À chaque itération de la boucle, elle gère les événements (appel à gestion_events()), met à jour les éléments du jeu (appel à update()), affiche le contenu du jeu sur l’écran (appel à display()) et attend un certain temps défini par la méthode tick de pygame.time.Clock. Enfin, elle quitte le jeu (pygame.quit()) lorsque la boucle est terminée.
# Initialiser Pygame pygame.init() # Créer une fenêtre de jeu screen = pygame.display.set_mode((1080, 720)) # Créer une instance de la classe Game en lui passant la fenêtre de jeu en argument game = Game(screen) # Lancer le jeu game.run() # Quitter Pygame pygame.quit()
La partie en dehors de la classe initialise la bibliothèque Pygame (pygame.init()) et crée une fenêtre de jeu (pygame.display.set_mode((1080, 720))). Ensuite, elle crée une instance de la classe Game avec l’écran en tant qu’argument et appelle la méthode run de l’instance pour démarrer le jeu. Enfin, elle quitte la bibliothèque Pygame (pygame.quit()) après la fin du jeu.
Sources :
- TNtube pour la base de mon code
- Graven – Développement pour l’organisation du code
- Clear Code
- Tech With Tim pour la programmation orienté objet
- ChatGPT

Conclusion
En conclusion, ce projet est un petit jeu simple mais amusant, il est sur l’univers de Mario. Si vous voulez essayer ce jeu par vous-même, vous pouvez le télécharger en cliquant sur le lien ci-dessous. Pour lancer le jeu, assurez-vous d’écrire le niveau de difficulté souhaité (1, 2 ou 3) dans la console Python. Si vous ne le faites pas, la fenêtre restera noire et le jeu ne se lancera pas. On commande le personnage avec sa souris.
Amusez-vous bien !
Étudiant en classe de première au lycée Louis Pasteur d’Avignon (2022-2023).
Actuellement dans la faille de l’invocateur (je vous attend tous en 1v1 : jmebbBGF43 #EUW) …
1 commentaire
ez