7: ANCORA SUGLI EVENTI

GLI ALTRI ATTRIBUTI DI UN Event

Abbiamo detto che, oltre all'attributo type, l'oggetto Event contiene altri attributi (che variano secondo il tipo di evento), che specificano meglio quello che è successo. La documentazione ufficiale non è molto chiara su di essi, e fornisce solo un elenco sintetico di questi atributi; vediamone comunque alcuni che useremo spesso in seguito.

Un evento di tipo MOUSEBUTTONDOWN o MOUSEBUTTONUP ha, oltre al type, altri due attributi:

pos una tuple con due elementi interi che contiene le coordinate del clic rispetto alla finestra principale di pygame;
button un numero intero che indica quale bottone è stato premuto; anche qui pygame definisce una serie di costanti:
  • BUTTON_LEFT: bottone sinistro
  • BUTTON_CENTER: bottone centrale (o click sulla rotella)
  • BUTTON_RIGHT: bottone destro
  • BUTTON_WHEELDOWN: rotella giù
  • BUTTON_WHEELUP: rotella su

Quindi se ev è una variabile di tipo Event (e ev.type == MOUSEBUTTONDOWN o ev.type == MOUSEBUTTONUP) con ev.pos otterrò la coppia (tuple) delle coordinate, con ev.pos[0] la sola x (l'elemento della coppia di indice 0) e con ev.pos[1] la sola y. Inoltre con ev.button potrò sapere quale bottone è stato premuto.

Anche l'evento MOUSEMOTION ha l'attributo pos che funziona allo stesso modo.

NUOVO PROGRAMMA: prova_clic.py

Ricarichiamo il nostro programma prova_eventi.py, (qui), salviamolo come prova_clic.py e modifichiamolo in modo che scriva sulla finestra di IDLE le coordinate del nostro clic.


.   .   .
    for ev in pygame.event.get():
        if ev.type == QUIT:                  # chiusura della finestra
            done = True
        elif ev.type == MOUSEBUTTONDOWN:     # click del mouse
            mouse_x = ev.pos[0]
            mouse_y = ev.pos[1]
            print("Hai fatto click in", mouse_x, ",", mouse_y)
.   .   .

Nelle righe #16 e #17 assegniamo alla variabile mouse_x la x del click (che è l'elemento di indice 0 nella tuple ev.pos) e a mouse_y la y. Poi stampiamo il tutto con una print(). Notate che ho usato le due variabili mouse_x e mouse_y solo per rendere più chiaro il codice, ma avrei potuto scrivere direttamente ev.pos[0] e ev.pos[1] tra gli argomenti della print(). Allenatevi a cliccare sulla finestra cercando di prevedere, più o meno, quali sono le coordinate del puntatore.

ESERCIZIO 7.1: Modificate il programma in modo che, ad ogni clic, scriva anche quale tasto del mouse è stato premuto.
SUGGERIMENTO: Nel blocco che viene eseguito quando si verifica l'evento MOUSEBUTTONDOWN dovete inserire una serie di if ... elif per ognuno dei possibili valori dell'attributo button.
ESERCIZIO 7.2: Modificate ancora il programma in modo che scriva "Hai premuto un bottone in ... " quando premete e "Hai rilasciato un bottone in ... " quando rilasciate un bottone del mouse (dovete mettere un elif per il MOUSEBUTTONDOWN ed uno per il MOUSEBUTTONUP).
SUGGERIMENTO: Un errore molto comune è quello di sbagliare l'indentazione, facendo eseguire a Python istruzioni che non vanno eseguite o provocando errori. Ricordate la struttura a blocchi del linguaggio: ogni istruzione di controllo (if, elif, for, while) si estende per tutto il blocco indentato che la segue e "finisce" quando trova del codice allo stesso livello di indentazione.
SOLUZIONI

Un evento di tipo KEYDOWN e KEYUP ha invece degli attributi che ci dicono quale tasto è stato premuto.

unicode una string di Python. Se il tasto è un carattere stampabile i (lettera, numero, punteggiatura) la string contiene il carattere; se non è stampabile (es Invio, Alt, ecc.) essa è vuota.
key un numero intero che determina quale tasto è stato premuto. Ad ogni tasto è assegnato un numero diverso, e per ricordarli tutti facilmente pygame fornisce un elenco di costanti in manera simile a quello che accade con gli eventi (ne parliamo tra poco).
mod un altro numero intero per i "modificatori" (come Shift, Ctrl, Alt ...). Anche qui pygame fornisce un elenco di costanti per poter individuare facilmente il tasto.

Ad esempio vediamo questo programma che controlla se l'attributo unicode contiene effettivamente un carattere e nel caso lo stampa:

NUOVO PROGRAMMA: prova_key.py

import pygame
from pygame.locals import *

# inizializzazione
pygame.init()
screen = pygame.display.set_mode((800, 600))

# ciclo principale
done = False
while not done:
    for ev in pygame.event.get():
        if ev.type == QUIT:                  # chiusura della finestra
            done = True
        elif ev.type == KEYDOWN:             # e' stato premuto un tasto
            if (len(ev.unicode)) == 1:       # se la string ev.unicode contiene un carattere ...
                print(ev.unicode)            # ... la stampa
            else:
                print("Carattere non stampabile")

# fine del ciclo e termine del programma
pygame.quit()

L'attributo key, come abbiamo detto, non contiene invece un carattere stampabile, ma un numero diverso per ogni possibile tasto della nostra tastiera. Come succede per gli eventi, pygame definisce nel sottomodulo locals una serie di costanti (variabili scritte tutte in lettere maiuscole) per ricordare facilmente il tasto: ad esempio K_ESCAPE è il tasto Esc, K_TAB il Tab, ecc.. L'elenco completo è qui, nel riquadro giallo: https://www.pygame.org/docs/ref/key.html. Per accorgersi se abbiamo premuto Esc scriveremo quindi if ev.key == K_ESCAPE: ecc. L'attributo key ha il vantaggio, rispetto all'unicode, di poter rappresentare tutti i tasti della nostra tastiera. Provate a modificare il programma in questo modo:


.   .   .
        elif ev.type == KEYDOWN:             # e' stato premuto un tasto
            if ev.key == K_ESCAPE:           
                print("Hai premuto ESC")
            elif ev.key == K_TAB:
                print("Hai premuto TAB")

# fine del ciclo e termine del programma
pygame.quit()

Se però provate a stampare direttamente l'attributo ev.key con print(ev.key) vedrete che IDLE stamperà dei numeri per voi senza senso.

USIAMO GLI EVENTI CON LE Surface E I Rect

ESERCIZIO 7.3: Riprendete i programmi prova_surface.py e prova_blit.py (li abbiamo sviluppati nella Lezione 4) e sostituite la input() finale con il ciclo degli eventi, facendoli finire come quelli mostrati.
SUGGERIMENTO: Basta sostituire le righe da #9 a #15 del nostro primo esempio (qui) al posto dell'istruzione input() dei vecchi programmi. In questo modo, dopo aver disegnato sullo schermo, il programma entrerà nel ciclo principale e si metterà in attesa della nostra chiusura.
SOLUZIONI

Riprendiamo anche il programma due_quadrati.py (se avete svolto gli esercizi della lezione precedente i quadrati potrebbero essere posizionati diversamente o avere altre dimensioni: per il momento basta che non siano sovrapposti). Nel ciclo degli eventi verificheremo, per mezzo del metodo collidepoint() dell'oggetto Rect e dell'attributo pos, se l'utente clicca all'interno di essi. Per comodità vi riscrivo il programma completo.


import pygame
from pygame.locals import *

pygame.init()
screen = pygame.display.set_mode((800, 600))

# creiamo la Surface surf1, la coloriamo di rosso e la posizioniamo sullo schermo
surf1 = pygame.Surface((200, 200))
surf1.fill("red")
rect1 = surf1.get_rect()
rect1.topleft = (50, 50)
screen.blit(surf1, rect1)

# stessa cosa con surf2 (blu)
surf2 = pygame.Surface((100, 100))
surf2.fill("blue")
rect2 = surf2.get_rect()
rect2.topleft = rect1.topright
screen.blit(surf2, rect2)

# aggiorniamo lo schermo
pygame.display.flip()

# ciclo principale
done = False
while not done:
    for ev in pygame.event.get():
        if ev.type == QUIT:                  # chiusura della finestra
            done = True
        elif ev.type == MOUSEBUTTONDOWN:     # click del mouse
            click = ev.pos
            if rect1.collidepoint(click):
                print("Hai cliccato il rettangolo rosso")
            elif rect2.collidepoint(click):
                print("Hai cliccato il rettangolo blu")
            else:
                print ("Hai cliccato fuori dalle superfici")

# fine del ciclo e termine del programma
pygame.quit()

Se avete ripreso il vostro vecchio programma ricordatevi di aggiungere la #2 o Python non riconoscerà i nomi degli eventi. Fino alla #25 tutto è rimasto invariato: il programma crea due Surface surf1 e surf2 ed i rispettivi Rect rect1 e rect2. Vediamo ora il ciclo principale: in #30 monitoriamo l'evento MOUSEBUTTONDOWN. In #31 ricaviamo le coordinate del punto in cui è avvenuto il click e le assegnamo alla variabile click (una tuple di due valori). In #32 e nelle righe successive usiamo il metodo collidepoint() (ricordate questo) per verificare se il punto è all'interno di uno dei due rettangoli e scriviamo un messaggio nella finestra di IDLE.

ESERCIZIO 7.4: Posizionate ora i due quadrati in modo che il più piccolo sia sopra a quello grande. Cosa succede? Perchè? Se riuscite a capire bene il significato delle if . . . elif . . . dovrebbe bastarvi una piccola modifica al programma per riportare le cose a posto.
SOLUZIONI
NUOVO PROGRAMMA: click_quadrati.py

Ecco un altro grazioso effetto grafico nel quale uniamo le nostre conoscenze sulle Surface (blit(), fill() ecc.) alla gestione degli eventi. Se ne avete bisogno ripassate la Lezione 4.


import pygame, random
from pygame.locals import *

pygame.init()
screen = pygame.display.set_mode((800, 600))

# creiamo la Surface surf
surf = pygame.Surface((100, 100))

# ciclo principale
done = False
while not done:
    for ev in pygame.event.get():
        if ev.type == QUIT:                  # chiusura della finestra
            done = True
        elif ev.type == MOUSEBUTTONDOWN:     # click del mouse
            r = random.randrange(256)
            g = random.randrange(256)
            b = random.randrange(256)
            surf.fill((r, g, b))
            screen.blit(surf, ev.pos)
            pygame.display.flip()

# fine del ciclo e termine del programma
pygame.quit()

Fate partire il programma e provate a cliccare sulla finestra di pygame: ad ogni click verrà disegnato un quadrato di un colore diverso! Vediamo come abbiamo fatto.

Notate che nella riga #1 abbiamo importato anche il modulo random, nel quale è definita la funzione randrange() che serve a creare dei numeri casuali; nella #8 creiamo una Surface surf 100x100. Vediamo ora il ciclo principale, nel quale, alla riga #16, monitoriamo l'evento MOUSEBUTTONDOWN: nelle righe dalla #17 alla #19 creiamo tre numeri casuali (da 0 a 255) e li usiamo nella #20 per colorare surf con il colore indicato dalla tripla (r, g, b). A questo punto, nella #21, disegniamo surf sulla finestra principale a partire dalla posizione del cursore (la tuple ev.pos) e nella successiva aggiorniamo lo schermo con la flip().

Possiamo infine realizzare l'effetto grafico di cui avevamo parlato all'inizio della lezione scorsa: un quadrato che cambia colore quando il mouse vi passa sopra.

NUOVO PROGRAMMA: evidenzia.py
ESERCIZIO 7.5: Realizzate il programma seguendo questi passi:
  1. cominciate come nel programma due_quadrati.py: create una Surface surf, coloratela di rosso, ricavate da essa il Rect rect e posizionate la Surface sullo schermo usando il Rect;
  2. prima di entrare nel ciclo aggiornate lo schermo con la flip();
  3. nel ciclo principale dovete monitorare l'evento MOUSEMOTION;
  4. quando accade controllate se la pos dell'evento è dentro il quadrato rosso;
  5. se lo è colorate il quadrato con un rosso più chiaro (ad esempio (255, 100, 100));
  6. altrimenti coloratelo con il colore iniziale;
  7. dopo che avete colorato il quadrato, dovete nuovamente disegnarlo ed aggiornare lo schermo.
SOLUZIONI

Nei suggerimenti che vi ho dato ho privilegiato la semplicità del programma a scapito dell'efficienza: infatti il programma ricolora il quadrato (di un colore o dell'altro) ogni volta che il mouse si muove, facendo molto lavoro inutile e sprecando potenza di calcolo. Sarebbe molto meglio se esso "ricordasse" di che colore è il quadrato, in modo da non ricolorarlo se è già del colore desiderato. Vi lascio un ultimo esercizio, avvertendovi che esso richiede già una certa abilità nella programmazione.

ESERCIZIO 7.6: Modificate il programma così:
  1. All'inizio il quadrato è rosso scuro: create (prima del ciclo degli eventi) una variabile light ponendola uguale a False
  2. Nel ciclo degli eventi:
    • Dovete colorare con il colore più chiaro se il mouse è dentro il quadrato e light è uguale a False (cioè il quadrato è scuro). In questo caso usate il colore chiaro e ponete light = True per ricordare che ora il quadrato è chiaro
    • Dovete colorare con il colore più scuro se il mouse è fuori dal quadrato e light è uguale a True (cioè il quadrato è chiaro): agite in maniera simile al punto precedente
    • A questo punto dovete porvi il problema di dove inserire le istruzioni che disegnano il quadrato e aggiornano lo schermo: dovete farle eseguire solo se avete ricolorato il quadrato ...
SOLUZIONI

Fine della lezione