Différences entre les versions de « Pygame/Concevoir des jeux avec Pygame »

m
<source> -> <syntaxhighlight> (phab:T237267)
m (<source> -> <syntaxhighlight> (phab:T237267))
La première chose à laquelle il faut penser, lors de l'approche d'un projet de programmation, est de décider d'un style d'écriture, et de le conserver. Python en lui-même facilite cela, à cause de son interprétation stricte des espaces et de l'indentation, mais cela ne vous empêche pas de choisir la largeur de votre indentation, de quelle manière vous placerez les importations, comment vous allez commenter le code, etc. Vous verrez comment je fais tout cela dans mes exemples de code, mais quel que soit le style que vous adopterez, conservez-le tout au long de votre code. Essayez également de documenter toutes vos classes, et de commenter tous les morceaux de code qui peuvent sembler obscurs. Par ailleurs, il ne sert à rien de commenter ce qui est évident. J'ai vu beaucoup de personnes faire la chose suivante :
 
<sourcesyntaxhighlight lang="python">
player1.score += scoreup # Add scoreup to player1 score
</syntaxhighlight>
</source>
 
Ce n'est pas très grave, mais un peu inutile. Un mauvais code est mal agencé, avec des changements aléatoires dans le style d'écriture, et une maigre documentation. Ce mauvais code ne sera pas seulement ennuyeux pour les autres personnes, mais il sera également difficile à maintenir pour vous.
Dans cet exemple, le code complet ressemble à ça :
 
<sourcesyntaxhighlight lang="python">
#!/usr/bin/python
# coding: utf-8
 
if __name__ == '__main__': main()
</syntaxhighlight>
</source>
 
=== Objets Pygame de base ===
Tout d'abord, vous avez besoin de démarrer votre jeu et de charger vos modules. C'est toujours une bonne idée de définir certaines choses directement en haut du fichier source principal, comme : le nom du fichier, ce qu'il contient, sa licence, ainsi que n'importe quelle autre information que vous jugerez utile de faire lire à ceux qui la regardent. Ensuite vous pouvez charger des modules, agrémentés d'une gestion d'erreur qui fera en sorte que Python ne vous affichera pas ces horribles ''traceback'' que les non-programmeurs ne comprennent pas. Le code est très simple, je ne m'étendrai pas dessus :
 
<sourcesyntaxhighlight lang="python">
#!/usr/bin/env python
# coding: utf-8
print "Impossible de charger le module. %s" % (err)
sys.exit(2)
</syntaxhighlight>
</source>
 
=== Fonctions de gestion des ressources ===
C'est toujours une bonne idée d'écrire vos propres fonctions de gestion de ressources, car bien que Pygame possède des méthodes pour l'ouverture des images et des sons (ainsi que d'autres modules qui possèdent eux aussi leurs propres méthodes pour l'ouverture d'autres ressources), ces méthodes peuvent prendre plus d'une ligne, et peuvent requérir de consistantes modifications faites par vous-mêmes, et bien souvent elles ne fournissent pas de gestion d'erreur satisfaisante. Écrire des fonction de gestion de ressources vous donne un code sophistiqué, réutilisable, et vous offre plus de contrôle sur vos ressources. Prenez cet exemple d'une fonction de chargement d'image :
 
<sourcesyntaxhighlight lang="python">
def load_png(name):
"""Charge une image et retourne un objet image"""
raise SystemExit, message
return image, image.get_rect()
</syntaxhighlight>
</source>
 
Ici nous avons créé une fonction de chargement d'image plus sophistiquée que celle fournie par Pygame : <code>image.load()</code>. À noter que la première ligne de la fonction débute par un ''docstring'' (chaine de caractère de documentation) qui décrit ce que fait la fonction et quel objet elle retourne. La fonction suppose que toutes vos images soient dans un répertoire appelé <code>data</code>, et donc utilisera le nom de fichier et créera le chemin complet (par exemple <code>data/ball.png</code>), en utilisant le module <code>os</code> pour s'assurer de la compatibilité entre plateforme différente (Linux, MacOS, Windows, ...). Ensuite elle essaye de charger l'image, et de convertir les régions alpha (ce qui vous permettra d'utiliser la transparence), et le cas échéant retourne une erreur ''lisible par un être humain'' si elle rencontre un problème. Finalement elle retourne un objet image, ainsi que son <code>rect</code>.
Une fois les modules chargées et les fonctions de gestion de ressources écrites, vous aimeriez écrire certains objets du jeu. La manière de le faire est très simple, bien qu'elle semble complexe au début. Vous devez écrire une classe pour chaque type d'objet du jeu, et ensuite vous pourrez créer une instance de ces classes pour chaque objet. Vous pourrez ensuite utiliser les méthodes de ces classes pour manipuler les objets, les déplacer et leur donner des capacités d'interaction. Votre jeu ressemblera donc à ceci (pseudo-code) :
 
