Le meilleur moyen pour saisir le fonctionnement d'une nouvelle bibliothèque est de directement regarder un exemple parlant. Jetons un coup d'œil à une version simplifiée d'une animation de balle rebondissante.
import pygame
pygame.init()
size = width, height = 320, 240
speed = [2, 2]
color = 255, 255, 255
screen = pygame.display.set_mode(size)
ball = pygame.image.load("ball.png")
ballrect = ball.get_rect()
game_over = False
while not game_over:
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_over = True
ballrect = ballrect.move(speed)
if ballrect.left < 0 or ballrect.right > width:
speed[0] = -speed[0]
if ballrect.top < 0 or ballrect.bottom > height:
speed[1] = -speed[1]
screen.fill(color)
screen.blit(ball, ballrect)
pygame.display.flip()
pygame.quit()
quit()
Si vous voulez essayer cet exemple, mettez dans le même dossier, une image nommée ball.png
Vous allez être surpris en visitant le site internet de Pygame en voyant ce que les autres utilisateurs sont capables de faire avec Python.
Lorsque vous comprenez Python, vous pourrez utiliser Pygame pour créer un simple jeu en une ou deux semaines. Mais pour avoir un jeu complètement présentable, il vous faudra de nombreuses heures de travail.
Pour télécharger le dossier ressources cliquer ici : Téléchargez le fichier. Téléchargez le fichier source
from IPython.display import HTML
HTML('<iframe height="360" frameborder="0" width="640" src="http://www.youtube.com/embed/IGSmLvfEz18?feature=oembed"></iframe>')
from IPython.display import HTML
HTML('<iframe height="480" frameborder="0" width="640" src="http://www.youtube.com/embed/HXe-bq5i3nk?feature=oembed"></iframe>')
from IPython.display import HTML
HTML('<iframe height="480" frameborder="0" width="640" src="http://www.youtube.com/embed/og31B02dlFs?feature=oembed"></iframe>')
De simples pixels sur l'écran
Pygame possède une Surface d'affichage. C'est typiquement l'image visible à l'écran, et cette image est constituée de pixels. La principale façon de modifier ces pixels est d'appeler la fonction blit() : elle copie les pixels d'une image sur une autre.
C'est la première chose à comprendre. En appelant la fonction $\texttt{blit()}$ d'une image sur l'écran, vous changez simplement la couleur des pixels de l'écran. Les pixels ne sont pas ajoutés ou déplacés, c'est seulement la couleur de certains pixels qui est modifiée. Ces images que vous blitez sur l'écran sont des Surfaces dans Pygame, mais elles ne sont aucunement connectées à la Surface d'affichage. Quand elles sont blitées sur l'écran, elles sont copiées sur la Surface d'affichage, mais vous avez toujours accès à l'image originale.
Avec cette brève description, peut-être pouvez-vous déjà comprendre ce que nécessite l'animation d'une image. En réalité, nous ne déplaçons rien. Nous faisons simplement un blit de l'image dans une nouvelle position. Mais avant de dessiner l'image dans une nouvelle position, il faut effacer l'ancienne. Autrement, l'image serait visible à deux places sur l'écran. En effaçant rapidement l'image et en la redessinant à un nouvel endroit, nous réalisons l'illusion du mouvement.
À travers le reste du tutoriel, nous décomposerons ce processus en étapes simples. Nous verrons également comment animer plusieurs images à l'écran en même temps. Vous avez probablement déjà des questions, par exemple : comment effacer l'image avant de la redessiner dans une nouvelle position ? Peut-être êtes-vous déjà totalement perdu ? Espérons que le reste de ce tutoriel pourra éclaircir certaines choses.
Retournons sur nos pas
Peut-être que ce concept de pixels et d'images est encore un peu étranger à vos yeux ? Bonne nouvelle, durant les prochaines sections nous utiliserons du code pour faire tout ce que nous voulons, il n'utilisera pas de pixels. Nous allons créer une petite liste de 6 nombres en Python, et imaginer qu'elle représente des graphismes fantastiques que nous pourrions visualiser sur l'écran. Et il est surprenant de s'apercevoir à quel point cela correspond à ce que nous ferons plus tard avec des graphismes réels.
Alors commençons par créer notre liste et remplissons-la d'un beau paysage fait de $1$ et de $2$.
screen = [1, 1, 2, 2, 2, 1]
print(screen)
Nous venons de créer l'arrière-plan. Mais ça ne sera pas franchement excitant tant que nous n'aurons pas dessiné un joueur à l'écran. Nous allons créer un puissant Héros qui ressemblera à un $8$. Déposons-le au milieu de la carte et voyons de quoi il a l'air.
screen[3] = 8
print(screen)
Maintenant que notre écran n'est qu'une simple liste de nombres, il est plus facile de voir comment les déplacer ?
Déplacement de notre Héros
Avant que nous puissions déplacer notre personnage, nous avons besoin de garder une trace de sa position. Dans la section précédente, quand nous l'avons dessiné, nous l'avons juste posé à une position arbitraire. Faisons-le plus rigoureusement cette fois-ci.
playerpos = 3
screen[playerpos] = 8
print(screen)
Maintenant il est assez facile de le déplacer vers une nouvelle position. Changeons simplement la valeur de playerpos, et dessinons-le une nouvelle fois à l'écran.
playerpos = playerpos - 1
screen[playerpos] = 8
print(screen)
Aïe! Maintenant nous pouvons voir 2 héros. Un dans l'ancienne position, et un dans la nouvelle. C'est exactement la raison pour laquelle nous avons besoin d'effacer le héros dans son ancienne position avant de le dessiner sur sa nouvelle position. Pour l'effacer, nous devons changer la valeur dans la liste pour qu'elle soit de nouveau comme avant la présence du héros. Pour ça, nous devons conserver une trace des valeurs de l'affichage avant que notre héros ne les remplace. Il y a plusieurs manières de le faire, mais la plus simple est de garder une copie séparée de l'arrière-plan. Ceci signifie que nous devons faire subir quelques modifications à notre jeu.
Création d'une carte
Ce que nous voulons faire c'est créer une liste séparée que nous appellerons arrière-plan. Nous créerons cet arrière-plan de façon à ce qu'il soit comme notre écran original rempli de $1$ et de $2$. Ensuite nous copierons chaque objet dans l'ordre de d'affichage : de l'arrière-plan vers l'écran. Après, nous pourrons redessiner notre héros sur l'écran.
background = [1, 1, 2, 2, 2, 1]
screen = [0]*6 #a new blank screen
for i in range(6):
screen[i] = background[i]
print(screen)
playerpos = 3
screen[playerpos] = 8
print(screen)
Cela peut sembler être un surplus de travail. Nous n'en sommes pas plus loin d'où nous étions la dernière fois, lorsque nous avons tenté de le déplacer. Mais cette fois nous avons plus d'information pour pouvoir le déplacer correctement.
Déplacement de notre Héros (2ème essai)
Cette fois ci, il sera plus simple de déplacer le héros. D'abord nous effacerons le héros de son ancienne position. Nous faisons cela en recopiant les bonnes valeurs de l'arrière-plan sur l'écran. Ensuite, nous dessinerons le personnage dans sa nouvelle position sur l'écran.
print(screen)
screen[playerpos] = background[playerpos]
playerpos = playerpos - 1
screen[playerpos] = 8
print(screen)
Et voilà. Le héros s'est déplacé d'un pas vers la gauche. Nous pouvons utiliser le même code pour le bouger une nouvelle fois à gauche.
screen[playerpos] = background[playerpos]
playerpos = playerpos - 1
screen[playerpos] = 8
print(screen)
Excellent! Ce n'est pas exactement ce que l'on pourrait appeler une animation fluide. Mais avec quelques arrangements, nous ferons ce travail directement avec des graphismes sur l'écran.
Définition de blit
Dans la prochaine partie, nous transformerons notre programme qui utilise des listes en un programme qui utilise de vrais graphismes. Pour l'affichage des graphismes, nous utiliserons le terme blit fréquemment. Si vous êtes débutant dans le graphisme, vous êtes probablement peu familier avec ce terme.
De la liste à l'écran
Utiliser le code que nous avons vu dans les exemples plus haut, et le faire fonctionner avec Pygame est très simple :
background = [terrain1, terrain1, terrain2, terrain2, terrain2, terrain1]
screen = create_graphics_screen()
for i in range(6):
screen.blit(background[i], (i*10, 0))
playerpos = 3
screen.blit(playerimage, (playerpos*10, 0))
Ce code devrait vous sembler très familier, et prendre un peu de sens. La seule partie qui soit un travail supplémentaire est celle qui convertit la position du joueur en coordonnées sur l'écran. Pour l'instant nous utilisons simplement $\texttt{(playerpos*10, 0)}$.
Maintenant déplaçons l'image du joueur dans un autre endroit. Ce code ne devrait pas vous surprendre :
screen.blit(background[playerpos], (playerpos*10, 0))
playerpos = playerpos - 1
screen.blit(playerimage, (playerpos*10, 0))
Voila! Avec ce code, nous avons vu comment afficher un simple arrière-plan avec l'image du héros dessus. Ensuite nous avons correctement déplacé le héros d'un espace vers la gauche.
Mais c'est encore un peu maladroit. La première chose que nous voudrions faire serait de trouver une manière plus propre de représenter l'arrière-plan et la position du joueur. Et peut être de faire une vraie animation fluide.
Coordonnées écran
Pour positionner un objet sur l'écran, nous avons besoin de la fonction $\texttt{blit()}$ où l'on met l'image. Dans Pygame nous passons toujours nos positions comme des coordonnées $(X,Y)$. $X$ est le nombre de pixels vers la droite et $Y$ le nombre de pixels vers le bas. Le coin supérieur gauche d'une Surface correspond aux coordonnées $(0, 0)$. Un déplacement vers la droite donnerait $(10, 0)$, et ajouter un déplacement vers le bas nous donnerait $(10, 10)$. Quand nous blitons, l'argument passé en position représente le coin supérieur gauche de la source devant être placé sur la destination.
Pygame possède un conteneur intéressant, l'objet $\texttt{Rect}$ qui représente une zone rectangulaire avec ses coordonnées. Il est définit par son coin supérieur gauche et sa dimension. L'objet $\texttt{Rect}$ possède de nombreuses méthodes utiles qui peuvent vous aider à le déplacer et le positionner. Dans notre prochain exemple, nous représenterons les positions de nos objets avec des $\texttt{Rect}$.
Ensuite sachez que beaucoup de fonctions de Pygame utilisent les $\texttt{attributs}$ des objets $\texttt{Rect}$. Toutes ces fonctions peuvent accepter un simple tuple de 4 éléments (position gauche, position dessus, largeur, hauteur). Vous n'êtes pas toujours obligé d'utiliser ces objets $\texttt{Rect}$, mais vous les trouverez utiles. Ainsi, la fonction $\texttt{blit()}$ peut accepter un objet $\texttt{Rect}$ en tant qu'argument de position, elle utilise le coin supérieur gauche du $\texttt{Rect}$ comme position réelle.
Changer l'arrière-plan
Précédemment, nous avons stocké l'arrière-plan comme une liste de différents types de sol. C'est une bonne manière de créer un jeu basé sur des cases, mais nous voulons faire un défilement (scrolling en anglais) fluide. Pour faire simple, nous commencerons par modifier l'arrière-plan en une simple image qui couvre entièrement l'écran. De cette façon, quand nous voudrons effacer nos objets (avant de les redessiner) nous aurons simplement besoin de bliter la section effacée de l'arrière-plan dans l'écran.
En passant un troisième argument optionnel à la fonction $\texttt{blit()}$, nous lui indiquerons de bliter uniquement une sous-section de l'image source. Vous la verrez en action quand nous effacerons l'image du joueur.
Notez également, que lorsque nous aurons fini de tout dessiner, nous invoquerons la fonction $\texttt{pygame.display.update()} \quad$ qui affichera tout ce que nous avons dessiné sur l'écran.
Mouvement fluide
Pour obtenir quelque chose qui apparaisse comme un mouvement fluide, nous déplacerons quelques pixels à la fois. Voici le code pour faire un objet qui se déplace de manière fluide à travers l'écran :
screen = create_screen()
player = load_player_image()
background = load_background_image()
screen.blit(background, (0, 0)) #draw the background
position = player.get_rect()
screen.blit(player, position) #draw the player
pygame.display.update() #and show it all
for x in range(100): #animate 100 frames
screen.blit(background, position, position) #erase
position = position.move(2, 0) #move player
screen.blit(player, position) #draw new player
pygame.display.update() #and show it all
pygame.time.delay(100) #stop the program for 1/10 second
Nous pouvons aussi utiliser un joli personnage d'arrière-plan. Un autre avantage sur cette façon de procéder, est que l'image du joueur peut inclure de la transparence ou être découpée en section, elle sera toujours dessinée correctement sur l'arrière-plan.
Nous avons aussi fait appel à la fonction $\texttt{pygame.time.delay()}\quad$ à la fin de notre boucle pour ralentir un peu notre programme, autrement il tournerait tellement vite qu'on aurait pas le temps de le voir.
Et ensuite ?
À ce stade, le code n'est pas encore prêt pour réaliser le prochain jeu le plus vendu. Comment faire pour obtenir simplement de multiple déplacements d'objets ? Que sont réellement ces mystérieuses fonctions comme $\texttt{load_player_image()}$ ? Nous avons également besoin d'une manière simple d'accéder aux entrées de l'utilisateur (clavier, souris ou autre), et boucler sur plus de 100 images.
Les fonctions mystères
Des informations complètes sur ces types de fonctions peuvent être trouvées dans d'autres tutoriaux et références. Le module $\texttt{pygame.image}\quad$ possède une fonction $\texttt{load()}$ qui fera ce que nous voudrons. Les lignes pour charger des images devraient ressembler à ceci :
player = pygame.image.load('player.bmp').convert()
background = pygame.image.load('liquid.bmp').convert()
La fonction de chargement demande seulement un nom de fichier et retourne une nouvelle surface avec l'image chargée. Après le chargement, nous faisons appel à la méthode de Surface : $\texttt{convert()}\quad$ qui nous retourne une nouvelle Surface contenant l'image, mais convertie dans le même espace colorimétrique que notre affichage. Maintenant que les images ont le même format d'affichage, le blit est très rapide. Si nous ne faisons pas la conversion, la fonction $\texttt{blit()}$ est plus lente, c'est pourquoi il est préférable de faire la conversion de pixels d'un format à un autre au fur et à mesure.
Les deux fonctions, $\texttt{load()}$ et $\texttt{convert()}$, retournent de nouvelles Surfaces. Ceci signifie que nous avons réellement créé deux Surfaces à chacune de ces lignes. Dans d'autres langages de programmation, on obtiendrait une fuite de mémoire (ce n'est clairement pas une bonne chose). Heureusement Python est là pour les gérer, et Pygame effacera proprement la Surface que nous n'utiliserons pas.
Cette autre fonction mystérieuse que nous avons vue dans l'exemple précédent était $\texttt{create_screen()}$. Dans Pygame, c'est simple de créer une nouvelle fenêtre pour les graphismes. Le code pour créer une surface de $640\times480$ pixels est le suivant : (sans passer aucun autre argument, Pygame choisit la meilleure profondeur de couleur et le meilleur espace colorimétrique pour nous)
screen = pygame.display.set_mode((640, 480))
Manipulation des entrées utilisateur
Nous avons désespérément besoin de modifier la boucle principale pour prendre en compte une entrée utilisateur, comme par exemple, lorsque celui ci ferme la fenêtre. Nous devons ajouter la manipulation d'évènements à notre programme.Tous les programmes graphiques utilisent ce concept basé sur les évènements. Le programme reçoit des évènements de l'ordinateur lorsqu'une touche du clavier est enfoncée ou lorsque la souris s'est déplacée. Alors le programme répond aux différents évènements. Au lieu de boucler sur 100 images, nous continuons à boucler jusqu'à ce que l'utilisateur nous demande d'arrêter :
while 1:
for event in pygame.event.get():
if event.type in (QUIT, KEYDOWN):
sys.exit()
move_and_draw_all_game_objects()
Ce code boucle en continu, et vérifie s'il y a un quelconque évènement provenant de l'utilisateur. Nous quittons le programme si l'utilistateur appuie sur un bouton de son clavier ou clique sur le bouton de fermeture de la fenêtre. Ensuite, après avoir vérifié tous les évènements, nous déplaçons et dessinons tous les objets du jeu. (Nous les effacerons également avant de les déplacer).
Déplacer de multiples images
Voici la partie où nous allons vraiment changer les choses. Disons que nous désirons déplacer 10 images différentes en même temps à l'écran. Une bonne manière de le faire est d'utiliser les classes Python. Nous allons créer une classe qui représente un objet du jeu. Cet objet aura une fonction pour se déplacer lui-même, nous pourrons alors en créer autant que nous le voulons. Les fonctions pour dessiner et déplacer cet objet nécessitent de travailler d'une manière où ils se déplacent seulement d'une image (ou d'un pas) à la fois. Voici le code de python pour créer notre classe :
class GameObject:
def __init__(self, image, height, speed):
self.speed = speed
self.image = image
self.pos = image.get_rect().move(0, height)
def move(self):
self.pos = self.pos.move(0, self.speed)
if self.pos.right > 600:
self.pos.left = 0
Nous avons donc deux fonctions dans notre classe. La méthode $\texttt{__init__()}$ construit notre objet. Elle le positionne et définit sa vitesse. La méthode $\texttt{move()}$ bouge l'objet d'un pas. S'il va trop loin, elle déplace l'objet en arrière vers la gauche.
Positionner le tout
Maintenant avec notre nouvelle classe, nous pouvons assembler l'intégralité du jeu. Voici à quoi ressemblerait la fonction principale de notre programme :
screen = pygame.display.set_mode((640, 480))
player = pygame.image.load('player.bmp').convert()
background = pygame.image.load('background.bmp').convert()
screen.blit(background, (0, 0))
objects = []
for x in range(10): #create 10 objects
o = GameObject(player, x*40, x)
objects.append(o)
while 1:
for event in pygame.event.get():
if event.type in (QUIT, KEYDOWN):
sys.exit()
for o in objects:
screen.blit(background, o.pos, o.pos)
for o in objects:
o.move()
screen.blit(o.image, o.pos)
pygame.display.update()
pygame.time.delay(100)
C'est le code dont nous avons besoin pour animer 10 objets à l'écran. Le seul point qui ait besoin d'explication, ce sont les deux boucles que nous utilisons pour effacer tous les objets et dessiner tous les objets. De façon à faire les choses proprement, nous avons besoin d'effacer tous les objets avant de redessiner chacun d'eux. Dans notre exemple nous n'avons pas de problème, mais quand les objets se recouvrent, l'utilisation de deux boucles comme celles-ci devient nécessaire. Autrement l'effacement de l'ancienne position d'un objet pourrait effacer la nouvelle position d'un objet affiché avant.
Le mot de la fin
Alors quelle sera la prochaine étape sur la route de votre apprentissage ? D'abord, jouer un peu avec cet exemple. La version complète de cet exemple est disponible dans le répertoire exemples de Pygame, sous le nom $\texttt{moveit.py}$. Jetez un coup d'oeil sur le code et jouez avec, lancez-le et apprenez-le.
Il y a plusieurs choses que vous pouvez faire avec, comme utiliser plus d'un type d'objet. Trouvez une façon pour supprimer proprement les objets quand vous ne désirez plus les afficher. Pour faire une mise à jour, utilisez la méthode $\texttt{display.update()}$ pour passer une liste de zones d'écran qui ont changé.
Il existe beaucoup d'autres tutoriaux et exemples pour Pygame qui peuvent vous aider en toutes circonstances. Alors maintenant, pour retenir en apprenant, retenez en lisant. :-)
Enfin, vous êtes libre de vous inscrire sur la mailing-list de Pygame ou dans un salon de discussion et poser toutes les questions que vous voulez à ce sujet. Il y a toujours quelqu'un pour vous aider.
Pour finir, prenez du plaisir, c'est pour ça que les jeux sont faits !