Chimp - Ligne par ligne

Contributeurs Wikibooks

Les exemples de Pygame incluent un petit programme avec un poing interactif et un singe. Il a été inspiré par une bannière flash du début des années 2000. Ce tutoriel étudie chaque ligne de code utilisée dans cet exemple.

Commentez

- Titre : Chimp - Ligne par ligne

- Auteur : Contributeurs Wikibooks

- Parution : 5 novembre 2013

- Licence : Le contenu de cet article est rédigé par Contributeurs Wikibooks et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 3.0 non transposé. Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright ® 2013 Developpez.com.

Sommaire

- I. Introduction

- II. Importer des modules

- III. Chargement des ressources

- IV. Classes d'objets du jeu

- V. Tout initialiser

- VI. Créer l'arrière-plan

- VII. Appliquer le texte sur l'arrière-plan et le centrer

- VIII. Afficher l'arrière-plan une fois les paramètres définis

- IX. Préparer les objets du jeu

- X. La boucle principale

- XI. Gérer tous les évènements d'entrée

- XII. Actualiser les sprites

- XIII. Dessiner la scène entière

- XIV. Game Over

- XV. Remerciements

I - Introduction

Dans les exemples de Pygame vous pouvez trouver un petit programme nommé chimp. Cet exemple simule un singe à frapper qui bouge dans un petit écran avec des promesses de récompenses. Cet exemple est vraiment simple, et les codes pour la vérification d'erreurs sont légers. Cet exemple de programme démontre les possibilités de Pygame, comme la création de fenêtres graphiques, le chargement de fichiers d'images et de sons, de rendu de texte TTF (police TrueType) et la gestion simple des évènements et des mouvements de la souris.

Ce programme ainsi que les images sont disponibles dans les sources de Pygame. Pour la version 1.3 de Pygame, cet exemple a été complètement réécrit pour ajouter quelques fonctions et corriger les vérifications d'erreurs. Ceci fait que la taille de l'exemple a doublé par rapport à l'original, mais nous donne plus de matière à analyser. Je ne peux que vous recommander de le réutiliser pour vos propres projets.

Ce tutoriel analyse le code bloc par bloc, expliquant comment le code fonctionne. Il sera également mentionné la façon dont le code pourrait être amélioré et quel contrôle d'erreur peut nous venir en aide.

Ceci est un excellent tutoriel pour les personnes qui étudient pour la première fois du code Pygame. Une fois Pygame complètement installé, vous pourrez trouver et exécuter vous-même la démo chimp dans le répertoire des exemples.

