Outils pour utilisateurs

Outils du site


nsi:tds:pygame:zombies

Ceci est une ancienne révision du document !



Warning: Undefined array key 1 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 172

Warning: Undefined array key 1 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 149

Warning: Trying to access array offset on value of type null in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 149

Warning: Undefined array key 1 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 172

Warning: Undefined array key 1 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 149

Warning: Trying to access array offset on value of type null in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 149

Warning: Undefined array key 1 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 172

Warning: Undefined array key 1 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 149

Warning: Trying to access array offset on value of type null in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 149

Warning: Undefined array key 1 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 172

Warning: Undefined array key 1 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 172

Warning: Undefined array key 1 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 172

Warning: Undefined array key 1 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 172

Warning: Undefined array key 1 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 172

Warning: Undefined array key 1 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 172

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Warning: Undefined array key 2 in /home/goupillf/wiki.goupill.fr/lib/plugins/codeprettify/syntax/code.php on line 214

Attaque de zombies avec Pygame

Des mort vivants attaquent. Il faut les abattre. Difficile de faire plus simple…

La figure ci-dessus schématise l'espace de jeu :

  • les morts-vivants, en jaune, apparaissent aléatoirement en haut et avancent vers le bas en ligne droite,
  • le joueur, en rouge en bas, se déplace horizontalement, commandé par le clavier,
  • le joueur tir des balles de fusil, carrés blancs, qui se déplacent vers le haut et percutent éventuellement des morts-vivants.

Chacun de ses éléments est un sprite, c'est à dire un élément graphique mobile dans le jeu. Dans Pygame on définira une classe pour chacun d'eux, c'est à dire un modèle définissant leurs propriétés. Une fois que les propriétés d'une balle ou d'un mort-vivant sont définies, on peut les créer, les dupliquer…

On fait le choix de définir un fichier par classe.

La classe Undead

Nous ne créons pas la classe Undead définissant un mort-vivant à partir de rien : nous créons une classe qui hérite de pygame.sprite.Sprite, une classe de Pygame qui définit déjà tout un tas de comportement. C'est bien sûr l'intérêt d'un module comme Pygame : nous n'avons pas à tout faire !

Je détaille ci-dessous ce que nous avons à faire.

# Fichier undead.py

import pygame

class Undead(pygame.sprite.Sprite):
    SIZE = 40              # taille du sprite en pixels
    COLOR = (245, 242, 66) # couleur du sprite en RGB - du jaune
    VELOCITY = 10          # nombre de pixels parcourus à chaque image
    def __init__(self, x):
        """
        x: position x de création du mort-vivant
        Cette fonction crée un mort-vivant en haut de l'écran.
        """
        # on commence par demander toutes les initialisations
        # requises par l'objet de base défini dans pygame.sprite.Sprite :
        super().__init__()

        # un sprite doit avoir un attribut image.
        # Nous pourrions charger un fichier image, mais ici, nous
        # nous contentons de créer un rectangle
        self.image = pygame.Surface((self.SIZE, self.SIZE))

        # nous remplissons ce rectangle dans la couleur choisie
        self.image.fill(self.COLOR)
        
        # le sprite a un attribut rect qui est un objet définissant
        # le cadre délimitant le sprite
        self.rect = self.image.get_rect()
        # on règle le centre rectangle à la position choisie
        self.rect.centerx= x
        # la position y = 0 est en haut, on laisse une marge
        self.rect.y = 10
    
    def update_position(self):
        """
        Tenant compte du mouvement du mort-vivant, calcule
        la nouvelle position du mort-vivant
        """
        self.rect.y += self.VELOCITY

Plus d'aide sur rect.

Classe Bullet

Dans un fichier bullet.py on définit une classe Bullet.

Quasiment la même chose avec ces différences :

  • l'aspect graphique un peu différent : c'est un carré plus petit et blanc,
  • la vitesse est plus élevée,
  • il faut préciser x et y à la création,
  • la balle va vers le haut et non vers le bas,

