Spécifie une image comme marqueur list-item
Tutoriel, Dans cette seconde partie nous allons entamer la finalisation du jeu en ajoutant le contrôle du joueur à l'aide du clavier, dessinant les ennemies et finalisant le code du jeu
Nous rappelons qu'à la fin de la première partie de ce tutoriel nous avons obtenu le code suivant:
Exemple : 📋 Copier le code
# Importer le module pygame import pygame # Importer pygame.locals pour faciliter l'accès aux coordonnées clés # Mise à jour pour se conformer aux normes flake8 et black from pygame.locals import ( K_UP, K_DOWN, K_LEFT, K_RIGHT, K_ESCAPE, KEYDOWN, QUIT, ) # Définir des constantes pour la largeur et la hauteur de l'écran SCREEN_WIDTH = 400 SCREEN_HEIGHT = 300 # Définir un objet joueur en étendant pygame.sprite.Sprite # La surface dessinée sur l'écran est maintenant un attribut de 'joueur' class Player(pygame.sprite.Sprite) : def __init__(self) : super(Player, self).__init__() self.surf = pygame.Surface((75, 25)) self.surf.fill((255, 255, 255)) self.rect = self.surf.get_rect() # Initialiser pygame pygame.init() # Créer l'objet écran # La taille est déterminée par les constantes SCREEN_WIDTH et SCREEN_HEIGHT ecran = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) # Instanciation du joueur. Pour l'instant, c'est juste un rectangle. joueur = Player() # Variable pour maintenir la boucle principale en cours d'exécution running = True # Boucle principale while running : # Boucle de recherche dans la file d'attente des événements for event in pygame.event.get() : # Vérifier la présence d'un événement KEYDOWN if event.type == KEYDOWN : # Était-ce la touche Escape ? Si c'est le cas, arrêter la boucle. if event.key == K_ESCAPE : running = False # Vérifier s'il y a un événement QUIT. Si c'est le cas, mettre running à false. elif event.type == QUIT : running = False # Remplir l'écran de noir ecran.fill((0, 0, 0)) # Dessine le joueur sur l'écran ecran.blit(joueur.surf, (SCREEN_WIDTH/2, SCREEN_HEIGHT/2)) # Mettre à jour l'affichage pygame.display.flip() pygame.quit()
Jusqu'à présent dans la 1ére partie, nous avons appris à configurer pygame et à dessiner des objets sur l'écran. Maintenant, les choses sérieuses commencent ! Nous allons faire en sorte que le joueur puisse être contrôlé à l'aide du clavier.
Dans les cours sur pygame, nous avons vu que pygame.event.get() renvoie une liste d'événements dans la file d'attente des événements, que nous recherchons pour les types d'événements KEYDOWN. Eh bien, ce n'est pas la seule façon de lire les pressions de touches. pygame fournit également pygame.event.get_pressed(), qui renvoie un dictionnaire contenant tous les événements KEYDOWN actuels dans la file d'attente.
Plaçons cette fonction dans notre boucle de jeu, juste après la boucle de gestion des événements. Ceci renvoie un dictionnaire contenant les touches pressées au début de chaque image.
Voici le bout du code à insérer dans la boucle principale à partir de la ligne 54:
# Obtenir l'ensemble des touches pressées et vérifier l'entrée de l'utilisateur pressed_keys = pygame.key.get_pressed()
Ensuite, nous écrivons une méthode dans Player pour accepter ce dictionnaire. Cela définira le comportement du sprite en fonction des touches pressées. En voici un exemple, insérer le code suivant avant
# Initialiser pygame
pygame.init() à partir de la ligne 29 :
# Déplace le sprite en fonction des touches pressées par l'utilisateur def update(self, pressed_keys): if pressed_keys[K_UP]: self.rect.move_ip(0, -5) if pressed_keys[K_DOWN]: self.rect.move_ip(0, 5) if pressed_keys[K_LEFT]: self.rect.move_ip(-5, 0) if pressed_keys[K_RIGHT]: self.rect.move_ip(5, 0)
K_UP, K_DOWN, K_LEFT et K_RIGHT correspondent aux touches fléchées du clavier. Si l'entrée du dictionnaire pour cette touche est True, alors elle est enfoncée et nous déplaçons le .rect du joueur dans la bonne direction. Ici, nous utilisons .move_ip(), qui signifie déplacer sur place, pour déplacer le Rect actuel.
Nous pouvons ensuite appeler .update() à chaque image pour déplacer le sprite du joueur en fonction de la pression des touches. Ajouter l'appel à cette fonction juste après l'appel à la fonction .get_pressed() :
Ce qui donne le code complet comme suit :
Exemple : 📋 Copier le code
# Importation du module pygame import pygame # Importer pygame.locals pour un accès plus facile aux coordonnées des touches from pygame.locals import ( K_UP, K_DOWN, K_LEFT, K_RIGHT, K_ESCAPE, KEYDOWN, QUIT, ) # Définir des constantes pour la largeur et la hauteur de l'écran LARGEUR_ECRAN = 800 HAUTEUR_ECRAN = 600 # Définir un objet joueur en étendant pygame.sprite.Sprite class Joueur(pygame.sprite.Sprite): def __init__(self): super(Joueur, self).__init__() self.surf = pygame.Surface((75, 25)) self.surf.fill((255, 255, 255)) self.rect = self.surf.get_rect() # Déplacer le sprite en fonction des touches utilisées par l'utilisateur def update(self, touches_appuyees): if touches_appuyees[K_UP]: self.rect.move_ip(0, -5) if touches_appuyees[K_DOWN]: self.rect.move_ip(0, 5) if touches_appuyees[K_LEFT]: self.rect.move_ip(-5, 0) if touches_appuyees[K_RIGHT]: self.rect.move_ip(5, 0) # Initialiser pygame pygame.init() # Créer l'objet écran ecran = pygame.display.set_mode((LARGEUR_ECRAN, HAUTEUR_ECRAN)) # Instancier le joueur. Pour l'instant, il s'agit simplement d'un rectangle. joueur = Joueur() # Variable pour maintenir la boucle principale en cours d'exécution en_marche = True # Boucle principale while en_marche: # Boucle à travers la file d'événements for evenement in pygame.event.get(): # Vérifier l'événement KEYDOWN if evenement.type == KEYDOWN: # Si la touche Échap est pressée, quitter la boucle principale if evenement.key == K_ESCAPE: en_marche = False # Vérifier l'événement QUIT. S'il est présent, définir en_marche sur False. elif evenement.type == QUIT: en_marche = False # Obtenir toutes les touches actuellement enfoncées touches_appuyees = pygame.key.get_pressed() # Mettre à jour le sprite du joueur en fonction des touches appuyées par l'utilisateur joueur.update(touches_appuyees) # Remplir l'écran en noir ecran.fill((0, 0, 0)) # Dessiner le joueur à l'écran ecran.blit(joueur.surf, joueur.rect) # Mettre à jour l'affichage pygame.display.flip() # Quitter pygame correctement pygame.quit()Vous pouvez maintenant utiliser les touches fléchées pour déplacer le rectangle du joueur sur l'écran.
Nous remarquons deux petits problèmes :
Pour que le joueur reste à l'écran, il faut ajouter une logique permettant de détecter si le rectangle se déplace hors de l'écran. Pour ce faire, nous vérifions si les coordonnées du rectangle se sont déplacées au-delà de la limite de l'écran. Si c'est le cas, nous demandons au programme de le ramener au bord de l'écran :
Voici le code :
Exemple : 📋 Copier le code
# Déplacer le sprite en fonction des touches utilisées par l'utilisateur def update(self, touches_appuyees): if touches_appuyees[K_UP]: self.rect.move_ip(0, -5) if touches_appuyees[K_DOWN]: self.rect.move_ip(0, 5) if touches_appuyees[K_LEFT]: self.rect.move_ip(-5, 0) if touches_appuyees[K_RIGHT]: self.rect.move_ip(5, 0) # Garder le joueur à l’intérieur de l'écran if self.rect.left < 0: self.rect.left = 0 if self.rect.right > LARGEUR_ECRAN: self.rect.right = LARGEUR_ECRAN if self.rect.top <= 0: self.rect.top = 0 if self.rect.bottom >= HAUTEUR_ECRAN: self.rect.bottom = HAUTEUR_ECRAN
Ce morceau de code est responsable de la gestion des limites de l'écran pour le déplacement du joueur afin de s'assurer qu'il reste entièrement visible dans la fenêtre de jeu. Voici une explication ligne par ligne :
# Garder le joueur à l’intérieur de l'écran if self.rect.left < 0: self.rect.left = 0
Cette ligne vérifie si le côté gauche du joueur (la coordonnée x la plus à gauche de son rectangle) est en dehors de la zone de jeu (c'est-à-dire, si sa position est plus petite que 0, donc hors de l'écran à gauche).
Si c'est le cas, cela déplace le côté gauche du joueur vers la position x = 0, ce qui le ramène à l'intérieur de l'écran.
if self.rect.right > LARGEUR_ECRAN: self.rect.right = LARGEUR_ECRAN
Cette ligne vérifie si le côté droit du joueur (la coordonnée x la plus à droite de son rectangle) dépasse la largeur de l'écran (c'est-à-dire, si sa position est supérieure à la largeur de l'écran).
Si c'est le cas, cela ajuste le côté droit du joueur pour qu'il soit égal à la largeur de l'écran, le ramenant ainsi à l'intérieur de la zone de jeu.
if self.rect.top <= 0: self.rect.top = 0
Cette ligne vérifie si le haut du joueur (la coordonnée y la plus haute de son rectangle) est au-dessus de la zone de jeu (c'est-à-dire, si sa position est inférieure ou égale à 0, donc hors de l'écran en haut).
Si c'est le cas, cela ajuste la position du haut du joueur à y = 0, le ramenant à l'intérieur de la zone de jeu.
if self.rect.bottom >= HAUTEUR_ECRAN: self.rect.bottom = HAUTEUR_ECRAN
Cette ligne vérifie si le bas du joueur (la coordonnée y la plus basse de son rectangle) dépasse la hauteur de l'écran (c'est-à-dire, si sa position est supérieure ou égale à la hauteur de l'écran).
Si c'est le cas, cela ajuste la position du bas du joueur à la hauteur de l'écran, le ramenant ainsi à l'intérieur de la zone de jeu.
En résumé, ces lignes de code garantissent que le joueur ne puisse pas sortir des limites de l'écran en ajustant sa position s'il dépasse les bords de la fenêtre de jeu.
Maintenant, ajoutons des ennemis !
Que ferait un jeu sans ennemis ? Nous allons utiliser les techniques que nous avons déjà apprises et créer une classe d'ennemis de base, puis en créer un grand nombre pour que le joueur tente de les éviter. Tout d'abord, importez la bibliothèque random :
Exemple : 📋 Copier le code
# Importation de la bibliothèque random import random
Puis nous créons une nouvelle classe de sprites appelée Enemy, en suivant le même modèle que celui utilisé pour Player :
Exemple : 📋 Copier le code
# Définir l'objet ennemi en étendant pygame.sprite.Sprite # La surface que vous dessinez à l'écran est maintenant un attribut de 'ennemi' class Ennemi(pygame.sprite.Sprite): def __init__(self): super(Ennemi, self).__init__() self.surf = pygame.Surface((20, 10)) self.surf.fill((255, 255, 255)) self.rect = self.surf.get_rect( center=( random.randint(LARGEUR_ECRAN + 20, LARGEUR_ECRAN + 100), random.randint(0, HAUTEUR_ECRAN), ) ) self.speed = random.randint(5, 20) # Déplacer le sprite en fonction de la speed # Supprimer le sprite lorsqu'il dépasse le bord gauche de l'écran def update(self): self.rect.move_ip(-self.speed, 0) if self.rect.right < 0: self.kill()
Il existe quatre grandes différences entre Enemy et Player :
self.rect = self.surf.get_rect( center=( random.randint(LARGEUR_ECRAN + 20, LARGEUR_ECRAN + 100), random.randint(0, HAUTEUR_ECRAN), )nous mettons à jour rect pour qu'il soit un endroit aléatoire le long du bord droit de l'écran. Le centre du rectangle est juste en dehors de l'écran. Il est situé à une distance comprise entre 20 et 100 pixels du bord droit, et quelque part entre les bords supérieur et inférieur.
self.vitesse = random.randint(5, 20)
nous définissons .speed comme un nombre aléatoire compris entre 5 et 20. Ce nombre indique la vitesse à laquelle l'ennemi se déplace vers le joueur.def update(self): self.rect.move_ip(-self.speed, 0)nous définissons .update(). Elle ne prend aucun argument puisque les ennemis se déplacent automatiquement. En revanche, .update() déplace l'ennemi vers la gauche de l'écran à la vitesse .speed définie lors de sa création.
if self.rect.right < 0: self.kill()
nous vérifions si l'ennemi s'est déplacé hors de l'écran. Pour s'assurer que l'ennemi est complètement hors de l'écran et qu'il ne disparaîtra pas simplement alors qu'il est encore visible, nous vérifions que le côté droit du .rect a dépassé le côté gauche de l'écran. Une fois que l'ennemi est hors de l'écran, vous appelez .kill() pour l'empêcher d'être traité ultérieurement.Mais que fait donc .kill() ? Pour le savoir, vous devez connaître les groupes de sprites.
Une autre classe super utile fournie par pygame est le Sprite Group. C'est un objet qui contient un groupe d'objets Sprite. Alors pourquoi l'utiliser ? Ne pouvons-nous pas simplement suivre nos objets Sprite dans une liste à la place ? Eh bien, nous le pouvons, mais l'avantage d'utiliser un groupe réside dans les méthodes qu'il contient. Ces méthodes permettent de détecter si un ennemi est entré en collision avec le joueur, ce qui facilite grandement les mises à jour.
Voyons comment créer des groupes de sprites. Nous allons créer deux objets Group différents :
Le premier groupe contiendra tous les sprites du jeu.
Le second ne contiendra que les objets Enemy.
Voici à quoi cela correspond dans le code :
Exemple : 📋 Copier le code
... ... ... # Instancier le joueur. Pour l'instant, il s'agit simplement d'un rectangle. joueur = Joueur() # Créer des groupes pour contenir les sprites des ennemis et tous les sprites # - 'ennemis' est utilisé pour la détection de collision et les mises à jour de position # - 'all_sprites' est utilisé pour le rendu ennemis = pygame.sprite.Group() tous_sprites = pygame.sprite.Group() tous_sprites.add(joueur) # Variable pour maintenir la boucle principale en cours d'exécution en_marche = True ... ... ...
Voici l'explication du code :
1. `ennemis = pygame.sprite.Group()` : Cette ligne crée un groupe de sprites nommé `ennemis`. Les groupes de sprites dans Pygame sont des collections qui contiennent et gèrent plusieurs sprites. Dans ce cas, nous créons un groupe spécifique pour les sprites ennemis.
2. `all_sprites = pygame.sprite.Group()` : Cette ligne crée un autre groupe de sprites nommé `all_sprites`. Ce groupe est destiné à contenir tous les sprites du jeu, y compris le joueur et les ennemis.
3. `all_sprites.add(joueur)` : Ici, nous ajoutons le sprite `joueur` (qui représente le joueur) au groupe `all_sprites`. Le sprite `joueur` fait ainsi partie de la collection gérée par `tous_sprites`.
Lorsque nous appelons .kill(), le Sprite est supprimé de tous les groupes auxquels il appartient. Les références au Sprite sont également supprimées, ce qui permet au gestionnaire de Python de récupérer la mémoire si nécessaire.
Maintenant que nous avons un groupe all_sprites, nous pouvons modifier la façon dont les objets sont dessinés. Au lieu d'appeler .blit() sur le seul joueur, nous pouvons itérer sur tout ce qui se trouve dans all_sprites :
Exemple : 📋 Copier le code
# Remplir l'écran en noir ecran.fill((0, 0, 0)) # Dessiner tous les sprites à l'écran for entity in all_sprites: ecran.blit(entity.surf, entity.rect) # Mettre à jour l'affichage pygame.display.flip()
Maintenant, tout ce qui est placé dans all_sprites sera dessiné à chaque frame, qu'il s'agisse d'un ennemi ou du joueur.
Mais il y a un problème... Nous n'avons pas d'ennemis ! Nous pourrions créer un tas d'ennemis au début du jeu, mais le jeu deviendrait rapidement ennuyeux lorsqu'ils quitteraient tous l'écran quelques secondes plus tard. Voyons plutôt comment assurer un approvisionnement régulier en ennemis au fur et à mesure que le jeu progresse.
La conception exige que les ennemis apparaissent à intervalles réguliers. Cela signifie que nous devrons faire deux choses à certains intervalles :
Nous disposons déjà d'un code permettant de gérer les événements aléatoires. La boucle d'événements est conçue pour rechercher les événements aléatoires qui se produisent à chaque image et les traiter de manière appropriée. Heureusement, pygame ne nous limite pas à utiliser les types d'événements qu'il définit. Vous pouvez définir vos propres événements et les gérer comme vous le souhaitez.
Voyons comment créer un événement personnalisé qui sera généré toutes les quelques secondes. Nous pouvons créer un événement personnalisé en lui donnant un nom :
Exemple : 📋 Copier le code
..... # Créer l'objet écran ecran = pygame.display.set_mode((LARGEUR_ECRAN, HAUTEUR_ECRAN)) # Créer un événement personnalisé pour l'ajout d'un nouvel ennemi ADDENEMY = pygame.USEREVENT + 1 pygame.time.set_timer(ADDENEMY, 250) # Instancier le joueur. Pour l'instant, il s'agit simplement d'un rectangle. joueur = Joueur() ....
pygame définit les événements en interne comme des entiers, vous devez donc définir un nouvel événement avec un entier unique. Comme le dernier événement réservé par pygame s'appelle USEREVENT, la définition de ADDENEMY = pygame.USEREVENT + 1 à la ligne 83 permet de s'assurer qu'il est unique.
Ensuite, nous devons ajouter ce nouvel événement à la file d'attente des événements à intervalles réguliers tout au long du jeu. C'est là que le module de temps entre en jeu.
On déclenche le nouvel événement ADDENEMY toutes les 250 millisecondes, soit quatre fois par seconde. Nous appelons .set_timer() en dehors de la boucle du jeu parce que nous n'avons besoin que d'un seul timer, mais il se déclenchera tout au long du jeu.
Exemple : 📋 Copier le code
# Boucle principale while en_marche: # Boucle à travers la file d'événements for evenement in pygame.event.get(): # Vérifier l'événement KEYDOWN if evenement.type == KEYDOWN: # Si la touche Échap est pressée, quitter la boucle principale if evenement.key == K_ESCAPE: en_marche = False # Vérifier l'événement QUIT. S'il est présent, définir en_marche sur False. elif evenement.type == QUIT: en_marche = False # Ajout d'un nouveau ennemi elif evenement.type == ADDENEMY: # Create the new enemy and add it to sprite groups new_enemy = Ennemi() ennemis.add(new_enemy) all_sprites.add(new_enemy) # Obtenir toutes les touches actuellement enfoncées touches_appuyees = pygame.key.get_pressed() # Mettre à jour le sprite du joueur en fonction des touches appuyées par l'utilisateur joueur.update(touches_appuyees) # Mise à jour de la position de ennemis ennemis.update()
Chaque fois que le gestionnaire d'événement voit le nouvel événement ADDENEMY, il crée un Ennemi et l'ajoute aux groupes ennemis et all_sprites. Puisque Ennemi est dans all_sprites, il sera dessiné à chaque frame. Nous devons également appeler ennemis.update(), qui met à jour tout ce qui se trouve dans le groupe ennemis, afin de s'assurer qu'ils se déplacent correctement :
Cependant, ce n'est pas la seule raison pour laquelle nous avons créé un groupe spécifique pour les ennemis. Et c'est ce que nous allons voir dans ce qui suit
Nous nous attendons à ce que le jeu s'arrête dès qu'un ennemi entre en collision avec le joueur. La vérification des collisions est une technique de base dans la programmation de jeux, et nécessite généralement des mathématiques non triviales pour déterminer si deux sprites vont se chevaucher.
C'est là qu'un framework comme pygame s'avère utile ! Écrire du code de détection de collision est fastidieux, mais pygame dispose de beaucoup de méthodes de détection de collision que nous pouvons utiliser.
Pour ce tutoriel, nous utiliserons une méthode appelée .spritecollideany(), qui peut être lue comme "sprite collide any". Cette méthode prend un sprite et un groupe comme paramètres. Elle examine chaque objet du groupe et vérifie si son .rect croise le .rect d'un sprite. Si c'est le cas, elle renvoie True. Sinon, il renvoie False. C'est parfait pour ce jeu, car nous devons vérifier si le joueur unique entre en collision avec l'un des groupes d'ennemis.
Voici le code :
Exemple : 📋 Copier le code
# Dessiner tous les sprites for entity in all_sprites: ecran.blit(entity.surf, entity.rect) # Vérifier si des ennemis ont collisionné avec le joueur if pygame.sprite.spritecollideany(joueur, ennemis): # Si c'est le cas, supprimer le joueur et arrêter la boucle joueur.kill() en_marche = False
Maintenant, habillons-le un peu, rendons-le plus jouable et lui ajoutons quelques fonctionnalités avancées pour qu'il se démarque.