(non, ceci n'est pas une publicité, c'est une capture d'écran)

II - Importer des modules

Voici le code qui importe tous les modules nécessaires dans notre programme. Aussi, il vérifie la disponibilité de certains des modules optionnels de Pygame.

import os, sys
import pygame
from pygame.locals import *

if not pygame.font: print 'Attention, polices désactivées'
if not pygame.mixer: print 'Attention, son désactivé'

D'abord, nous importons les modules standards de Python os et sys. Ceux-ci nous permettent de faire certaines actions comme créer des chemins de fichiers indépendants du système d'exploitation.

Dans la ligne suivante, nous importons l'ensemble des modules de Pygame. Quand Pygame est importé, tous les modules appartenant à Pygame sont importés. Certains modules sont optionnels, s'ils ne sont pas trouvés, leur valeur est définie à None.

Il existe un module Pygame spécial nommé locals. Ce module est un sous-ensemble de Pygame. Les membres de ce module utilisent couramment des constantes et des fonctions qui ont prouvé leur utilité à être incorporées dans l'espace de nom global de votre programme. Ce module de locales inclut des fonctions comme « Rect » pour un objet rectangle et plusieurs constantes comme « QUIT », « HWSURFACE » qui sont utilisées pour interagir avec le reste de Pygame. L'importation de ce module de locales dans l'espace de nommage global est complètement optionnelle. Si vous choisissez de ne pas l'importer, tous les membres des locales sont toujours disponibles dans le module pygame.

Enfin, nous avons décidé d'afficher un joli message si les modules font ou sound ne sont pas disponibles dans Pygame.

III - Chargement des ressources

Ici nous avons deux fonctions que nous pouvons utiliser pour charger des images et des sons. Nous examinerons chaque fonction individuellement dans cette section.

def load_image(name, colorkey=None):
fullname = os.path.join('data', name)
try:
image = pygame.image.load(fullname)
except pygame.error, message:
print "Impossible de charger l'image :", name
raise SystemExit, message
image = image.convert()
if colorkey is not None:
if colorkey is -1:
colorkey = image.get_at((0,0))
image.set_colorkey(colorkey, RLEACCEL)
return image, image.get_rect()

Cette fonction prend le nom de l'image à charger. Elle prend également un argument optionnel qui peut être utilisé pour définir une couleur-clé à l'image. Une couleur-clé est utilisée dans les graphismes pour représenter une couleur de l'image qui devra être transparente.

La première chose que cette fonction fait est de créer un chemin de fichier complet vers le fichier. Dans cet exemple, toutes les ressources sont situées dans le sous-répertoire data. En utilisant la fonction os.path.join(), un chemin sera créé qui fonctionnera, quelle que soit la plateforme sur laquelle est lancé le jeu.

Ensuite, nous chargeons l'image en utilisant la fonction pygame.image.load(). Nous enveloppons cette fonction dans un bloc de try/except, ainsi s'il y a un problème lors du chargement de l'image, nous pouvons quitter élégamment. Une fois l'image chargée, nous faisons un appel important à la fonction convert(). Ceci crée une nouvelle copie de la Surface et la convertit dans un format et une profondeur de couleurs qui correspondent à l'affichage en cours. Ceci signifie que le blitage de l'image sur l'écran sera aussi rapide que possible.

Enfin, nous définissons la couleur-clé de l'image. Si l'utilisateur fournit un argument pour la couleur-clé, nous utiliserons cette valeur de couleur-clé pour l'image. Ceci devrait être habituellement une valeur RGB, comme (255, 255, 255) pour le blanc. Vous pouvez également passer la valeur -1 comme couleur-clé. Dans ce cas, la fonction examinera la couleur en haut à gauche de l'image, et utilisera cette couleur comme couleur-clé.

def load_sound(name):
class NoneSound:
def play(self): pass
if not pygame.mixer:
return NoneSound()
fullname = os.path.join('data', name)
try:
sound = pygame.mixer.Sound(fullname)
except pygame.error, message:
print 'Impossible de charger le son :', wav
raise SystemExit, message
return sound

Vient ensuite la fonction de chargement des fichiers son. La première chose que cette fonction effectue est de contrôler si le module pygame.mixer a été importé correctement. Si ce n'est pas le cas, elle retourne une petite instance de classe qui possède une méthode de lecture factice. Ceci agira comme un objet Son normal pour ce jeu qui tournera sans contrôle d'erreur supplémentaire.

Cette fonction est similaire à la fonction de chargement d'images, mais gère des problèmes différents. En premier lieu nous créons un chemin complet vers le fichier son, et chargeons ce fichier son à travers un bloc try/except, qui nous retourne alors l'objet Son chargé.

IV - Classes d'objets du jeu

Ici nous créons deux classes qui représentent les objets dans notre jeu. La majeure partie de la logique de jeu vient de ces deux classes. Nous les examinerons dans cette section.

class Fist(pygame.sprite.Sprite):
"""Déplacer un poing fermé sur l'écran qui suit la souris"""
def __init__(self):
pygame.sprite.Sprite.__init__(self)        #Appel du constructeur de Sprite
self.image, self.rect = load_image('fist.bmp', -1)
self.punching = 0

def update(self):
"Déplace le poing sur la position de la souris"
pos = pygame.mouse.get_pos()
self.rect.midtop = pos
if self.punching:
self.rect.move_ip(5, 10)

def punch(self, target):
"Renvoie true si le poing entre en collision avec la cible"
if not self.punching:
self.punching = 1
hitbox = self.rect.inflate(-5, -5)
return hitbox.colliderect(target.rect)

def unpunch(self):
"Appelé pour faire revenir le poing"
self.punching = 0

Ici nous créons une classe pour représenter le poing du joueur. Elle est dérivée de la classe Sprite incluse dans le module pygame.sprite. La méthode __init__() est appelée lorsqu'une nouvelle instance de cette classe est créée. La première chose que nous faisons est de s'assurer d'appeler la méthode __init__() de notre classe de base. Ceci autorise la méthode __init__() de Sprite à préparer notre objet pour l'utiliser comme un sprite. Ce jeu utilise un des groupes de classes de dessin de sprite. Ces classes peuvent dessiner des sprites qui possèdent un attribut image et rect. En changeant tout simplement ces deux attributs, le moteur de rendu dessinera les images actuelles à leur position actuelle.

Tous les sprites possèdent une méthode update(). Cette méthode est généralement appelée une fois par image. C'est le lieu où vous pouvez mettre le code qui déplace et actualise les variables de chaque sprite. La méthode update() pour le poing déplace ce poing vers l'endroit où pointe la souris. Elle compense légèrement la position du poing si celui est en état de frappe.

Les deux fonctions suivantes punch() et unpunch() modifient l'état du poing. La méthode punch() retourne la valeur true si le poing entre en collision avec le sprite cible.

class Chimp(pygame.sprite.Sprite):
"""Déplace un singe à travers l'écran. Elle peut faire tournoyer
le singe quand il est frappé."""
def __init__(self):
pygame.sprite.Sprite.__init__(self)        #Appel du constructeur de Sprite
self.image, self.rect = load_image('chimp.bmp', -1)
screen = pygame.display.get_surface()
self.area = screen.get_rect()
self.rect.topleft = 10, 10
self.move = 9
self.dizzy = 0

def update(self):
"Déplace ou fait tournoyer, suivant l'état du singe"
if self.dizzy:
self._spin()
else:
self._walk()

def _walk(self):
"Déplacer le singe à travers l'écran, et le faire pivoter à la fin"
newpos = self.rect.move((self.move, 0))
if not self.area.contains(newpos):
if self.rect.left < self.area.left or \
self.rect.right > self.area.right:
self.move = -self.move
newpos = self.rect.move((self.move, 0))
self.image = pygame.transform.flip(self.image, 1, 0)
self.rect = newpos

def _spin(self):
"Faire tournoyer l'image du singe"
center = self.rect.center
self.dizzy += 12
if self.dizzy >= 360:
self.dizzy = 0
self.image = self.original
else:
rotate = pygame.transform.rotate
self.image = rotate(self.original, self.dizzy)
self.rect = self.image.get_rect(center=center)

def punched(self):
"Entraine le tournoiement du singe"
if not self.dizzy:
self.dizzy = 1
self.original = self.image

La classe Chimp fait un peu plus de travail que celle du poing, mais rien de complexe. Cette classe déplacera le chimpanzé de gauche à droite sur l'écran. Quand le singe sera frappé, il tournoiera sur lui-même avec un effet amusant. Cette classe est dérivée de la classe de base Sprite, et est initialisée de la même façon que celle du poing. Pendant l'initialisation, la classe définit l'attribut area comme dimension de l'affichage.

La fonction update() du singe vérifie simplement l'état actuel, lequel est true quand le singe tournoie après un coup de poing. Elle appelle la méthode _spin ou _walk. Ces fonctions sont préfixées d'un underscore. C'est un idiome Python qui suggère que ces méthodes devraient uniquement être utilisées à l'intérieur de la classe Chimp. Nous pourrions aller plus loin en leur attribuant un double-underscore, qui indiquera à Python de réellement essayer d'en faire des méthodes privées, mais nous n'avons pas besoin de ce type de protection. :)