Balle perdue

Si le self.rect.y devient <0, cela signifie que la balle est sorti de l'écran par le haut. C'est une balle perdue. On peut la détruire. On utilise la méthode self.kill() qui se charge de supprimer l'objet.

Cela se passe dans la méthode update_position().

Classe player

Dans un fichier player.py on définit la classe Player.

Le fichier suivant est complet.

# fichier player.py

import pygame
from bullet import Bullet

class Player(pygame.sprite.Sprite):
    COLOR = (255, 0, 0)
    VELOCITY = 5
    SIZE = 50

    def __init__(self, x0, y0, xmin, xmax):
        """
        x0, y0: position initiale
        xmin, xmax: valeurs à ne pas dépasser
        """
        super().__init__()
        self.image = pygame.Surface((self.SIZE, self.SIZE))
        self.image.fill(self.COLOR)
        self.rect = self.image.get_rect()
        self.rect.centerx = x0
        self.rect.centery = y0
        self.xmin = xmin
        self.xmax = xmax
        self.vx = 0

    def move_left(self):
        """
        amorce un mouvement vers la gauche
        """
        self.vx = -self.VELOCITY

    def move_right(self):
        """
        amorce un mouvement vers la droite
        """
        self.vx = self.VELOCITY

    def fire(self):
        """
        crée une balle à la position courante du joueur
        """
        return Bullet(self.rect.centerx, self.rect.centery)

    def update_position(self):
        """
        Calcule la nouvelle position tenant compte de la vitesse
        courante et sans dépasser les limites
        """
        self.rect.centerx += self.vx
        if self.rect.centerx > self.xmax:
            # il ne faut pas dépasser à droite
            self.rect.centerx = self.xmax
        elif self.rect.centerx < self.xmin:
            # il ne faut pas dépasser à gauche
            self.rect.centerx = self.xmin
        # à chaque réactualisation, la vitesse est amortie
        # pour créer un effet de ralentissement
        self.vx *= 0.8
        # quand la vitesse devient assez faible, on la met à 0
        if abs(self.vx) < 1:
            self.vx = 0

Le programme principal

On crée un fichier main.py, programme principal.

# fichier main.py

import pygame
from random import randint

# import des modules des éléments de jeu
from player import Player
from undead import Undead

# gestion du rythme de rafraîchissement écran,
# notamment pour que le jeu s'écoule au même rythme
# quelque soit la puissance de la machine.
clock = pygame.time.Clock() # gestion du temps

# réglage des paramètres de jeu
# c'est plus propre de le faire en définissant des constantes
LARGEUR = 750
HAUTEUR = 750


# création surface de jeu
screen = pygame.display.set_mode((LARGEUR, HAUTEUR))

# titre de la fenêtre
pygame.display.set_caption("L'attaque des morts-vivants")

# Gère le cas d'une touche restant appuyée
pygame.key.set_repeat(10, 22)

# Il sera parfois utile de grouper les sprites.
# Par exemple, il peut être pratique de demander quelque chose
# à l'ensemble des morts-vivants. De plus, certaines fonctions
# de Pygame n'existent que sur des groupes, comme les fonctions
# permettant de détecter une collision de sprites.
# un même sprite peut être dans plusieurs groupes

all_sprites = pygame.sprite.Group() # groupe contenant tous les sprites
undeads_sprites = pygame.sprite.Group() # groupe pour les morts-vivants
bullets_sprites = pygame.sprite.Group() # groupe pour les balles

# création du sprite joueur
player = Player(50, HAUTEUR - Player.SIZE - 10, 0, LARGEUR)
# ajout du sprite au groupe de tous les sprites
all_sprites.add(player)

# on enferme le jeu dans une boucle infinie.
# la sortie de la boucle est gérée par la valeur de running
running = True

# on décide d'envoyer un nouveau mort-vivant toutes les 150 images
FRAMES_BETWEEN_UNDEAD = 150
# pour cela on initialise un décompteur.
# Quand il tombe à 0, un mort-vivant apparaît
frames_count_down = FRAMES_BETWEEN_UNDEAD

