Introduction au développement de jeu vidéo
Ce chapitre présente succintement quelques principes de développement de jeu vidéo en temps réel avec la bibliothèque Pygame.
Le temps réel veut dire que les entités du jeu évoluent en continue (ex : Minecraft, CS2, Fortnite, Mario). C'est l'opposé du tour par tour (ex : Dofus).
Principe
Le code d'un niveau de jeu a en général la structure suivante :
L'état du niveau est initialisé au lancement du niveau.
Ensuite, le code du niveau simule l'évolution de l'état par petites intervalles de temps, appelés frames ou ticks:
A chaque frame, le code :
- récupère les éventuelles intéractions faites par le joueur, par exemple un appui sur une touche.
- Simule l'évolution de l'état du niveau sur la durée d'une frame, en prenant en compte les intéractions du joueur.
- Affiche le nouvel état du niveau au joueur.
Installation de pygame et première fenêtre
Commencez par installer Pygame. Si vous n'y arrivez pas, utilisez la méthode dans le dépliant suivant :
Installation de Pygame par un script
Créez un fichier Python, copiez-collez y le script suivant, et executez-le avec votre outils de développement (par exemple, Thonny).
Attention
Il est peu recommandé d'exécuter des scripts qui ne proviennent pas d'une source fiable sans les comprendre, n'en faite pas une habitude !
import subprocess
import sys
def install(package):
subprocess.check_call([sys.executable, "-m", "pip", "install", package])
install("pygame")
Vous devriez obtenir une sortie de cette forme :
Defaulting to user installation because normal site-packages is not writeable
Collecting pygame
Downloading pygame-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (13.7 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 13.7/13.7 MB 2.8 MB/s eta 0:00:00
Installing collected packages: pygame
Successfully installed pygame-2.1.3
L'important est la dernière ligne, qui doit mentionner "Successfully installed".
Si vous pygame est déjà installé, vous aurez une sortie de la forme :
Defaulting to user installation because normal site-packages is not writeable
Requirement already satisfied: pygame in ./.local/lib/python3.10/site-packages (2.1.3)
Qui mentionne "Requirement already satisfied : pygame".
Super. Voici le code de départ :
import pygame
from pygame import *
ECRAN = "ECRAN"
QUITTER = "QUITTER"
def initialiser_niveau():
pygame.init()
ecran = pygame.display.set_mode([1280, 720])
return {
ECRAN : ecran
}
def entrees_joueur():
quitter = False
for evt in pygame.event.get():
if evt.type == pygame.KEYDOWN:
if evt.key == pygame.K_ESCAPE:
quitter = True
return {
QUITTER : quitter
}
def niveau():
etat = initialiser_niveau()
stop = False
while not stop:
entrees = entrees_joueur()
if entrees[QUITTER] :
stop = True
niveau()
Si vous exécutez ce code, vous devriez voir apparaître une fenêtre toute noire :

Que vous pouvez quitter en cliquant sur ECHAP.
Félicitation, vous avez créé votre première fenêtre !
Voyons plus en détail ce que fait le code :
import pygame
from pygame import *
# En python, les variables avec un nom en majuscules indiquent qu'elles ne doivent pas être modifiées
# C'est une convention, et non quelque chose que Python force. On les appelle des "constantes".
# On va utiliser des dictonnaires pour stocker toutes nos valeurs et les organiser.
# Et les constantes vont nous servir de clé. Pourquoi ?
# Si on se trompe dans l'écriture d'une constante, Python affichera une erreur, alors qu'avec
# directement une chaîne de caractère, Python ne dira rien en cas d'erreur.
ECRAN = "ECRAN"
QUITTER = "QUITTER"
# Cette fonction initialise la totalité de l'état du niveau. Pour le moment, elle ne fait pas grand chose.
def initialiser_niveau():
# On dit à Pygame de démarrer
pygame.init()
# On crée la fenêtre, de taille 1280x720.
# set_mode retourne une *Surface*, c'est à dire une zone sur laquelle on peut dessiner
ecran = pygame.display.set_mode([1280, 720])
return {
# Pour le moment, le seul état du niveau est... l'écran !
ECRAN : ecran
}
# Cette fonction récupère les intéractions du joueur.
def entrees_joueur():
quitter = False
# pygame.event.get retourne la liste des évènements (touche appuyé, souris bougée, ...)
# qui ont eu lieu depuis le dernier appel à pygame.event.get
for evt in pygame.event.get():
# On regarde le type de l'évènement. Ici, vrai si l'évènement corresponds à un appui sur une touche
if evt.type == pygame.KEYDOWN:
# On regarde sur quelle touche a appuyé le joueur. Ici, vrai si le joueur a appuyé sur ECHAP.
if evt.key == pygame.K_ESCAPE:
quitter = True
# On retourne les entrées sous forme d'un dictionnaire qui récapitule les actions que le joueur
# a effectuées cette frame
return {
QUITTER : echap
}
# Cette fonction contient le code principal du niveau
def niveau():
#création de l'état
etat = initialiser_niveau()
#Boucle infinie de la frame.
stop = False
#Stop sert à quitter si le joueur appuie sur échap
while not stop:
entrees = entrees_joueur()
if entrees[QUITTER] :
stop = True
# On appelle la fonction pour lancer le niveau
niveau()
Premier dessin
Pygame nous donne deux manières de réaliser l'affichage :
La première consiste à dessiner des formes à l'écran. La seconde à "coller" des images sur l'écran. Voyons la première.
Ajoutons un personnage et dessinons-le sous forme d'une cercle.
import pygame
from pygame import *
ECRAN = "ECRAN"
QUITTER = "QUITTER"
PERSONNAGE = "PERSONNAGE"
POSITION = "POSITION"
#on se crée une fonction pour créer le personnage
# On va toujours passer par des fonction et pas directement par des dicts
# Parce que les fonctions permettent plus de vérifications. On aura l'occasion d'en reparler.
def creer_personnage():
# Vector2 est un type fourni par Pygame, qui représente un ... vecteur en 2 d
# et supporte toutes les opérations de base sur les vecteurs : normalisation, addition, soustraction, multiplication, ...
return {
POSITION : Vector2(0, 0)
}
def initialiser_niveau():
pygame.init()
ecran = pygame.display.set_mode([1280, 720])
#on ajoute le personnage au niveau
return {
ECRAN : ecran,
PERSONNAGE : creer_personnage()
}
def entrees_joueur():
quitter = False
for evt in pygame.event.get():
if evt.type == pygame.KEYDOWN:
if evt.key == pygame.K_ESCAPE:
quitter = True
return {
QUITTER : quitter
}
# On crée une fonction pour afficher le niveau
def afficher_niveau(etat):
# remplit l'écran de la couleur noire (code RGB 0, 0, 0)
etat[ECRAN].fill((0, 0, 0))
#pygame.draw.circle dessine un cercle sur une surface. Les paramètres sont les suivants :
# - la Surface sur laquelle dessiner (ici, l'écran)
# - Le code RGB de la couleur, (196, 154, 214) corresponds à du rose/violet
# - le centre du cercle. ici, ce sera 0, 0, la position que l'on a donné à notre personnage.
# Elle corresponds à la position en pixels sur l'écran
# - Le rayon du cercle, egalement en pixels
pygame.draw.circle(
etat[ECRAN],
(196, 154, 214),
etat[PERSONNAGE][POSITION],
20
)
# pygame.display.flip permet de valider le dessin et de l'afficher à l'écran.
pygame.display.flip()
def niveau():
etat = initialiser_niveau()
stop = False
while not stop:
entrees = entrees_joueur()
if entrees[QUITTER] :
stop = True
#bien sûr, on appelle la fonction ici :
afficher_niveau(etat)
o
niveau()
Normalement, en lançant le code, vous verrez apparaître le cercle dans le coin haut gauche de la fenêtre :

En HAUT à gauche ? mais comment ça ? Et bien tout simplement, le repère de l'écran est inversé : l'axe y croît vers le bas.
!!! example "Activité"*
=== "Enoncé, partie 1"
Essayez de changer la position du personnage pour que le cercle apparaîsse en bas à gauche de la fenetre.
=== "Solution"
Il faut changer la position du cercle avec y au maximum.
La fenetre fait 720 pixels de haut, donc :
```python
def creer_personnage():
return {
POSITION : Vector2(0, 720)
}
```
===! "Enoncé, partie 2"
Même chose, mais au milieu de la fenêtre
=== "Solution"
On prends la moitié à chaque fois :
```python
def creer_personnage():
return {
POSITION : Vector2(1280/2, 720/2)
}
```
Déplacer le personnage
On va rendre le personnage réactif aux entrées de l'utilisateur, en le déplaçant avec les flèches.
On commence par UNE des directions.
import pygame
from pygame import *
ECRAN = "ECRAN"
QUITTER = "QUITTER"
PERSONNAGE = "PERSONNAGE"
POSITION = "POSITION"
DIRECTION = "DIRECTION"
VITESSE = "VITESSE"
def creer_personnage():
# On ajoute une vitesse et une direction au personnage
# La direction est un vecteur indiquant vers où le perso va
# la vitesse un nombre qui indique la vitesse du déplacement.
return {
POSITION : Vector2(1280/2, 720/2),
DIRECTION : Vector2(0, 0),
VITESSE : 10
}
def initialiser_niveau():
pygame.init()
ecran = pygame.display.set_mode([1280, 720])
return {
ECRAN : ecran,
PERSONNAGE : creer_personnage()
}
def entrees_joueur():
quitter = False
for evt in pygame.event.get():
if evt.type == pygame.KEYDOWN:
if evt.key == pygame.K_ESCAPE:
quitter = True
# cette fonction renvoie l'état actuel de toutes les
# touches du clavier. Une touche vaut True si elle est
# enfoncée, False sinon
touches = pygame.key.get_pressed()
direction = Vector2(0, 0)
# pygame.K_RIGHT est la touche fleche droite
if touches[pygame.K_RIGHT]:
direction.x += 1.0
return {
QUITTER : quitter,
DIRECTION : direction
}
# cette fonction modifie l'état du niveau en fonction
# des entrées du joueur
def appliquer_entrees_joueur(etat, entrees):
etat[PERSONNAGE][DIRECTION] = entrees[DIRECTION]
# cette fonction déplace le personnage
# en additionant sa vitesse et direction à sa position
def deplacer_personnage(etat):
etat[PERSONNAGE][POSITION] += etat[PERSONNAGE][VITESSE] * etat[PERSONNAGE][DIRECTION]
# cette fonction fait avancer l'état du jeu d'une frame
# on la découpe en sous-fonctions pour plus de clarté
# Chaque fonction représente une "règle" de fonctionnement
# de la simulation
def simuler_frame(etat):
deplacer_personnage(etat)
def afficher_niveau(etat):
etat[ECRAN].fill((0, 0, 0))
pygame.draw.circle(
etat[ECRAN],
(196, 154, 214),
etat[PERSONNAGE][POSITION],
20
)
pygame.display.flip()
def niveau():
etat = initialiser_niveau()
stop = False
while not stop:
entrees = entrees_joueur()
if entrees[QUITTER] :
stop = True
#on applique les entrées à l'état
appliquer_entrees_joueur(etat, entrees)
#on avance la simulation
simuler_frame(etat)
afficher_niveau(etat)
niveau()
Si vous lancez ce code et maintenez la flèche droite enfoncée, vous devriez voir le personnage se déplacer... très vite.
On verra plus tard quelques réglages. Avant ça, activité :
Activité
Permettez au personnage de se déplacer vers la gauche.
On ajoutes simplement la direction dans la captation des entrées :
Ajoutez le déplacement vertical
Rappel : l'axe y va croissant vers le bas !
il faut modifier la coordonnée y du vecteur direction
Et ne pas oublier que l'axe est INVERSE
Temps réel et précision
Vous avez vu que le personnage se déplace très vite. Et si vous aviez plusieurs ordinateurs, vous verriez aussi que le personnage ne se déplace pas à la même vitesse sur toutes les machines.
C'est parce que pour le moment, on enchaîne les frames sans aucune attente, donc la vitesse de déplacement dépends du nombre de frames par secondes, et donc de la vitesse de la machine sur laquelle s'exécute le jeu.
Commençons par attendre à chaque frame pour essayer d'avoir exactement 60 frames par seconde.
def niveau():
etat = initialiser_niveau()
# l'utilitaire python.time.Clock crée une
# horloge capable de mesurer le temps et
# de faire des attentes
horloge = pygame.time.Clock()
stop = False
while not stop:
entrees = entrees_joueur()
if entrees[QUITTER] :
stop = True
appliquer_entrees_joueur(etat, entrees)
simuler_frame(etat)
afficher_niveau(etat)
# clock.tick(FPS) mesure le temps écoulé depuis le dernier. FPS = frame par seconde
# appel, et attends de manière à ce que le temps écoulé
# soit de au moins 1/FPS secondes
horloge.tick(60)
Remplacez la fonction niveau par celle ci-dessus dans le code, et lancez le jeu. Le personnage devrait se déplacer plus doucement.
Ok, mais... à quelle vitessé précisément ? à quoi correspond 10 dans la vitesse du personnage ?
Des pixels par frame. Ok, mais c'est pas hyper compréhensible ça... On voudrait une unité plus humainement compréhensible. Par exemple, des pixels par seconde. C'est bien plus simple à manipuler : à 100 pixels par seconde, on sait que le personnage traverse l'écran verticalement en 7 secondes environ.
Idéalement, on veut connaître la durée de chaque frame. Et ça tombe bien, horloge.tick retourne la durée réelle mesurée pour la frame courante.
Récupérons sa valeur de retour et affichons là.
On lance le jeu, et on regarde l'affichage en console :
C'est en milisecondes, et vous voyez que c'est pas totalement stable. C'est normal, c'est parce que tick ne peut que garantir que les frames ne durent pas moins d'un certain temps (16ms pour 60 FPS). tick ne peut rien faire si la frame dure plus de 16ms, par exemple à cause d'un lag. Et donc pour que le jeu soit le plus fluide possible, on doit tenir compte de la durée réelle de chaque frame dans la simulation.
import pygame
from pygame import *
ECRAN = "ECRAN"
QUITTER = "QUITTER"
PERSONNAGE = "PERSONNAGE"
POSITION = "POSITION"
DIRECTION = "DIRECTION"
VITESSE = "VITESSE"
def creer_personnage():
# On modifie la vitesse pour la valeur que l'on veut en
# pixels par seconde
# j'ai pris 300 parce que ça semble raisonnable;
return {
POSITION : Vector2(1280/2, 720/2),
DIRECTION : Vector2(0, 0),
VITESSE : 300
}
def initialiser_niveau():
pygame.init()
ecran = pygame.display.set_mode([1280, 720])
return {
ECRAN : ecran,
PERSONNAGE : creer_personnage()
}
def entrees_joueur():
quitter = False
for evt in pygame.event.get():
if evt.type == pygame.KEYDOWN:
if evt.key == pygame.K_ESCAPE:
quitter = True
# cette fonction renvoie l'état actuel de toutes les
# touches du clavier. Une touche vaut True si elle est
# enfoncée, False sinon
touches = pygame.key.get_pressed()
direction = Vector2(0, 0)
# pygame.K_RIGHT est la touche fleche droite
if touches[pygame.K_RIGHT]:
direction.x += 1.0
if touches[pygame.K_LEFT]:
direction.x -= 1.0
if touches[pygame.K_UP]:
direction.y += -1.0 #-1.0 parce que axe inversé
if touches[pygame.K_DOWN]:
direction.y -= -1.0
return {
QUITTER : quitter,
DIRECTION : direction
}
def appliquer_entrees_joueur(etat, entrees):
etat[PERSONNAGE][DIRECTION] = entrees[DIRECTION]
# on s'attends maintenant à ce que dt soit en *secondes*
# (voir la fonction niveau pour l'explication)
def deplacer_personnage(etat, dt):
#remarquez le *dt en bout de ligne
etat[PERSONNAGE][POSITION] += etat[PERSONNAGE][VITESSE] * etat[PERSONNAGE][DIRECTION] * dt
# on s'attends maintenant à ce que dt soit en *secondes*
# (voir la fonction niveau pour l'explication)
def simuler_frame(etat, dt):
deplacer_personnage(etat, dt)
def afficher_niveau(etat):
etat[ECRAN].fill((0, 0, 0))
pygame.draw.circle(
etat[ECRAN],
(196, 154, 214),
etat[PERSONNAGE][POSITION],
20
)
pygame.display.flip()
def niveau():
etat = initialiser_niveau()
# l'utilitaire python.time.Clock crée une
# horloge capable de mesurer le temps et
# de faire des attentes
horloge = pygame.time.Clock()
stop = False
#on considère la première frame comme de taille fixe
dt = 0.016
while not stop:
entrees = entrees_joueur()
if entrees[QUITTER] :
stop = True
appliquer_entrees_joueur(etat, entrees)
# on passe la durée de la frame à la simulation
simuler_frame(etat, dt)
afficher_niveau(etat)
# ici, /1000 pour passer en secondes
# pour plus de lisibilité dans le reste du code
dt = horloge.tick(60) / 1000
# ici on a retiré le print
niveau()
Lancez le jeu et mesurez le temps que prends le personnage à traverser l'écran verticalement. Il devrait être de 2 secondes et demie environ. N'hésitez pas à changer la valeur de vitesse comme vous préférez.
Géométrie et précision
Activité
Lancez le jeu et déplacez le personnage, horizontalement, verticalement, et en diagonale. Remarquez-vous quelque chose ? Essayez d'expliquer pourquoi.
Et oui, le personnage se déplace plus vite en diagonale qu'horizontalement ou verticalement !
La raison, c'est qu'on fait :
Or, si on va disons à droite, direction vaut (1,0), donc de longueur 1. Si on va en diagonale bas-droite, direction vaut (1,1), de longueur racine de 2 (environ 1.4), et donc la vitesse en résultant va plus vite.
Voici comment résoudre ce problème :
def entrees_joueur():
# ...
direction = Vector2(0, 0)
if touches[pygame.K_RIGHT]:
direction.x += 1.0
if touches[pygame.K_LEFT]:
direction.x -= 1.0
if touches[pygame.K_UP]:
direction.y += -1.0
if touches[pygame.K_DOWN]:
direction.y -= -1.0
# normalize retourne une copie normalisée du vecteur,
# c'est à dire avec sa longueur à 1
# et avec la même direction
# on ne peut pas normaliser un vecteur de longueur 0
# C'est pour ca qu'on vérifie avec un if avant.
if direction.length() > 0:
direction = direction.normalize()
return {
QUITTER : quitter,
DIRECTION : direction
}
Remplacez la fonction par celle-ci et lancez le jeu. Vous pouvez observer que le personnage se déplace maintenant à vitesse égale dans toutes les directions.
Physique de base : gravité et saut
On va petit à petit transformer le jeu en jeu de plateforme. Donc, le personnage pourra se déplacer horizontalement, et sauter/tomber sur l'axe vertical.
Pour ça, il faut ajouter de la physique de base. La physique conserve la vitesse, ou une partie de la vitesse, entre les frames : c'est l'inertie.
Modifions le code du jeu pour permettre au personnage de sauter et de tomber.
import pygame
from pygame import *
ECRAN = "ECRAN"
QUITTER = "QUITTER"
PERSONNAGE = "PERSONNAGE"
POSITION = "POSITION"
DIRECTION = "DIRECTION"
VITESSE = "VITESSE"
INERTIE = "INERTIE"
SAUT = "SAUT"
# on ajoute au personnage une *inertie*. C'est un vecteur
def creer_personnage():
return {
POSITION : Vector2(1280/2, 720/2),
DIRECTION : Vector2(0, 0),
VITESSE : 300,
INERTIE : Vector2(0, 0)
}
def initialiser_niveau():
pygame.init()
ecran = pygame.display.set_mode([1280, 720])
return {
ECRAN : ecran,
PERSONNAGE : creer_personnage()
}
def entrees_joueur():
quitter = False
saut = False
# le saut est géré par un évènement et non par get_pressed.
# C'est plus précis pour les touches que le
# joueur va appuyer pendant peu de temps
for evt in pygame.event.get():
if evt.type == pygame.KEYDOWN:
if evt.key == pygame.K_ESCAPE:
quitter = True
# ici, on detecte le saut
if evt.key == pygame.K_UP:
saut = True
touches = pygame.key.get_pressed()
direction = Vector2(0, 0)
if touches[pygame.K_RIGHT]:
direction.x += 1.0
if touches[pygame.K_LEFT]:
direction.x -= 1.0
# On retire la direction verticale
#qui était ici avant
if direction.length() > 0:
direction = direction.normalize()
#on oublie pas de retourner la commande de saut
return {
QUITTER : quitter,
DIRECTION : direction,
SAUT : saut
}
# on traite la commande du saut.
def appliquer_entrees_joueur(etat, entrees):
etat[PERSONNAGE][DIRECTION] = entrees[DIRECTION]
if entrees[SAUT]:
# On modifie l'inertie du personnage pour avoir une vitesse
# verticale forte
# La valeur est très élevée parce que la gravitée va continuer à s'appliquer
# pendant le saut.
# Un changement d'inertie brutal s'appelle une *impulsion* dans le monde
# de la simulation physique
# On oublie pas que l'axe y est inversé, d'où le -
etat[PERSONNAGE][INERTIE] = Vector2(0, -500)
# ici, on applique la gravité au personnage
def gravite_personnage(etat, dt):
# on augmente simplement la vitesse vers le bas en fonction du temps passé
# J'ai pas calculé proprement les valeurs ici, j'ai simplement lancé le jeu
# et testé avec plusieurs valeurs. Pareil pour l'impulsion du saut
etat[PERSONNAGE][INERTIE] += Vector2(0, 1000) * dt
# On modifie l'application de la vitesse pour prendre en compte l'inertie
def deplacer_personnage(etat, dt):
# L'inertie est une vitesse comme une autre
etat[PERSONNAGE][POSITION] += (etat[PERSONNAGE][VITESSE] * etat[PERSONNAGE][DIRECTION] + etat[PERSONNAGE][INERTIE]) * dt
# on oublie pas d'appliquer la gravité
def simuler_frame(etat, dt):
gravite_personnage(etat, dt)
deplacer_personnage(etat, dt)
def afficher_niveau(etat):
etat[ECRAN].fill((0, 0, 0))
pygame.draw.circle(
etat[ECRAN],
(196, 154, 214),
etat[PERSONNAGE][POSITION],
20
)
pygame.display.flip()
def niveau():
etat = initialiser_niveau()
horloge = pygame.time.Clock()
stop = False
dt = 0.016
while not stop:
entrees = entrees_joueur()
if entrees[QUITTER] :
stop = True
appliquer_entrees_joueur(etat, entrees)
simuler_frame(etat, dt)
afficher_niveau(etat)
dt = horloge.tick(60) / 1000
niveau()
Lancez le jeu. Vous devriez voir le personnage tomber. Vous devriez aussi pouvoir sauter avec flèche haut.
Activité
Modifiez la physique du jeu pour qu'on ai l'impression que la gravité est moins forte.
Collision avec le sol
Si vous lancez le jeu et ne faites rien ... Le personnage disparaît sous le sol !
# On détecte quand le personnage sort de l'écran
def collision_sol(etat, dt):
if etat[PERSONNAGE][POSITION].y > 720:
#et on corrige
etat[PERSONNAGE][POSITION].y = 720
# on gère les collisions APRES avoir déplacé le personnage
def simuler_frame(etat, dt):
gravite_personnage(etat, dt)
deplacer_personnage(etat, dt)
collision_sol(etat, dt)
Activité
Vous avez sans doute remarqué que le cercle du personnage s'enfonce à moitié dans le sol.
Expliquez le problème puis résolvez-le. Il y a au moins deux solutions. L'une est bien meilleure que l'autre.
La position du personnage correspond au centre du cercle, et donc comme la position du personnage bloque au niveau du sol, le cercle, lui, s'enfonce plus.
La première solution, c'est de "décaler" le sol du rayon du cercle personnage (20) vers le haut
if etat[PERSONNAGE][POSITION].y > (720 - 20):
#et on corrige
etat[PERSONNAGE][POSITION].y = 720 - 20
Cette solution marche, mais elle risque de poser problème par la suite : on va devoir décaler toutes les intéractions physiques du rayon du personnage ! Ca peut vite devenir très compliqué...
La seconde solution, c'est de simplement dessiner le cercle "décalé" vers le haut de son rayon. Comme ça, la position du personnage correspondra à la position de ses "pieds"
Plateforme
Dans un jeu de plateformes, il faut... des plateformes.
Une plateforme est simplement une ligne horizontale. Le joueur peut traverser une plateforme par dessous, mais pas par dessus.
On va procéder par étapes :
- D'abord, on crée UNE plateforme et on la dessine sans qu'elle interagisse avec le joueur
- Ensuite, on gère la collision entre le joueur et la plateforme
- Finalement, on généralise le code à un nombre variable de plateformes.
Bon, commençons par créer une seule plateforme:
import pygame
from pygame import *
ECRAN = "ECRAN"
QUITTER = "QUITTER"
PERSONNAGE = "PERSONNAGE"
POSITION = "POSITION"
DIRECTION = "DIRECTION"
VITESSE = "VITESSE"
INERTIE = "INERTIE"
SAUT = "SAUT"
DEBUT = "DEBUT"
FIN = "FIN"
PLATEFORMES = "PLATEFORMES"
def creer_personnage():
return {
POSITION : Vector2(1280/2, 720/2),
DIRECTION : Vector2(0, 0),
VITESSE : 300,
INERTIE : Vector2(0, 0)
}
# Fonction pour créer une plateforme
# Les paramètres permettent de ne rien oublier, par rapport à un dictionaire
# debut et fin sont des coordonnées x
# hauteur est la coordonnée y
def creer_plateforme(debut, fin, hauteur):
return {
DEBUT : Vector2(debut, hauteur),
FIN : Vector2(fin, hauteur)
}
# On ajoute la plateforme au niveau.
# Pour le moment sous forme d'une valeur simple
def initialiser_niveau():
pygame.init()
ecran = pygame.display.set_mode([1280, 720])
return {
ECRAN : ecran,
PERSONNAGE : creer_personnage(),
PLATEFORMES : creer_plateforme(100, 300, 500)
}
def entrees_joueur():
quitter = False
saut = False
for evt in pygame.event.get():
if evt.type == pygame.KEYDOWN:
if evt.key == pygame.K_ESCAPE:
quitter = True
if evt.key == pygame.K_UP:
saut = True
touches = pygame.key.get_pressed()
direction = Vector2(0, 0)
if touches[pygame.K_RIGHT]:
direction.x += 1.0
if touches[pygame.K_LEFT]:
direction.x -= 1.0
if direction.length() > 0:
direction = direction.normalize()
return {
QUITTER : quitter,
DIRECTION : direction,
SAUT : saut
}
def appliquer_entrees_joueur(etat, entrees):
etat[PERSONNAGE][DIRECTION] = entrees[DIRECTION]
if entrees[SAUT]:
etat[PERSONNAGE][INERTIE] = Vector2(0, -500)
def gravite_personnage(etat, dt):
etat[PERSONNAGE][INERTIE] += Vector2(0, 1000) * dt
def deplacer_personnage(etat, dt):
etat[PERSONNAGE][POSITION] += (etat[PERSONNAGE][VITESSE] * etat[PERSONNAGE][DIRECTION] + etat[PERSONNAGE][INERTIE]) * dt
def collision_sol(etat, dt):
if etat[PERSONNAGE][POSITION].y > (720):
etat[PERSONNAGE][POSITION].y = 720
def simuler_frame(etat, dt):
gravite_personnage(etat, dt)
deplacer_personnage(etat, dt)
collision_sol(etat, dt)
# On déplace le code d'affichage du personnage dans un autre
# fonction pour plus de clarté
def afficher_personnage(etat):
pygame.draw.circle(
etat[ECRAN],
(196, 154, 214),
etat[PERSONNAGE][POSITION] + Vector2(0, -20),
20
)
# Et on définit une fonction pour afficher la plateforme
def afficher_plateformes(etat):
#pygame.draw.rect permet de dessiner un rectangle à l'écran :
# surface
# couleur
# Rectangle : Rect(gauche, haut, largeur, hauteur)
pygame.draw.rect(
etat[ECRAN],
(128, 128, 128), #C'est du gris
Rect(
etat[PLATEFORMES][DEBUT].x,
etat[PLATEFORMES][DEBUT].y,
etat[PLATEFORMES][FIN].y - etat[PLATEFORMES][FIN].x,
10)
)
# on dessine la ligne comme un rectangle, dont le bord haut
# est aligné avec la ligne et de quelques pixels d'epaisseur (ici, 10)
def afficher_niveau(etat):
etat[ECRAN].fill((0, 0, 0))
# On affiche la plateforme AVANT d'afficher le personnage, de cette manière en cas de superposition
# c'est le personnage qui sera devant
afficher_plateformes(etat)
afficher_personnage(etat)
pygame.display.flip()
def niveau():
etat = initialiser_niveau()
horloge = pygame.time.Clock()
stop = False
dt = 0.016
while not stop:
entrees = entrees_joueur()
if entrees[QUITTER] :
stop = True
appliquer_entrees_joueur(etat, entrees)
simuler_frame(etat, dt)
afficher_niveau(etat)
dt = horloge.tick(60) / 1000
niveau()
Si vous lancez le jeu, vous verrez apparaître une plateforme :

Maintenant, il faut gérer les collisions avec la plateforme.
Là, pas le choix, c'est DES MATHS.
TODO explications -> projections
TODO ajouter schema ici