La méthode _walk crée une nouvelle position pour le singe, en décalant le rect actuel. Si cette nouvelle position se situe à l'extérieur de la zone d'affichage de l'écran, elle inverse le déplacement élémentaire. Elle inverse également le sens de l'image en utilisant la fonction pygame.transform.flip(). C'est un effet rudimentaire qui permet au singe d'inverser le sens de son image suivant son déplacement.

La méthode _spin() est appelée quand le singe est étourdi (dizzy). L'attribut dizzy est utilisé pour enregistrer le nombre de rotations actuel. Quand le singe a entièrement tournoyé sur lui-même (360 degrés), il réinitialise l'image du singe à sa version droite originale. Avant d'appeler la fonction transform.rotate(), vous verrez que le code crée une référence locale à la fonction nommée rotate(). Il n'y a pas lieu de le faire pour cet exemple, nous l'avons uniquement fait ici pour conserver une longueur raisonnable à la ligne suivante. À noter qu'en appelant la fonction rotate(), nous faisons toujours tournoyer l'image originale du singe. Pendant la rotation, il y a une légère perte de qualité. Effectuer une rotation de façon répétitive sur la même image entraine au fur et à mesure une dégradation de l'image. Quand une image tourne sur elle-même, sa dimension sera modifiée. Ceci est dû au fait que les coins de l'image sortent de la dimension originale pendant la rotation et augmentent alors les dimensions de l'image. Nous nous assurons que le centre de la nouvelle image correspond au centre de l'ancienne image, de cette façon elle tournoie sans se déplacer.

