Dans ce nouveau chapitre, on va pouvoir passer aux choses intéressantes : les interactions avec l’utilisateur par le biais des événements !
Allez hop, c’est parti !
- Qu'est-ce qu'un événement ?
- Comment les capturer ?
- Les types d'événements disponibles
- Les événements disponibles !
- Entrainons-nous avec les événements !
Qu'est-ce qu'un événement ?
En informatique, un événement peut être une entrée clavier (soit l’appui soit le relâchement d’une touche), le déplacement de votre souris, un clic (encore une fois, appui ou relâchement, qui seront traités comme deux événements distincts). Un bouton de votre joystick peut aussi engendrer un événement, et même la fermeture de votre fenêtre est considéré comme un événement !
Pour Pygame, un événement est représenté par un type
et divers autres attributs que nous allons détailler dans ce chapitre.
De plus, il faut savoir que chaque événement créé est envoyé sur une file (ou queue), en attendant d’être traité. Quand un événement entre dans cette queue, il est placé à la fin de celle-ci. Vous l’aurez donc compris, le premier événement transmis à Pygame sera traité en premier ! Cette notion est très importante, puisque nous allons nous en servir sous peu !
Ce type de queue est dit FIFO
.
Comment les capturer ?
Comme on vous l’avait montré succinctement dans les chapitres précédents, on utilise le module event
de Pygame.
Voici ce que nous dit la documentation à propos de ce module :
Et comme on peut le voir, le module event
ne permet pas que d’intercepter des événements. Il nous permet aussi de créer des événements. Et même d’en bloquer !
Hein ? Mais pourquoi voudrait-on bloquer un événement ?
Et bien, prenons l’exemple d’une boite de dialogue. On va récupérer uniquement les chiffres et les lettres du clavier, et les afficher les unes à la suite des autres. Mais si notre utilisateur appuie sur F8 ou l’on ne sait pas quelle autre touche de fonction ? Que va faire notre programme ? Il va essayer d’afficher la valeur de la touche tapée (donc A affichera ’A’). Mais quelle valeur est affectée à notre touche de fonction pour Pygame ? Aucune ! Notre programme planterait avec un joli traceback
.
Voyons maintenant les différentes méthodes qui nous sont offertes pour capturer ces événements :
-
pygame.event.get()
- obtient les événements qui attendent sur la queue. S’il n’y en a pas, retourne un ensemble vide
-
pygame.event.poll()
- obtient un seul événement qui attend sur la queue. S’il n’y en a pas, retourne un ensemble vide
-
pygame.event.wait()
- attend qu’un événement arrive sur la queue et le retourne
S’il y a autant de méthodes différentes pour un seul but, c’est assez simple en fait (même si cela peut paraitre tordu au premier abord). Regardons de plus près ce que font ces méthodes.
pygame.event.get()
retourne une Eventlist
d’après la documentation. Prenons le cas où l’on aimerait pouvoir gérer CTRL + C dans un programme. C’est là que pygame.event.get()
nous sera très utile ! Car il va intercepter tous les événements, donc deux appuis de touches simultanés !
pygame.event.poll()
, lui, retourne seulement une instance de Eventtype
, donc un seul événement depuis la queue
pygame.event.wait()
retourne aussi une instance de Eventtype
, donc toujours un événement depuis la queue
Pourquoi Pygame dispose de deux méthodes faisant exactement la même chose ?
Hé hé ! Ces méthodes ne font pas totalement la même chose, elles ne font que retourner le même type d’objet
Si on se penche sur pygame.event.poll()
, on verra que celle-ci va occuper notre processeur, même si aucuns événements ne se sont produits, en tentant d’en récupérer un (un événement) à chaque fois que l’on lui demande !
Regardons cette capture d’écran pour mieux comprendre :
Alors que pygame.event.wait()
, lui va faire ’dormir’ notre programme en attendant qu’un événement arrive sur la queue ! En conséquence, s’il n’y a pas d’événement(s) sur la queue, et bien … il va tout simplement bloquer le thread
principal, autrement dit votre application va "s’arrêter" pendant une durée indéterminée … Pas pratique si on veut mettre à jour la position de nos ennemis à chaque frame
, par exemple.
Vous comprenez la différence maintenant ?
Si vous ne voulez pas utiliser tout le processeur de l’utilisateur, alors utilisez pygame.event.wait()
, sinon si vous vous en fichez, prenez pygame.event.poll()
! Et pour ceux qui souhaitent intercepter plusieurs événements (ou non, on peut très bien avoir qu’un seul événement en attente sur la queue), il y a pygame.event.get()
. C’est généralement celui-ci que l’on utilise, car il permet de tout couvrir.
Il faut aussi savoir que l’on ne va pas les utiliser de la même manière !
Utilisation de pygame.event.get()
:
1 2 | for event in pygame.event.get(): if event.type == KEYDOWN: ... |
Utilisation de pygame.event.poll()
(similaire à pygame.event.wait()
):
1 2 | event = pygame.event.poll() if event.type == KEYDOWN: ... |
Attends une seconde … pygame.event.get()
retourne un itérable et pas pygame.event.poll()
ainsi que pygame.event.wait()
?
Oui, c’est tout à fait ça !
Les types d'événements disponibles
Allez, on part faire du shopping !
Voyons ce que l’on peut mettre dans notre panier :
Ça en fait peu en fait, non ? En fait, ici ce ne sont que les types des différents événements que Pygame peut nous capturer.
On va quand même vous les détailler, car nous sommes des gentils, hein ? (Dîtes oui en hochant la tête )
Donc, reprenons.
QUIT
, c’est lui qu’on utilise pour gérer l’événement de fermeture de notre fenêtre, donc quand l’utilisateur clique sur la croix. Il ne renvoie rien.
Concernant ACTIVEEVENT
, Pygame nous indique ceci :
Donc le type d’événement ACTIVEEVENT
arrive sur la queue uniquement quand votre fenêtre perd ou regagne le focus de votre souris. Il renvoie le gain (gain
), c’est à dire si vous avez perdu ou gagné le focus, et son état (state
), qui veut dire soit : la fenêtre est visible, la fenêtre est icônifiée dans la barre des taches
KEYDOWN
et KEYUP
, on va les traiter ensemble, ils sont presque de la même famille.
Un événement de type KEYDOWN
est placé sur la queue dès que vous appuyez sur une touche (donc au point où l’on en est dans l’écriture de ce tutoriel, on a placé plus d’une dizaine de milliers d’événements de ce type sur la queue de nos navigateurs). Il retourne le nom unicode (unicode
; donc ’b’ par exemple), key
, le code de la touche - par exemple le code de la touche X est 120, mais on tapera K_x
, plus simple non ? Il retourne aussi mod
, c’est un modifier, il indique quelles touches de contrôle sont actuellement actives (CTRL, ALT, SHIFT … etc).
Alors qu’un événement de type KEYUP
ne sera placé sur la queue que quand vous aurez relâché une touche ! Celui-là nous renverra uniquement le code de la touche relâchée, et le modifier.
Alors autant utiliser tout le temps KEYUP
! En plus c’est plus court que KEYDOWN
Et là je dis carton rouge pour le monsieur au premier rang !
Car un événement de type KEYDOWN
est extrêmement utile ! Prenons l’exemple d’un jeu, puisque nous allons en créer un petit très bientôt. Votre personnage, vous voulez (dîtes pas le contraire, c’est certain) qu’il se déplace tant que vous appuyez sur Z / Q / S / D, pas vrai ? Alors c’est là que ce type d’événement nous sera utile.
Mais à contrario, le type d’événement KEYUP
, lui, nous sera utile quand on appuie sur la touche pour faire sauter notre personnage ! On ne voudrait tout de même pas qu’il puisse sauter à l’infini, même en étant dans les airs, hein ? De cette manière, un seul appui à la fois, et on devra vérifier que notre personnage n’est pas en l’air avant de le faire sauter.
MOUSEMOTION
est un événement produit lorsque vous déplacez votre souris (uniquement lors de son déplacement). Un événement de type MOUSEMOTION
nous renverra la position actuelle de la souris (pos
), le mouvement relatif (rel
) effectué depuis le dernier événement MOUSEMOTION
(donc de combien de pixels, en x et y, notre souris s’est déplacée depuis son ancienne position), et les boutons actuellement enfoncés (buttons
). rel
et pos
étant des tuple
s de deux éléments, une abscisse et une ordonnée.
MOUSEBUTTONUP
et MOUSEBUTTONDOWN
font la même chose sauf que ici encore, l’un est appelé dès qu’on appuie sur un bouton, et l’autre quand on relâche un bouton ! Et tous deux renverront pos
(un tuple
de deux éléments également, l’abscisse et l’ordonnée), la position actuelle de la souris dans votre fenêtre, et button
, les boutons enfoncés / relâchés. button
prend la valeur 1 si on clic avec le bouton gauche, 2 pour le droit, 3 pour un clic molette, 4 pour un déplacement de la molette vers le haut, et 5 pour un déplacement de la molette vers le bas.
Tous les types d’événements commençant par JOY
sont mis sur la queue quand vous générez un événement avec un joystick (encore faut il en connecter un)
JOYAXISMOTION
est appelé quand vous déplacez un des deux axes de votre manette, et renvoie axis
, l’axe (0 : axe horizontal, 1 : axe vertical), joy
, le joystick qui a déclenché l’événement, et value
, -1 (haut ou gauche) ou +1 (bas ou droite)
JOYBALLMOTION
est appelé quand vous bougez une des ball
de votre manette, et renverra joy
, le joystick ayant crée cet événement, ball
, la ball
qui a bougé, et rel
, le mouvement relatif depuis le dernier mouvement de cette ball
JOYHATMOTION
est appelé quand vous bougez un des hat
de votre manette (si elle en a, hein) et renverra comme les précédents joy
, hat
et value
(hat
: le hat
ayant bougé, value
: -1 ou +1 pour "haut" ou "bas")
JOYBUTTONUP
et JOYBUTTONDOWN
renvoi la même chose, joy
, et button
(le bouton qui a déclenché l’événement), mais l’un est appelé à l’appuie, l’autre au relâchement du dit bouton
Voilà ce que nous dit Pygame à propos de VIDEORESIZE
:
Donc cet événement n’est mit sur la queue que si vous avez créé votre fenêtre avec ce fameux flag RESIZABLE
Et pour ce qu’il en est de VIDEOEXPOSE
, il nous indique ceci, dans son obscure dialecte … :
Celui-là ne sera donc pas très utile, car il est déclenché uniquement quand vous devez réactualiser une portion de votre écran
Quant à USERVENT
, lui, est le type de tous les événements que vous, en tant que programmeur, pouvez créer.
Les événements disponibles !
Ouff, vous y êtes arrivé.
Si vous avez réussi à suivre tout ce que l’on a dit, bravo !
Maintenant que nous avons vu quels types d’événement sont disponibles, et comment les capturer, on va voir comment capturer … un Pokémon ! un événement complet, donc type et attributs inclus.
Vous vous rappelez comment capturer un événement ? Super !
Pour les poissons rouges, voici le code :
1 2 3 | for event in pygame.event.get(): if event.type == un_type: une_fonction_a_executer() |
Pour savoir sur quelle touche on a tapé, on y arrive. Mais avant ! Il va nous falloir vous assommer avec ce qui va devenir votre livre de chevet pour fanatique de pygame (quel titre honorifique !) : la documentation.
Et même plus précisément, la table des keys disponibles, que voici rien que pour vous en exclusivité ! (pour une consultation à part : lien)
KeyASCII | ASCII | Common Name |
---|---|---|
K_BACKSPACE | \b | backspace |
K_TAB | \t | tab |
K_CLEAR | clear | |
K_RETURN | \r | return |
K_PAUSE | pause | |
K_ESCAPE | ^[ | escape |
K_SPACE | space | |
K_EXCLAIM | ! | exclaim |
K_QUOTEDBL | " | quotedbl |
K_HASH | # | hash |
K_DOLLAR | $ | dollar |
K_AMPERSAND | & | ampersand |
K_QUOTE | quote | |
K_LEFTPAREN | ( | left parenthesis |
K_RIGHTPAREN | ) | right parenthesis |
K_ASTERISK | * | asterisk |
K_PLUS | + | plus sign |
K_COMMA | , | comma |
K_MINUS | - | minus sign |
K_PERIOD | . | period |
K_SLASH | / | forward slash |
K_0 | 0 | 0 |
K_1 | 1 | 1 |
K_2 | 2 | 2 |
K_3 | 3 | 3 |
K_4 | 4 | 4 |
K_5 | 5 | 5 |
K_6 | 6 | 6 |
K_7 | 7 | 7 |
K_8 | 8 | 8 |
K_9 | 9 | 9 |
K_COLON | : | colon |
K_SEMICOLON | ; | semicolon |
K_LESS | < | less-than sign |
K_EQUALS | = | equals sign |
K_GREATER | > | greater-than sign |
K_QUESTION | ? | question mark |
K_AT | @ | at |
K_LEFTBRACKET | [ | left bracket |
K_BACKSLASH | \ | backslash |
K_RIGHTBRACKET | ] | right bracket |
K_CARET | ^ | caret |
K_UNDERSCORE | _ | underscore |
K_BACKQUOTE | ` | grave |
K_a | a | a |
K_b | b | b |
K_c | c | c |
K_d | d | d |
K_e | e | e |
K_f | f | f |
K_g | g | g |
K_h | h | h |
K_i | i | i |
K_j | j | j |
K_k | k | k |
K_l | l | l |
K_m | m | m |
K_n | n | n |
K_o | o | o |
K_p | p | p |
K_q | q | q |
K_r | r | r |
K_s | s | s |
K_t | t | t |
K_u | u | u |
K_v | v | v |
K_w | w | w |
K_x | x | x |
K_y | y | y |
K_z | z | z |
K_DELETE | delete | |
K_KP0 | keypad 0 | |
K_KP1 | keypad 1 | |
K_KP2 | keypad 2 | |
K_KP3 | keypad 3 | |
K_KP4 | keypad 4 | |
K_KP5 | keypad 5 | |
K_KP6 | keypad 6 | |
K_KP7 | keypad 7 | |
K_KP8 | keypad 8 | |
K_KP9 | keypad 9 | |
K_KP_PERIOD | . | keypad period |
K_KP_DIVIDE | / | keypad divide |
K_KP_MULTIPLY | * | keypad multiply |
K_KP_MINUS | - | keypad minus |
K_KP_PLUS | + | keypad plus |
K_KP_ENTER | \r | keypad enter |
K_KP_EQUALS | = | keypad equals |
K_UP | up arrow | |
K_DOWN | down arrow | |
K_RIGHT | right arrow | |
K_LEFT | left arrow | |
K_INSERT | insert | |
K_HOME | home | |
K_END | end | |
K_PAGEUP | page up | |
K_PAGEDOWN | page down | |
K_F1 | F1 | |
K_F2 | F2 | |
K_F3 | F3 | |
K_F4 | F4 | |
K_F5 | F5 | |
K_F6 | F6 | |
K_F7 | F7 | |
K_F8 | F8 | |
K_F9 | F9 | |
K_F10 | F10 | |
K_F11 | F11 | |
K_F12 | F12 | |
K_F13 | F13 | |
K_F14 | F14 | |
K_F15 | F15 | |
K_NUMLOCK | numlock | |
K_CAPSLOCK | capslock | |
K_SCROLLOCK | scrollock | |
K_RSHIFT | right shift | |
K_LSHIFT | left shift | |
K_RCTRL | right ctrl | |
K_LCTRL | left ctrl | |
K_RALT | right alt | |
K_LALT | left alt | |
K_RMETA | right meta | |
K_LMETA | left meta | |
K_LSUPER | left windows key | |
K_RSUPER | right windows key | |
K_MODE | mode shift | |
K_HELP | help | |
K_PRINT | print screen | |
K_SYSREQ | sysrq | |
K_BREAK | break | |
K_MENU | menu | |
K_POWER | power | |
K_EURO | euro |
Alors maintenant voyons comme on ferait pour capturer un appuie sur la touche J par exemple :
1 2 3 4 5 | for event in pygame.event.get(): if event.type == pygame.KEYDOWN: # ici on suppose que vous avez fait un simple import pygame if event.key == pygame.K_j: une_action() |
Tout bête !
Quand on a un event
du type que l’on veut traiter, on doit ensuite traiter ses attributs pour l’identifier, et le tour est joué !
Entrainons-nous avec les événements !
Et maintenant voici quelques exercices, que l’on va aussi coder bien entendu
Premier exercice
Coder un script pour créer une fenêtre, puis la détruire si l’on appuie sur F.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import pygame pygame.init() ecran = pygame.display.set_mode((640, 480)) continuer = True while continuer: for event in pygame.event.get(): if event.type == pygame.KEYDOWN: if event.key == pygame.K_f: continuer = False pygame.quit() |
Deuxième exercice
Coder un script pour déplacer Clem à la souris (Clem doit toujours suivre la souris).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import pygame pygame.init() ecran = pygame.display.set_mode((640, 480)) clem = pygame.image.load("clem.png").convert_alpha() pos_clem = (0, 0) continuer = True while continuer: pygame.draw.rect(ecran, (255, 255, 255), (0, 0, 640, 480)) for event in pygame.event.get(): if event.type == pygame.MOUSEMOTION: pos_clem = event.pos if event.type == pygame.QUIT: continuer = False ecran.blit(clem, pos_clem) pygame.display.flip() pygame.quit() |
Hum … On vous des explications sur le pygame.draw.rect(ecran, (255, 255, 255), (0, 0, 640, 480))
, non ?
En fait, on fait appel à la fonction rect()
du module draw
de Pygame. Cette fonction permet de dessiner un rectangle d’une couleur que l’on veut, où l’on veut, de la taille que l’on veut. Cela nous permet ici d’effacer l’écran à chaque frame
pour ne pas avoir une trace pas très belle derrière Clem.
Voici son code pour mieux comprendre :
pygame.draw.rect(surface, couleur, rectangle)
-
surface est une image (ou votre fenêtre)
-
couleur est un tuple (de (0, 0, 0) à (255, 255, 255)) représentant notre couleur sous la forme RGB1
-
rectangle est aussi un tuple, mais de quatre éléments :
- le premier est le point de départ en abscisse
- le second est le point de départ en ordonnée
- le troisième est la largeur du rectangle
- et le dernier la longueur du rectangle
Et voilà ! Maintenant, Clem suit votre souris !
Troisième exercice
Coder un script pour dessiner sur l’écran au relâchement d’un bouton de la souris.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import pygame pygame.init() ecran = pygame.display.set_mode((640, 480)) largeur = 10 hauteur = 10 couleur = (20, 180, 20) continuer = True while continuer: for event in pygame.event.get(): if event.type == pygame.MOUSEBUTTONUP: x, y = event.pos pygame.draw.rect(ecran, couleur, (x, y, largeur, hauteur)) if event.type == pygame.QUIT: continuer = False pygame.display.flip() pygame.quit() |
Regardez ce beau dessin !
Des exercices facultatifs
Ici on vous allez trouver une liste d’exercices, à faire ou pas, ça c’est vous qui choisissez.
Seule différence : vous êtes sans filet ! Si vous avez un problème avec votre code, il faudra ouvrir la documentation ou demander sur le forum si vraiment vous n’y arrivez pas
- une class
Event
pour simplifier la gestion des événements, et pouvoir par exemple taper ce code :
1 2 3 4 5 | for event in my_event_listenner(): if event == (MOUSEBUTTONDOWN, 1): # si on a un clic souris enfoncé avec le bouton n°1 ... if event == (KEYDOWN, K_t): # si on a une key down avec la touche t ... |
Après, à vous d’ajouter vos méthodes pour rendre cette gestion encore plus simple (ou pas ) !
-
une classe
Joystick
pour gérer plus facilement un joystick, vous avez carte blanche pour en faciliter l’utilisation ! -
carrément une surcouche à la gestion des événements (tous, absolument tous) pour rendre l’usage plus orienté objet, à la SFML pour ceux qui connaissent
Et voilà ! Maintenant vous savez comment gérer différents types d’événements, nous allons pouvoir passer au TD !