Créer des jeux vidéo avec Pygame
Introduction
1. Contexte
Dans la plupart des langages de programmation, des fonctions permettent d’interagir avec le clavier, voire la souris. Mais dès qu’il s’agit de produire un son, d’effectuer des affichages à 30 images par seconde ou d’utiliser le joystick, nous sommes face à un grand vide. La cause profonde vient du contexte historique. En effet, ces besoins correspondent essentiellement à ceux de l’industrie du jeu vidéo. Par conséquent, des librairies spécialisées (OpenGl, DirectX) et des environnements dédiés (Unity3D, Unreal Engine…) proposent des packages spécifiques pour gérer la 3D, la 2D, les animations, les effets spéciaux, les interactions avec les joysticks, la gestion des Kinect ou des casques de réalité virtuelle…
En fait, pendant longtemps, l’offre s’est centrée autour de la librairie SDL (Simple DirectMedia Library) permettant un contrôle de l’audio, du clavier, de la souris, du joystick et de la carte 3D. Effectivement, proposée comme une surcouche de DirectX et d’OpenGL, cette librairie pouvait penser offrir une forme de simplicité, comparée à ces deux mastodontes destinés aux développeurs de haut niveau.
Malheureusement, SDL étant proposée en langage C, sans environnement de développement dédié, son utilisation avec les fameux pointeurs du langage C a dérouté plus d’un utilisateur. Développer un jeu très simple relevait du défi personnel dans ce contexte et mieux valait ne pas s’y aventurer sans une aide extérieure.
Heureusement, la librairie SDL a été portée sous Python, donnant naissance à Pygame. Ainsi, nous avons accès quasiment à la même puissance graphique que les librairies DirectX et OpenGL, sans toutefois avoir accès à toutes les options de dernière génération, mais nous n’en sommes pas à ce niveau-là pour l’instant ! Nous contournons cependant cette fois l’utilisation des pointeurs et la gestion de la mémoire bas niveau propre au langage C et cela fait beaucoup de travail difficile, chronophage et d’intérêt faible qui disparaît. Nous pouvons donc avec le temps disponible nous consacrer à l’essentiel : la logique des interactions et les affichages.
Restons réalistes, nous n’allons pas programmer la prochaine scène d’intro de FarCry ou de Fallout. Les grosses productions de jeu 3D, les fameuses productions Triple-A à gros budgets, nécessitent des dizaines et des dizaines de graphistes 2D et 3D et c’est eux qui rendent les jeux si beaux. Les game designers et les scénaristes sont chargés de les rendre intéressants et ludiques. Quant aux programmeurs, ils ont la lourde tâche de faire en sorte que tout cela marche !
Les productions de jeux vidéo sur téléphone portable sont déjà plus modestes : une dizaine de personnes, parfois seulement trois. Et on arrive encore à trouver des développeurs solo souvent à l’origine de la création de jeu d’auteur. Nous parlons du monde...
1. Présentation du code
Vous pouvez trouver la source du projet à compléter en téléchargement depuis l’onglet Compléments sous le nom de fichier : Pygame Premiers pas.py.
Recopiez le code suivant dans votre éditeur ou téléchargez et ouvrez le fichier Premier pas.py.
# permet d'accéder aux fonctions du module pygame
import pygame
# Definit des couleurs RGB
BLACK = [0, 0, 0]
WHITE = [255, 255, 255]
GREEN = [0, 255, 0]
RED = [255, 0, 0]
BLUE = [0 , 0 , 255]
# initialisation de l'écran de jeu
pygame.init()
# Initialise la fenêtre de jeu
TailleEcran = [400, 300]
screen = pygame.display.set_mode(TailleEcran)
pygame.display.set_caption("Super Ball")
# Gestion du rafraîchissement de l'écran
clock = pygame.time.Clock()
# Position de départ
balle_x = 50
balle_y = 50
# Vitesse et direction
balle_change_x = 3
balle_change_y = 3
balle_rayon= 5
# Le jeu continue tant que l'utilisateur ne ferme pas la fenêtre
Termine = False
# BOUCLE PRINCIPALE DU PROGRAMME
while not Termine:
# recupère la liste des évènements du joueur
event = pygame.event.Event(pygame.USEREVENT)
# ÉVÈNEMENTS
# détecte le clic sur le bouton close de la fenêtre
for event in pygame.event.get():
if event.type == pygame.QUIT:
Termine = True
# LOGIQUE
# Déplace la balle
balle_x += balle_change_x
balle_y += balle_change_y
# Rebond
if balle_y > TailleEcran[1] or balle_y < 0:
balle_change_y = balle_change_y * -1
if balle_x > TailleEcran[0] or balle_x < 0:
balle_change_x = balle_change_x * -1
# AFFICHAGE
# Dessine le fond
screen.fill(WHITE)
# Colorie les bords de l'écran
R = [0,0, TailleEcran[0], TailleEcran[1]]
pygame.draw.rect(screen,GREEN,R ,1)
# dessine le palet de jeu
pygame.draw.circle(screen,RED,[balle_x,balle_y],balle_rayon *2)
pygame.draw.circle(screen,BLACK,[balle_x,balle_y],balle_rayon )
# Demande à Pygame de se caler sur 30 FPS
clock.tick(30)
# Bascule l'image dessinée à l'écran
pygame.display.flip()
# débogage
print("position : " + str([balle_x,balle_y]))
print("Dir : " + str([balle_change_x,balle_change_y]))
# Ferme la fenêtre
pygame.quit()
Lancez le programme Python. Vous devez avoir une fenêtre qui s’ouvre dans laquelle une balle se déplace en rebondissant sur les murs.
Si vous examinez la fenêtre de sortie, vous trouvez les messages suivants :
À chaque affichage, nous donnons la position de la balle ainsi que sa direction de déplacement. C’est utile pour vérifier...
1. Historique
Pong est un des premiers jeux vidéo d’arcade à avoir connu un succès planétaire. Il a été imaginé par l’Américain Nolan Bushnell. La société Atari le commercialise en 1972 sous forme de bornes d’arcade, vendant environ 8 000 exemplaires en un an. Le succès étant au rendez-vous, en 1975, Pong est vendu sous forme de consoles de salon. Les consoles Atari ainsi que celles mises en place par la concurrence sous forme de contrefaçons ou de variantes vont totaliser plusieurs millions de ventes les années suivantes, c’est une véritable explosion. On peut considérer que ce succès mondial doublé par la production en masse de consoles a été le point de départ de l’industrie du jeu vidéo.
Le jeu est inspiré du tennis de table en vue de dessus. Les deux joueurs s’affrontent en déplaçant chacun son palet placé sur les bords gauche et droit. La balle rebondit sur le bord supérieur et inférieur de l’écran, et chaque fois qu’elle sort du côté gauche ou du côté droit, le joueur correspondant marque un point. Le score est affiché en haut de l’écran, au centre. La simplicité du jeu et son gameplay réussi contribuent à la réussite du titre.
2. Premier lancement
Vous pouvez trouver la source du projet à compléter en téléchargement depuis l’onglet Compléments sous le nom de fichier : Pygame Pong.py.
Ouvrez le fichier Pong.py dans votre éditeur de code Python et lancez le programme. Vous constatez qu’une petite partie du jeu est déjà fonctionnelle. La balle se déplace, le palet de gauche peut être contrôlé à partir des touches [Flèche en haut] et [Flèche en bas] et la balle peut rebondir dessus. Par contre, elle rebondit uniquement sur le mur du haut, et non sur le mur du bas !
Dans les fichiers d’exemple se trouve la correction du projet si vous êtes vraiment coincé.
3. Présentation du code
Vous remarquerez que la moitié des lignes de code sont comparables à celles du jeu précédent. Pourquoi ? Car il y a beaucoup de points communs : variables définissant des couleurs, création de la fenêtre, boucle principale de jeu, gestion des FPS, récupération des évènements, flip de la zone de dessin… Nous présentons donc uniquement les lignes de code nouvelles. La structure du jeu reste identique : variables d’état, boucle principale, étape de logique et étape d’affichage.
#fonctions permettant de dessiner la balle et les deux raquettes
def Drawpalet(x, y):
R = (x,y,palet_width,palet_height)
pygame.draw.rect(screen, WHITE, R, 0)
def DrawBall(x,y):
pygame.draw.circle(screen, WHITE, (x,y),10, 0)
La première fonction dessine un palet dans le jeu à la position (x,y). L’intérieur du palet est rempli de blanc, car le paramètre d’épaisseur de la fonction rect() est égal à zéro. Le coin haut gauche du palet correspond...
1. Historique
Snake (de l’anglais signifiant "serpent") est un genre de jeu vidéo dans lequel un joueur dirige un serpent qui grandit au cours de la partie. Lui et ses éventuels congénères constituent ainsi un obstacle pour eux-mêmes. L’objectif est de survivre le plus longtemps possible en ramassant éventuellement des bonus pour augmenter son score.
Il n’existe pas vraiment une version de référence pour ce jeu. En effet, son graphisme ultrasimple, son absence de musique et son fond d’écran noir ne permettent pas de lui donner une personnalité, comparativement à des jeux comme Pacman ou Tetris. De plus, ce concept est déclinable dans des versions très différentes, comme Tron. Presque disparu de la communauté vidéo ludique dans les années 2000, il est redevenu un classique grâce aux appareils mobiles.
2. Premier lancement
Vous pouvez trouver la source du projet à compléter en téléchargement depuis l’onglet Compléments sous le nom de fichier : Pygame Snake.py.
Le corrigé du projet est aussi disponible en téléchargement sous le nom de fichier : Pygame Snake corrigé.py.
Ouvrez le fichier Snake.py dans votre éditeur de code Python et lancez le programme. Vous constatez qu’une petite partie du jeu est déjà fonctionnelle. Le serpent se déplace sur la droite, sa tête est affichée en blanc, et son corps en jaune. Le score affiché augmente au fil du temps.
Dans les fichiers d’exemple se trouve la correction du projet si vous êtes coincé.
3. Présentation du code
Nous ne présentons pas la boucle principale de jeu de Pygame. Si besoin, référez-vous à la section Le projet balle rebondissante en début de chapitre.
Choisir comment programmer le serpent n’est pas chose aisée. En effet, celui-ci doit pouvoir s’allonger et, de plus, il se déplace et peut ainsi prendre des formes en zigzag. Pour répondre à ces besoins, nous choisissons de modéliser la zone de jeu comme une grille avec des cases de 10 pixels de large et nous modélisons le serpent par une liste de coordonnées.
Pour son initialisation, nous trouvons le code suivant :
Snake1 = []
for x in range(10):
Snake1.append([x+10,10])
Le serpent occupe donc 10 cases au lancement du jeu et démarre dans une position horizontale. Pour son affichage, rien de plus simple, il suffit de parcourir la liste des coordonnées et d’utiliser la fonction DessineCase() en passant en arguments la position et la couleur pour colorer chaque case de la grille occupée par le serpent. Par convention, la dernière coordonnée dans la liste correspond à celle de la tête. Nous l’affichons dans une couleur différente de celle du corps du serpent :
# affichage de serpent1
for s in Snake1 :
DessineCase(s,YELLOW) ...
La documentation en ligne de Pygame
Comment connaître les différents paramètres des fonctions de la librairie Pygame ? À ce niveau-là, il n’y a pas beaucoup d’options. Vous pouvez demander sur un forum ou plus rapidement consulter la documentation en ligne. La documentation de la librairie Pygame est plutôt bien faite. Cependant, elle est disponible uniquement en anglais. Ce n’est pas un obstacle normalement, car les documentations techniques informatiques utilisent une grammaire et un vocabulaire assez simples. Par contre, il y a des termes techniques, et là, ce n’est pas évident. Car ces termes ne sont pas forcément présents dans un dictionnaire anglais-français, vu la spécificité de la matière.
Il se peut d’ailleurs qu’il n’existe pas de mot équivalent en français et que le terme anglais soit ainsi utilisé couramment par la communauté. Dans ce cas, il faut trouver une source d’explication. Par exemple, Wikipédia connaît très bien les termes techniques. Nous allons vous présenter ici les termes spécifiques à la librairie Pygame et nous essaierons de décrypter une partie de la documentation concernant les fonctions de dessin.
Si vous n’êtes pas à l’aise en anglais, ce n’est pas grave : toute l’information nécessaire pour compléter les projets est donnée dans le livre.
1. La liste des termes techniques
-
Width : largeur.
-
Height : hauteur.
-
Draw : dessiner.
-
Straight line : ligne droite.
-
Render : dessiner dans un sens plus général que draw. On peut dire que draw concerne plutôt les tracés géométriques : cercle, droite, rectangle... et render englobe la totalité des opérations graphiques, comme le dessin, mais aussi les effets spéciaux.
-
Rendering : étape durant laquelle l’ordinateur effectue toutes les opérations graphiques, partant d’une scène vide jusqu’à l’affichage complet de la scène.
-
Width/size of stroke : épaisseur du crayon servant à dessiner.
-
Filled : tracé plein, cela indique que la forme dessinée doit être remplie.
-
Shape : forme. Terme générique utilisé pour désigner un rectangle, une ellipse, un trait ou tout objet géométrique pouvant être dessiné par la librairie…
-
Alpha value : paramètre indiquant le pourcentage de transparence de la forme à dessiner.
-
Tuple : terme venant du langage Python pour désigner une sorte de vecteur/séquence d’objets de taille fixe. Une couleur est représentée par trois valeurs que l’on peut stocker dans un tuple : (100,50,200). Mais la librairie Pygame accepte aussi les listes. On peut donc donner de manière équivalente : [100,50,200].
-
Surface : terme générique utilisé...
1. Historique
En 1985, la société Atari Games édite la borne d’arcade Gauntlet, borne révolutionnaire pour l’époque, car plusieurs joueurs, quatre exactement, peuvent coopérer pour évoluer à l’intérieur d’un donjon rempli de trésors, de monstres et de nombreux pièges en tout genre. Gauntlet fait aujourd’hui partie des classiques des jeux d’action et le jeu a d’ailleurs été porté sur de nombreux micro-ordinateurs. Cerise sur le gâteau, en vous plongeant dans un monde digne de Donjons et Dragons, vous avez le choix d’incarner quatre personnages différents : un guerrier, un mage, un elfe ou une valkyrie. Chacun a une arme ou un pouvoir différent avec des caractéristiques spécifiques. L’interface en vue de dessus permet d’avoir une bonne vision du décor et des méandres du labyrinthe dans lequel les joueurs sont plongés.
Gauntlet sur borne d’arcade Atari et sa version portée sur micro-ordinateur
Nous vous proposons de mettre en place les bases d’un jeu fonctionnant avec le même concept : un aventurier évoluant dans un labyrinthe à la recherche de clefs et de trésors.
Vous trouverez les exemples de code dans le fichier : Labyrinthe.py.
2. Gestion du labyrinthe
Nous stockons le plan du labyrinthe à l’intérieur d’une liste de string :
LABY = [ 'BBBBBBBBBB',
'B B',
'B BB BBBBB',
'B B B B',
'B BB BB B',
'B B BB B',
'B B B B',
'BB BB BB B',
'B B B',
'BBBBBBBBBB' ]
Ce choix technique est en fait orienté par la pratique. En effet, la lecture du plan se fait en regardant simplement la disposition des lettres B à l’écran.
Chaque lettre B traduit la présence d’un bloc de mur bleu. Les espaces, eux, correspondent à une zone de déplacement en noir. Remarquez que l’ensemble du labyrinthe est entouré de murs.
HauteurLABY = len(LABY)
LargeurLABY = len(LABY[0])
Pour déterminer la taille du labyrinthe, nous utilisons la fonction len() appliquée sur la liste LABY. Elle retourne le nombre d’éléments dans cette liste, nombre qui correspond à la quantité de lignes dans le décor. Ensuite, nous prenons le premier élément de la liste soit "BBBBBBBBBB" et nous appliquons la fonction len() à nouveau. La valeur retournée correspond au nombre de caractères dans cette chaîne, mais aussi au nombre de cases présentes dans la largeur du décor....
1. Historique
Voici un titre célèbre de la société Capcom, sorti en 1987. Le joueur pilote un avion au-dessus du Pacifique en pleine guerre du Japon. Il se trouve confronté à des vagues d’ennemis. Les cuirassés et notamment leurs tourelles anti-aériennes l’obligent à mener une lutte acharnée pour mener à bout chaque niveau. Le game design basé sur un défilement vertical est des plus simple. Quant aux missions, elles consistent à détruire le boss de fin de niveau, ce qui n’est pas particulièrement original. Cependant, la simplicité du jeu et l’originalité des décors lui ont procuré un certain succès, à tel point que le titre a été distribué sur l’ensemble des consoles et des micro-ordinateurs de l’époque.
Vous pouvez trouver la source du projet à compléter en téléchargement depuis l’onglet Compléments sous le nom de fichier : Pygame 1943.py.
Le corrigé du projet est aussi disponible en téléchargement sous le nom de fichier : Pygame 1943 corrigé.py.
2. Premier lancement
Téléchargez et ouvrez le fichier 1943.py dans votre éditeur de code Python et lancez le programme. Vous constatez qu’une petite partie du jeu est déjà fonctionnelle. L’avion se déplace ! Le cuirassé aussi, mais il ne semble pas très réactif. La mer défile sous l’avion, cela donne un effet de vitesse. Étrangement, elle disparaît petit à petit. Bref, vous l’avez compris, il y a du travail en perspective !
|
|
3. Gestion de la mer
Nous ne présentons plus la structure de boucle d’un jeu sous Pygame. Si vous ne la connaissez pas, reportez-vous au projet : Projet balle rebondissante de ce chapitre.
MER = pygame.Surface((LARG, HAUT))
pygame.draw.rect(MER, BLEU, [0,0,LARG,HAUT])
for i in range(2000) :
x = random.randint(0,LARG-1)
y = random.randint(1,HAUT-1)
MER.set_at((x, y), BLANC)
Pour gérer la mer, nous créons une surface (une image) de la taille de la fenêtre de jeu. Nous la remplissons de bleu. Ensuite, nous tirons aléatoirement des coordonnées pour positionner des pixels blancs afin de simuler l’écume des vagues. Cela donne un effet de relief lors du défilement vertical.
Dans la boucle de jeu, à chaque itération, nous incrémentons la variable compteur. Nous nous servons de sa valeur pour afficher l’image de la mer dans la fenêtre de jeu :
#MER
screen.blit(MER,(0,compteur))
Cela a pour effet de placer l’image de la mer sur la zone d’affichage et de la faire descendre au fil du temps. Nous obtenons donc ce comportement :
Ce mécanisme permet de voir l’eau défiler sous l’avion, ce qui est très intéressant. Cependant, une partie de la fenêtre n’est plus couverte au fil du temps. La solution est simple, nous utilisons deux fois l’image pour être sûrs de recouvrir toute la fenêtre :
Mais ce n’est pas suffisant, il faut utiliser une astuce supplémentaire : une fois que l’image...
1. Contexte
Vous pouvez trouver la source du projet à compléter en téléchargement depuis l’onglet Compléments sous le nom de fichier : Pygame Illumination.py.
Le corrigé du projet est aussi disponible en téléchargement sous le nom de fichier : Pygame Illumination corrigé.py.
Nous vous proposons de mettre en place une démonstration graphique. Le but de ce projet consiste à simuler l’éclairage d’une pièce. Le problème sous-jacent est celui du lancer de rayon. Ce problème est la base du graphisme en 3D. Les images de synthèse que vous voyez au cinéma et les jeux sur console exploitent largement cette technique.
Le lancer de rayon est aussi utilisé dans la gestion des interactions des jeux, par exemple pour savoir si un drone peut détecter votre présence ou pour savoir si votre tir a touché sa cible ou s’il a été arrêté par un obstacle. Le lancer de rayon consiste à envoyer une sorte de rayon laser depuis une source et à déterminer quel objet est touché par ce rayon.
Pour notre simulation, nous disposons une source de lumière en bas de l’écran, qui se déplace de gauche à droite. Nous ajoutons des murs dans la scène. Comme nous affichons le terrain en vue de dessus, ces murs apparaissent sous la forme de segments vus d’en haut. Leur rôle consiste à arrêter les trajets des rayons lumineux. Depuis la lampe, nous faisons partir une multitude de rayons et nous affichons leurs trajets jusqu’à ce qu’ils rencontrent un point d’impact. Voici un exemple de rendu :
2. Rappels mathématiques
Pour ce projet, nous devons calculer l’intersection entre un rayon lancé depuis une source et un segment qui symbolise notre mur. Nous allons travailler en 2D, ce qui va simplifier nos calculs. Le point L définit la position de la source de lumière, le point P définit un point éloigné indiquant la direction du rayon, et les points M et K définissent les extrémités d’un mur. Voici un schéma qui résume la situation :
Nous définissons une fonction pour calculer le produit scalaire entre deux vecteurs u=(ux,uy) et v=(vx,vy) :
def PS(u,v):
ux, uy = u
vx, vy = v
return ux * vx + uy * vy
Nous définissons dans le code de l’exemple une fonction Rot90(v) dont l’objectif consiste à tourner le vecteur passé en paramètre de 90°. Voici le code de la fonction associée :
def Rot90(v):
x,y = v
return (-y,x)
1. Historique
Vous pouvez trouver la source du projet à compléter en téléchargement depuis l’onglet Compléments sous le nom de fichier : Pygame Tetris.py.
Le corrigé du projet est aussi disponible en téléchargement sous le nom de fichier : Pygame Teris corrigé.py.
Tetris est un jeu vidéo entre arcade et puzzle, conçu par Alekseï Pajitnov en juin 1984. Le succès devient planétaire et toutes les consoles qui suivront posséderont leur version de Tetris. Il fait partie des jeux les plus addictifs de l’époque, avec Pacman. Le jeu consiste, grâce à seulement quatre touches, à orienter une pièce à droite ou à gauche pendant que cette dernière tombe inexorablement vers le bas de la grille. On peut aussi la faire tourner sur elle-même ou accélérer sa descente. Une fois la pièce bloquée, on ne peut plus modifier sa position et une autre pièce arrive en haut du décor. Et au grand désarroi du joueur, ce n’est jamais celle qu’il attend ! Ses ennemis sont les trous qu’il laisse au fur et à mesure de ses empilements maladroits. Cependant, lorsqu’une ligne est complète, elle disparaît et les lignes supérieures descendent alors d’un étage, laissant au joueur un espoir de survie supplémentaire ! Le jeu s’arrête lorsqu’une pièce nouvelle est bloquée dès son arrivée. Voici des copies d’écran de Tetris sur diverses machines de l’époque :
Sur GameBoy 1989 Sur AtariST 1989 Sur PS1 1994
2. Premier lancement
Téléchargez et ouvrez le fichier Tetris.py dans votre éditeur de code Python et lancez le programme. Vous constatez qu’une petite partie du jeu est déjà fonctionnelle. Une pièce apparaît, elle descend, descend, mais il est impossible de la déplacer :
3. Présentation du code
Nous ne présentons plus la structure de boucle d’un jeu sous Pygame. Si vous ne la connaissez pas, reportez-vous au projet : Projet balle rebondissante de ce chapitre.
Pour ce projet, il faut maîtriser les appels de fonction en Python. Si ce n’est pas le cas, reportez-vous au chapitre Les fonctions.
Nous présentons notre code. Tout d’abord, nous avons déclaré des constantes pour les couleurs et nous les avons stockées dans une liste de couleurs. Ainsi, une pièce du jeu associée au numéro 2 est affichée avec la couleur d’indice 2. Le fond noir est associé à la valeur 0, et les murs gris à la valeur 1 :
# Definit des couleurs RGB
NOIR = (0, 0, 0)
VERT = (0, 255, 0)
ROUGE = (255, 0, 0)
BLEU = (0 , 0 , 255)
GRIS = (128,128,128)
CYAN = (0,255,255)
JAUNE = (255,255,0)
ORANGE= (255,150,0)
VERT = (0,255,255)
MAUVE = (180,80,255)
LCoul = [ NOIR, GRIS, CYAN, JAUNE, MAUVE, ORANGE, BLEU, ROUGE, VERT ]
Dans...