La dernière méthode punched() indique que le sprite entre dans son état étourdi (dizzy). Ceci entrainera le tournoiement de l'image. Elle fera également une copie de l'actuelle image appelée original.

V - Tout initialiser

Avant d'aller plus loin avec Pygame, nous devons nous assurer que tous ses modules sont initialisés. Dans ce cas nous ouvrirons une simple fenêtre graphique. Maintenant nous sommes dans la fonction principale du programme, celle qui exécute tout.

pygame.init()
screen = pygame.display.set_mode((468, 60))
pygame.display.set_caption('Monkey Fever')
pygame.mouse.set_visible(0)

La première ligne d'initialisation de Pygame nous épargne un peu de travail. Elle contrôle les modules Pygame importés et tente d'initialiser chacun d'entre eux. Il est possible de vérifier si des modules n'ont pas échoué pendant l'initialisation, mais nous ne nous tracasserons pas avec ça. Il est ainsi possible d'effectuer un contrôle plus fin et d'initialiser chaque module spécifique à la main. Ce type de contrôle n'est généralement pas indispensable, mais il est disponible si besoin.

Ensuite, nous définissons le mode d'affichage. À noter que le module pygame.display est utilisé pour contrôler tous les paramètres d'affichage. Dans cet exemple, nous demandons une simple fenêtre. Il existe un tutoriel complet sur le paramétrage du mode graphique, mais nous n'en avons pas réellement besoin, Pygame effectue déjà du bon travail pour nous en obtenant quelque chose qui fonctionne. Pygame choisit la meilleure profondeur de couleur lorsque nous n'en indiquons pas.

Enfin nous définissons le titre de la fenêtre et désactivons le curseur de la souris de notre fenêtre. Très simple à faire, nous avons maintenant une petite fenêtre noire prête à recevoir nos requêtes. En fait le curseur est par défaut visible, ainsi il n'y a pas réellement besoin de définir son état tant que nous ne voulons pas le cacher.

VI - Créer l'arrière-plan

Notre programme affichera un message textuel en arrière-plan. Il serait bon pour nous de créer une simple surface pour représenter l'arrière-plan et l'utiliser à chaque fois. La première étape sera de créer cette surface.

background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill((250, 250, 250))

