Licence CC BY

La gestion des événements

L’auteur de ce contenu recherche un repreneur. N’hésitez pas à le contacter par MP pour proposer votre aide !

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 ?

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 :

pygame.event
pygame module for interacting with events and queues
pygame.event.pump — internally process pygame event handlers
pygame.event.get — get events from the queue
pygame.event.poll — get a single event from the queue
pygame.event.wait — wait for a single event from the queue
pygame.event.peek — test if event types are waiting on the queue
pygame.event.clear — remove all events from the queue
pygame.event.event_name — get the string name from and event id
pygame.event.set_blocked — control which events are allowed on the queue
pygame.event.set_allowed — control which events are allowed on the queue
pygame.event.get_blocked — test if a type of event is blocked from the queue
pygame.event.set_grab — control the sharing of input devices with other applications
pygame.event.get_grab — test if the program is sharing input devices
pygame.event.post — place a new event on the queue
pygame.event.Event — create a new event object
pygame.event.EventType — pygame object for representing SDL events

http://www.pygame.org/docs/ref/event.html

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 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 :

L'utilisation processeur est assez conséquente !

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.

On ne consomme presque rien côté processeur

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 :soleil: !

Voyons ce que l’on peut mettre dans notre panier :

QUIT
ACTIVEEVENT
KEYDOWN
KEYUP
MOUSEMOTION
MOUSEBUTTONUP
MOUSEBUTTONDOWN
JOYAXISMOTION
JOYBALLMOTION
JOYHATMOTION
JOYBUTTONUP
JOYBUTTONDOWN
VIDEORESIZE
VIDEOEXPOSE
USEREVENT

http://www.pygame.org/docs/ref/event.html

Ç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 :diable: )

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 :

The window will receive pygame.ACTIVEEVENT events as the display gains and loses input focus.

https://www.pygame.org/docs/ref/display.html

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 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 tuples 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)

Une manette
Une manette

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 :

If the display is set with the pygame.RESIZABLE flag, pygame.VIDEORESIZE events will be sent when the user adjusts the window dimensions.

https://www.pygame.org/docs/ref/display.html

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 … :

Hardware displays that draw direct to the screen will get pygame.VIDEOEXPOSE events when portions of the window must be redrawn.

https://www.pygame.org/docs/ref/display.html

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

Clem suit la souris !

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()

Mon beau dessin !

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 :lol: ) !

  • 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


  1. c’est un système universel pour la notation des couleurs. Pour plus d’informations, voir : Wikipédia 


Et voilà ! Maintenant vous savez comment gérer différents types d’événements, nous allons pouvoir passer au TD !