# boucle principale. Elle se répète indéfiniment pour
# rafraîchir l'affichage
while running:
    # limitation du taux de rafraîchissement à 60 images / seconde
    clock.tick(60)
    
    # Avant de dessiner la prochaine image, on repeint le cadre en noir
    # ce qui efface tout ce qu'il y a eu avant
    screen.fill((0,0,0))

    frames_count_down -= 1
    if frames_count_down == 0:
        # apparition d'un mort-vivant
        # calcul de la position aléatoire
        x = randint(100, LARGEUR-100)
        # création du mort-vivant
        new_undead = Undead(x)
        # ajout du mort-vivants aux groupes de sprites
        undeads_sprites.add(new_undead)
        all_sprites.add(new_undead)
        # réinitialisation du décompteur
        frames_count_down = FRAMES_BETWEEN_UNDEAD

    # actualisation de l'affichage de tous les sprites
    for sprite in all_sprites:
        sprite.update_position()
    all_sprites.draw(screen)

    # lecture des événements
    # les événement sont notamment les demandes de l'utilisateur
    # souris, clavier, demande de fermeture de la fenêtre...
    for event in pygame.event.get():
        if event.type == pygame.QUIT: # fermeture fenêtre
            running = False
        elif event.type == pygame.KEYDOWN: # c'est une touche qu'on appuie
            if event.key == pygame.K_LEFT: # flèche gauche
                player.move_left()
            elif event.key == pygame.K_RIGHT: # flèche droite
                player.move_right()
            elif event.key == pygame.K_SPACE: # barre d'espace
                # création d'une balle
                newBullet = player.fire()
                # ajout du sprite dans les groupes
                all_sprites.add(newBullet)
                bullets_sprites.add(newBullet)

    # parcours des morts-vivants pour savoir si l'un d'eux a reçu une balle
    for undead in undeads_sprites:
        # On utilise la fonction spritecollide
        # le dernier argument, True, signifie que la balle touchée est détruite
        hits = pygame.sprite.spritecollide(undead, bullets_sprites, True)
        # hits contient la liste des balles
        # il nous suffit de savoir si hits est vide ou non
        if len(hits) > 0:
            undead.kill()

    # mise à jour de l'affichage
    pygame.display.flip()

# quand la boucle while se termine, on peut quitter le jeu
pygame.quit()

Encore beaucoup de travail...

Si vous avez su créer la classe Bullet, le jeu devrait fonctionner. Mais il reste encore beaucoup à faire !

  • que se passe-t-il si un mort-vivant n'est pas abattu et atteint la ligne du bas ?
  • comptage et affichage des points ?
  • les morts-vivants pourraient arriver par vague.
  • les morts-vivants pourraient avoir une barre de vie et ne mas mourir d'une seule balle.
  • certains morts-vivants pourraient avoir plus ou moins de vie, être plus ou moins gros.
  • remplacer les sprites géométriques par des images ?
  • permettre au joueur de changer d'armes.
  • limiter les munitions du joueur, limiter le débit, contraindre le joueur à recharger.
  • les morts-vivants ne marchent pas en ligne droite.
  • les morts-vivants jettent des projectiles sur le joueur.
  • etc. Vous êtes libres d'ajouter vos propres idées !

Exemple de panneau de score

On souhaite ajouter un panneau qui affiche un score et une barre de vie pour le joueur.

On aura besoin d'ajouter un peu de texte. Il faut ajouter :

# dans main, après l'import de pygame
pygame.init()

On peut ajouter un module pour le score. Il consistera en un simple rectangle avec un texte pour le score et une barre pour la vie.

# fichier score.py

import pygame