<sourcesyntaxhighlight lang="python">
#!/usr/bin/python
# coding: utf-8
[appel de la méthode update() de la balle]
ball.update()
</syntaxhighlight>
</source>
 
Ce n'est bien sûr qu'un exemple très simple, et vous aurez besoin d'y insérer tout le code nécessaire, en lieu et place des commentaires entre crochets. Mais vous devez connaître l'idée de base. Vous créez une classe, dans laquelle vous insérez toutes les fonctions d'une balle, en y incluant <code>__init__()</code>, qui créera tous les attributs d'une balle, et <code>update()</code>, qui déplacera la balle dans sa nouvelle position, avant de la bliter à l'écran dans cette position.
Voici une classe simple incluant les fonctions nécessaires pour la création d'un objet balle qui se déplacera sur l'écran si la fonction <code>update()</code> est appelée :
 
<sourcesyntaxhighlight lang="python">
class Ball(pygame.sprite.Sprite):
"""Une balle qui se déplace sur lécran
(dx,dy) = (z*math.cos(angle),z*math.sin(angle))
return rect.move(dx,dy)
</syntaxhighlight>
</source>
 
Ici nous avons la classe <code>Ball</code>, avec une méthode <code>__init__()</code>, qui paramètre la balle, et une méthode <code>update()</code> qui change le rectangle de la balle pour une nouvelle position, et une méthode <code>calcNewPos()</code> pour calculer la nouvelle position de la balle basée sur sa position courante, et le vecteur par lequel elle se déplace. J'expliquerai la gestion de la physique dans un moment. La seule autre chose à noter est le ''docstring'', qui est un peu plus long cette fois, et explique les bases de la classe. Ces chaînes de caractères sont utiles non seulement pour vous-même et les autres programmeurs qui lisent votre code, mais aussi pour les outils qui [[w:Parseur|parsent]] votre code et le documentent. Elle ne feront pas la différence dans de petits programmes, mais dans les gros elles sont inestimables, c'est donc une bonne habitude à prendre.
Le principe derrière la classe Bat est similaire à la classe Ball. Vous avez besoin d'une méthode <code>__init__()</code> pour initialiser la raquette (vous pourrez donc créer des instances d'objet pour chaque raquette), d'une méthode <code>update()</code> pour appliquer les changements sur la raquette avant de la blitter à l'écran, et diverses autres méthodes qui définiront ce que fait cette classe. Voici un échantillon du code :
 
<sourcesyntaxhighlight lang="python">
class Bat(pygame.sprite.Sprite):
"""Raquette de 'tennis' déplaçable qui peut frapper la balle
self.movepos[1] = self.movepos[1] + (self.speed)
self.state = "movedown"
</syntaxhighlight>
</source>
 
Comme vous pouvez le voir, cette classe est très similaire à la classe Ball dans sa structure. Mais les différences se situent dans ce que fait chaque fonction. Tout d'abord, il y a une méthode <code>reinit()</code> qui est utilisée lorsqu'un round est terminé : la raquette retourne dans sa position de départ, et chacun de ses attributs à ses valeurs d'origine. Ensuite, la manière dont la raquette bouge est un peu plus complexe que la balle, étant donné que ses mouvements sont simples (haut/bas) mais dépendent de ce que désire l'utilisateur, tandis que la balle conservera son mouvement à chaque image. Pour mettre en évidence la façon dont la raquette bouge, il est pratique d'examiner ce petit diagramme pour voir la séquence des évènements :
Alors, comment allons nous savoir quand le joueur est en train d'enfoncer la touche, ou la relâche ? Avec le système de file d'évènement de Pygame, pardi! C'est un système vraiment simple à utiliser et à comprendre, çà ne devrait pas être long :) Vous avez déjà observé la file d'évènement en action dans le programme Pygame de base, où elle était utilisée pour vérifier si l'utilisateur voulait quitter l'application. Le code pour déplacer la raquette est aussi simple que çà :
 