Ceci crée pour nous une nouvelle surface qui est de la même taille que la fenêtre d'affichage. À noter l'appel supplémentaire à la méthode convert() après la création de la surface. La méthode convert() sans argument s'assure que notre arrière-plan est du même format que la fenêtre d'affichage, ce qui nous donnera des résultats plus rapides.

Nous remplissons alors entièrement l'arrière-plan avec une couleur blanchâtre. La méthode fill() prend un triplet RGB en argument de couleur.

VII - Appliquer le texte sur l'arrière-plan et le centrer

Maintenant que nous avons une surface d'arrière-plan, affichons du texte. Nous le ferons uniquement si nous voyons que le module pygame.font a été importé correctement. Sinon nous passons cette section.

if pygame.font:
font = pygame.font.Font(None, 36)
text = font.render("Pummel The Chimp, And Win $$$", 1, (10, 10, 10))
textpos = text.get_rect(centerx=background.get_width()/2)
background.blit(text, textpos)

Comme vous le voyez, il y a une paire d'étapes pour son obtention. D'abord nous créons un objet font et en faisons un rendu sur la nouvelle surface. Nous trouvons alors le centre de cette nouvelle surface et la blitons sur l'arrière-plan.

La police est créée avec le constructeur Font() du module font. En fait, nous passons le nom du fichier de police TrueType à cette fonction, mais nous pouvons aussi passer None et utiliser la police par défaut. Le constructeur Font() nécessite de connaître la taille de la police que nous désirons créer.

Nous faisons alors un rendu de cette police dans la nouvelle surface. La fonction render() crée une nouvelle surface d'une taille appropriée à notre texte. Dans cet exemple, nous dirons aussi à la fonction render() de créer un texte anti-aliasé (pour obtenir un effet lissé) et d'utiliser une couleur gris sombre.

Ensuite nous avons besoin de trouver le centre du texte sur l'affichage. Nous créons un objet Rect qui nous permet de l'assigner facilement au centre de l'écran.

Enfin, nous blitons le texte sur l'image d'arrière-plan.

VIII - Afficher l'arrière-plan une fois les paramètres définis

Nous avons encore une fenêtre noire sur l'écran. Affichons notre arrière-plan pendant que nous attendons de charger les autres ressources.

screen.blit(background, (0, 0))
pygame.display.flip()

Ceci blitera notre arrière-plan complet sur la fenêtre d'affichage. Le blit est lui-même trivial, mais qu'en est-il de la routine flip() ?

Dans Pygame, les changements de la surface d'affichage ne sont pas immédiatement visibles. Normalement, un affichage doit être mis à jour dans les zones qui ont changé pour les rendre visibles à l'utilisateur. Avec l'affichage par double-tampon (double buffer), l'affichage doit être interverti pour rendre les changements visibles. Dans cet exemple, la fonction flip() fonctionne parfaitement parce qu'elle manipule la zone entière de la fenêtre et gère les surfaces en simple et double-tampon.

IX - Préparer les objets du jeu

Ici nous créons tous les objets dont le jeu aura besoin.

whiff_sound = load_sound('whiff.wav')
punch_sound = load_sound('punch.wav')
chimp = Chimp()
fist = Fist()
allsprites = pygame.sprite.RenderPlain((fist, chimp))
clock = pygame.time.Clock()

D'abord nous chargeons deux effets sonores en utilisant la fonction load_sound(). Ensuite nous créons une instance pour chacune de nos classes de sprite. Enfin, nous créons un groupe de sprites qui contiendra tous nos sprites.

Nous utilisons en fait un groupe spécial de sprites nommé RenderPlain. Ce sprite peut dessiner tous les sprites qu'il contient à l'écran. Il est appelé RenderPlain parce qu'il y a en fait plusieurs groupes de rendu avancés. Mais pour notre jeu, nous avons simplement besoin de les dessiner. Nous créons le groupe nommé allsprites en passant une liste avec tous les sprites qui appartiennent au groupe. Nous pourrons plus tard ajouter ou supprimer des sprites de ce groupe, mais dans ce jeu, nous n'en aurons pas besoin.