class Score(pygame.sprite.Sprite):
    COLOR = (100, 100, 100)      # couleur du fond. Un gris
    LIFE_COLOR = (100, 255, 100) # couleur de la vie. Un vert
    LIFE_HEIGHT = 10             # hauteur de la barre de vie
    WIDTH=200                    # largeur du panneau
    HEIGHT=50                    # hauteur du panneau

    def __init__(self):
        super().__init__()
        self.image = pygame.Surface((self.WIDTH, self.HEIGHT))

        self.rect = self.image.get_rect()
        self.rect.x = 0
        self.rect.y = 0

        self.score = 0
        self.life = 100
        # on doit initialiser une objet pour le texte
        self.font = pygame.font.SysFont('timesnewroman', 30)
        # le panneau ne change pas beaucoup
        # on se contente de le rafraîchir quand nécessaire
        self.refresh()


    def up_score(self, amount:int):
        """
        augmente le score de la quantité amount
        réactualise l'affichage
        """
        self.score += amount
        self.refresh()

    def down_life(self, amount:int):
        """
        diminue le score de la quantité amount
        ne peut descendre en dessous de 0
        réactualise l'affichage
        """
        self.life -= amount
        if self.life < 0:
            self.life = 0
        self.refresh()

    def refresh(self):
        """
        reconstruit le contenu du panneau
        """
        self.image.fill(self.COLOR)
        # remarquez le formatage du score
        textsurface = self.font.render(f'Score : {self.score:04d}', False, (0, 0, 0))
        # blit permet de placer les pixels du texte dans self.image
        self.image.blit(textsurface,(0,0))
        
        # construction de la barre de vie: un simple rectangle
        # aligné en bas du panneau
        largeur_barre = int(self.WIDTH / 100 * self.life)
        barre = pygame.Surface((largeur_barre, self.LIFE_HEIGHT))
        barre.fill(self.LIFE_COLOR)
        rect_barre = barre.get_rect()
        rect_barre.bottom = self.rect.bottom
        # là encore, blit permet de placer les pixels de la barre dans self.image
        self.image.blit(barre, rect_barre)

    def draw(self, screen):
        # Les autres sprites du jeu sont placés dans des groupes de sprites
        # les groupes ont une fonction draw qui fait la même chose que celle ci.
        # comme notre score sera à part, on le dote du fonction draw
        # qui reçoit l'écran de jeu et y place le contenu de self.image
        screen.blit(self.image, self.rect)

À chaque nouvelle image, l'affichage est effacé. Il faut donc redessiner le panneau de score à chaque fois. On complète donc `main.py` :

    # actualisation de l'affichage de tous les sprites
    for sprite in all_sprites:
        sprite.update_position()
    all_sprites.draw(screen)
    score.draw(screen)

Et on souhaite qu'à chaque mort-vivant abattu on gagne par exemple 10 pts. On complète main.py.

    for undead in undeads_sprites:
        # On utilise la fonction sprtecollide
        # le dernier argument, True, signifie que la balle touchée est détruite
        hits = pygame.sprite.spritecollide(undead, bullets_sprites, True)
        # hits contient la liste des balles
        # il nous suffit de savoir si hits est vide ou non
        if len(hits) > 0:
            undead.kill()
            score.up_score(10)

On voudrait enfin que chaque fois qu'un mort vivant dépasse la ligne du bas de l'écran, le joueur perde un peu de vie. On ajoute une méthode dans undead.py :

    def accross(self, y):
        """
        y: ordonnée
        renvoie True si le undead dépasse l'ordonnée y
        """
        return self.rect.top > y

Et on complète main.py :

    for undead in undeads_sprites:
        # On utilise la fonction sprtecollide
        # le dernier argument, True, signifie que la balle touchée est détruite
        hits = pygame.sprite.spritecollide(undead, bullets_sprites, True)
        # hits contient la liste des balles
        # il nous suffit de savoir si hits est vide ou non
        if len(hits) > 0:
            undead.kill()
            score.up_score(10)
        elif undead.accross(HAUTEUR):
            undead.kill()
            score.down_life(10)
nsi/tds/pygame/zombies.1673960454.txt.gz · Dernière modification : de goupillwiki