<sourcesyntaxhighlight lang="python">
for event in pygame.event.get():
if event.type == QUIT:
player.movepos = [0,0]
player.state = "still"
</syntaxhighlight>
</source>
 
Ici nous supposons que vous avez déjà créé une instance de <code>Bat</code>, et appelé l'objet <code>player</code>. Vous pouvez observer la couche familière de la structure <code>for</code>, qui produit une itération à chaque évènement trouvé dans la file d'évènement de Pygame, eux-même retrouvés grâce à la fonction <code>event.get()</code>. L'utilisateur enfonce une touche, appuie sur le bouton de la souris, ou bouge le joystick, toutes ces actions seront placées dans la file d'évènement de Pygame, et conservées jusqu'à leur utilisation. Donc à chaque itération de la boucle de jeu principale, vous irez faire un tour dans ces évènements vérifier s'il y en a quelques uns que vous pouvez utiliser. La fonction <code>event.pump()</code> qui était dans la méthode <code>Bat.update()</code> est appelée à chaque itération pour ''pomper'' les vieux évènements et garder la file à jour.
La principe de base de ce type de rebond est simple à comprendre. Vous prenez les coordonnées des 4 coins de la balle, et vous vérifiez s'ils correspondent avec les coordonnées X et Y des bords de l'écran. Donc si les coins haut-gauche et haut-droit ont leur coordonnée Y à 0, vous savez que la balle est actuellement contre le bord haut de l'écran. Nous ferons tout cela dans la fonction <code>update()</code>, après que nous ayons défini la nouvelle position de la balle.
 
<sourcesyntaxhighlight lang="python">
if not self.area.contains(newPos):
tl = not self.area.collidepoint(newPos.topleft)
 
self.vector = (angle,z)
</syntaxhighlight>
</source>
 
Ici nous contrôlons que la variable <code>area</code> contient la nouvelle position de la balle. Elle devrait toujours être vraie, nous n'aurons donc pas besoin de la clause <code>else</code>, bien que dans d'autres circonstances vous devriez considérer ce cas de figure. Nous contrôlons alors si les coordonnées des quatre coins entrent en collision avec les bords de l'écran, et créons des objets pour chaque résultat. Si c'est vérifié, les objets auront leur valeur à 1, ou <code>true</code>. Sinon, la valeur sera <code>None</code>, ou <code>false</code>. Nous verrons alors si elle touche le dessus ou le dessous, et si oui nous changerons la direction de la balle. En pratique, grâce à l'utilisation des radians, nous pourrons le faire facilement, juste en inversant sa valeur (positif/négatif). Nous contrôlons aussi que la balle ne traverse pas les bords, ou alors nous appellerons la fonction <code>offcourt()</code>. Ceci dans mon jeu, replace la balle, ajoute 1 point au score du joueur spécifié lors de l'appel de la fonction, et affiche le nouveau score.
Faire en sorte que la balle rebondisse sur les raquettes est similaire au rebonds sur les bords de l'écran. Nous utilisons encore la méthode des collisions, mais cette fois, nous vérifions que les rectangles de la balle entrent en collision avec ceux des raquettes. Dans ce code, nous y ajoutons des suppléments pour éviter certains problèmes. Vous devriez trouver tout un tas de codes supplémentaires à ajouter pour éviter certains problèmes ou bugs, il est plus simple de les trouver lors de l'utilisation.
 
<sourcesyntaxhighlight lang="python">
else:
# Réduire les rectangles pour ne pas frapper la balle derrière les raquettes
self.hit = not self.hit
self.vector = (angle,z)
</syntaxhighlight>
</source>
 
Nous débutons cette section à partir de la condition <code>else</code>, à cause de la partie précédente du code qui vérifie si la balle frappe les bords. Cela prend tout son sens si elle ne frappe pas les bords, elle pourrait frapper une raquette, donc nous poursuivons la condition précédente. Le premier problème est de réduire le rectangle des joueurs de 3 pixels dans les deux dimensions pour empêcher la raquette de frapper la balle lorsqu'elle est derrière elle. Imaginez que vous venez juste de bouger la raquette et que la balle se trouvait derrière elle, les rectangles se chevauchent, et la balle sera considérée comme "frappée", c'est pour prévenir ce petit bug.
1 535

modifications