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
Table des matières
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
xetyà 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")
# 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
# récupère la liste des états des touches
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and not keys[pygame.K_RIGHT]:
# la touche flèche gauche est appuyée mais pas la flèche droite
player.move_left()
if keys[pygame.K_RIGHT] and not keys[pygame.K_LEFT]:
# la touche flèche droite est appuyée
player.move_right()
if keys[KEY_SPACE]:
# la touche espace est appuyée
# 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)