L'objet clock que nous créons, sera utilisé pour contrôler le taux d'images par seconde de notre jeu. Nous l'utiliserons dans la boucle principale de notre jeu pour s'assurer qu'il ne fonctionne pas trop vite.

X - La boucle principale

Rien de spécial ici, simplement une boucle infinie.

while 1:
clock.tick(60)

Tous les jeux exécutent ce type de boucle. L'ordre des choses est de vérifier l'état de l'ordinateur et des entrées utilisateur, déplacer et actualiser l'état de tous les objets, et ensuite de les dessiner sur l'écran. Vous verrez que cet exemple n'est pas différent.

Nous faisons ainsi appel à notre objet clock qui s'assurera que notre jeu ne dépasse pas les 60 images par seconde.

XI - Gérer tous les évènements d'entrée

C'est un exemple extrêmement simple sur le fonctionnement de la pile d'évènements.

for event in pygame.event.get():
if event.type == QUIT:
return
elif event.type == KEYDOWN and event.key == K_ESCAPE:
return
elif event.type == MOUSEBUTTONDOWN:
if fist.punch(chimp):
punch_sound.play() #frappé
chimp.punched()
else:
whiff_sound.play() #raté
elif event.type == MOUSEBUTTONUP:
fist.unpunch()

D'abord, nous prenons tous les évènements disponibles de Pygame et faisons une boucle pour chacun d'eux. Les deux premiers testent si l'utilisateur a quitté notre jeu ou a appuyé sur la touche Echap. Dans ces cas, nous retournons simplement dans la fonction principale et le programme se termine proprement.

Ensuite, nous vérifions si le bouton de la souris a été enfoncé ou relâché. Si le bouton est enfoncé, nous demandons à l'objet poing s'il est entré en collision avec le chimpanzé. Nous jouons l'effet sonore approprié, et si le singe est frappé, nous lui demandons de tournoyer (en appelant sa méthode punched().

XII - Actualiser les sprites

allsprites.update()

Les groupes de sprites possèdent une méthode update(), qui appelle la méthode update() de tous les sprites qu'ils contiennent. Chacun des objets se déplacera, en fonction de l'état dans lequel ils sont. C'est ici que le chimpanzé se déplacera d'un pas d'un côté à l'autre, ou tournoiera un peu plus loin s'il a récemment été frappé.

XIII - Dessiner la scène entière

Maintenant que tous les objets sont à la bonne place, il est temps de les dessiner.

screen.blit(background, (0, 0))
allsprites.draw(screen)
pygame.display.flip()

Le premier appel à blit() dessinera l'arrière-plan sur la totalité de la fenêtre. Ceci effacera tout ce que nous avons vu de la scène précédente (peu efficace, mais suffisant pour ce jeu). Ensuite nous appelons la méthode draw() du conteneur de sprites. Puisque ce conteneur de sprites est réellement une instance de groupe de sprites DrawPlain, il sait comment dessiner nos sprites. Enfin grâce à la méthode flip(), nous affichons le contenu du tampon de Pygame à l'écran. Tout ce que nous avons dessiné apparaît en une seule fois.

XIV - Game Over

L'utilisateur a quitté, c'est l'heure du nettoyage.

Le nettoyage d'un jeu dans Pygame est extrêmement simple. En fait puisque toutes les variables sont automatiquement détruites, nous n'avons pas réellement besoin de faire quoi que ce soit.

XV - Remerciements

Traduit de l'anglais, l'original par Pete Shinners : http://www.pygame.org/docs/tut/chimp/ChimpLineByLine.html

Cette traduction est aussi disponible sur Wikibooks. La liste des contributeurs est disponible ici.

Merci à ClaudeLELOUP pour sa correction orthographique.