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 directions
Une 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-ci
Maintenait 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 = False
On 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: pass
La 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