Per ora siamo riusciti a creare una finestra e a disegnare qualcosa su di essa. Per cominciare ad interagire con il nostro programma è necessario conoscere meglio il funzionamento di un'interfaccia grafica.
Windows, Mac, Linux o il nostro smartphone usano delle interfacce grafiche (in inglese GUI: Graphic User Interface) tra l'utente ed il computer: in un'applicazione moderna quasi tutta l'interazione tra l'uomo ed il computer avviene muovendo il mouse, cliccando su immagini, bottoni, menu, ecc.
Come funziona di fatto tutto questo? Bene, mentre noi lavoriamo al computer il sistema operativo controlla continuamente tutto quello che avviene: la tastiera, il cursore del mouse, i pulsanti del mouse, ecc. Ogni volta che accade qualcosa (muoviamo il mouse, facciamo clic, premiamo un tasto della tastiera, apriamo una nuova finestra...) esso genera un evento di sistema, cioè un messaggio che contiene informazioni su quello che è accaduto, e lo invia a tutte le applicazioni aperte in quel momento. Ogni applicazione che lo riceve può analizzarlo, capire che cosa è successo e comportarsi di conseguenza (ad esempio un clic sul bottone "Chiudi" provocherà la chiusura di una finestra, ecc). Poichè gli eventi possono susseguirsi anche in maniera molto rapida esiste una coda degli eventi per ogni applicazione, alla quale vengono spediti gli eventi in ordine di arrivo (come le persone all'ufficio postale); le applicazioni li esaminano uno dopo l'altro decidendo cosa fare (possono anche ignorare quelli che non interessano).
Ad esempio, un effetto ormai comune in tutte le applicazioni è l'evidenziazione delle varie icone quando il mouse vi passa sopra (lo faremo come esercizio nel prossimo capitolo). Ogni volta che il mouse si muove, il sistema operativo genera un evento che contiene le coordinate del puntatore e lo invia alle applicazioni aperte in quel momento. Il programma lo riceve, fa qualche calcolo e rileva se il puntatore è entrato nel rettangolo dove è disegnata l'icona; se la risposta è sì cambia l'immagine dell'icona con quella dell'icona evidenziata. Allo stesso modo, quando il puntatore esce dal rettangolo, cambia nuovamente l'immagine in quella normale.
Proprio trattando gli eventi possiamo capire l'utilità della programmazione ad oggetti: quando il nostro programma riceve un evento dal sistema operativo deve innanzitutto capire che cosa è successo (clic del mouse, tastiera, ecc.) ed in base a ciò avrà bisogno poi di altre informazioni (per un clic bisognerà sapere in che punto è avvenuto, per la tastiera quale tasto è stato premuto, etc.). In pygame esiste un oggetto apposito, l'oggetto Event, nel quale tutte queste informazioni sono "impacchettate" sotto forma di attributi.
L'attributo più importante di un Event è il type
, che contiene il tipo dell'evento; i vari tipi sono identificati da
delle costanti di pygame (cioè delle variabili predefinite in pygame stesso, scritte tutte in caratteri maiuscoli)
con nomi che ricordano facilmente l'evento. Ad esempio:
MOUSEMOTION | il mouse è stato mosso |
MOUSEBUTTONDOWN | clic del mouse (è stato premuto un bottone) |
KEYDOWN | un tasto della tastiera è stato premuto |
KEYUP | un tasto della tastiera è stato rilasciato |
QUIT | la finesta è stata chiusa (clic sull'icona della crocetta in alto a destra) |
Quindi, ad esempio, se ev
è una variabile di tipo Event, per conoscere il tipo di evento dovremo esaminare l'attributo
ev.type
; scrivendo if ev.type == MOUSEBUTTONDOWN:
potremo capire se l'utente ha cliccato sulla nostra
finestra. Se non capite che tipo di dato sono le variabili scritte tutte in maiuscolo potete consultare il mio approfondimento
qui.
Vediamo infine come fare per ricevere gli eventi dal sistema operativo. Il sottomodulo event
di pygame
contiene alcune funzioni per gestire gli eventi che il sistema operativo trasmette al nostro programma. Abbiamo detto che al
nostro programma è assegnata una coda di eventi (per noi invisibile perchè gestita da Windows): la funzione
pygame.event.get()
prende gli eventi dalla coda, li trasforma in oggetti Event di pygame e ci restituisce una lista di
Python che contiene i nostri Event in ordine cronologico. Ecco un esempio molto semplice dell'uso di questa funzione:
import pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
# riceve tutti gli eventi arrivati dal sistema
eventlist = pygame.event.get()
for ev in eventlist:
print("Ho ricevuto l'evento", ev)
# fine del ciclo e termine del programma
input("Premi INVIO per terminare il programma")
pygame.quit()
Nella riga #7 chiamiamo la get()
: il suo risultato è la lista degli oggetti Event arrivati
da Windows e viene assegnato alla variabile eventlist
; nella #8 facciamo un ciclo su
di essa stampando sullo schermo ogni evento.
Se lanciamo il programma vedremo nella finestra di IDLE alcune stringhe piuttosto lunghe e non facilmente comprensibili: quando chiediamo a Python di "stampare" un Event il risultato è una lunga stringa che contiene i valori degli attributi. Proprio all'inizio possiamo riconoscere il tipo dell'evento ricevuto (VideoExpose: l'apertura della finestra, etc.).
L'esempio precedente si limita a prelevare da Windows i primi quattro-cinque eventi, dopodichè il ciclo termina e non potremo
fare altro che chiudere la finestra. Inserendo a sua volta la get()
in un ciclo possiamo fare in modo che il programma
riceva continuamente gli eventi da Windows. Modificate il programma così:
import pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
while 1:
# riceve tutti gli eventi arrivati dal sistema
eventlist = pygame.event.get()
for ev in eventlist:
print("Ho ricevuto l'evento", ev)
# fine del ciclo e termine del programma
input("Premi INVIO per terminare il programma")
pygame.quit()
Abbiamo inserito (in #6) il nostro ciclo all'interno di un altro ciclo while
: ora ogni volta
che gli eventi sono finiti la get()
viene chiamata nuovamente (la funzione cancella i vecchi eventi nella coda di
Windows, cosicchè alla chiamata successiva troverà solo i nuovi eventi arrivati nel frattempo). Se lanciate ora il programma potete
divertirvi a muovere il mouse (dentro la finestra di pygame), fare click, premere tasti della tastiera, e vedrete che il vostro
programma si "accorgerà" di tutto quello che fate!
Ma c'è ancora un problema: così com'è il nostro programma non terminerà mai! Infatti, come saprete,
il ciclo while 1:
è un ciclo senza fine, ed in realtà non arriveremo mai alle ultime righe del programma (potreste
anche cancellarle). Vedremo nel prossimo paragrafo la soluzione generalmente adottata in tutti i programmi per pygame, che ci
permetterà anche di chiudere il programma direttamente cliccando la casella con la crocetta sulla finestra.
E' l'evento che viene generato quando si clicca sulla crocetta in alto a destra di una finestra, che dovrebbe provocare la sua chiusura. Ora possiamo fare in modo che esso provochi l'uscita dal programma. Modificate il programma così:
import pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
done = False
# si esce dal ciclo quando done diventa True
while not done:
# riceve tutti gli eventi arrivati dal sistema
eventlist = pygame.event.get()
for ev in eventlist:
print("Ho ricevuto l'evento", ev)
# se l'evento e' la fine del programma pone done = True
if ev.type == pygame.QUIT:
done = True
# fine del ciclo e termine del programma
pygame.quit()
Per gestire l'uscita dal ciclo usiamo la variabile done
: nella riga #6 la poniamo uguale a
False
e nella #8 facciamo ripetere il ciclo while
finchè rimarrà tale. All'interno
del ciclo abbiamo aggiunto alcune righe: nella #14 controlliamo se l'evento è un QUIT
, e, se
questo è vero, poniamo done = True
provocando l'uscita dal ciclo all'iterazione successiva. Ricordate
che in Python un ciclo finisce quando troviamo un'istruzione allo stesso livello di indentazione, quindi il nostro ciclo comprende
le righe dalla #8 alla #15.
In questo modo abbiamo finalmente eliminato l'istruzione input("Premi INVIO per terminare il programma")
che ci costringeva
ad usare la finestra di IDLE: ora non è più necessaria, perchè sarà pygame stesso ad aspettare, ripetendo il ciclo principale finchè
non riceve l'evento QUIT
.
Quella vista nell'ultimo esempio è la struttura tipica di un programma scritto per un'interfaccia grafica: c'è una prima parte di inizializzazione in cui si definiscono tutte le variabili necessarie, dopodichè si entra in un ciclo (che nel seguito chiameremo ciclo principale); in questo ciclo il programma aspetta gli eventi dal sistema operativo, li esamina e poi esegue i compiti richiesti da ogni evento (per il momento abbiamo solo stampato la descrizione degli eventi ricevuti). Il ciclo si interrompe di solito solo quando l'utente chiude la finestra principale, provocando l'uscita dal programma.
Ecco in particolare la nostra implementazione in Python e pygame.
pygame.init()
, apriremo la finestra
principale screen
e definiremo le principali variabili che ci serviranno.done
, ponendola uguale
a False, ed useremo subito dopo l'istruzione while not done:
che provocherà la ripetizione del
blocco successivo finchè done
rimarrà False. All'interno del ciclo chiameremo la
pygame.event.get()
ed esamineremo uno dopo l'altro tutti gli eventi arrivati dal sistema, decidendo che cosa fare in
conseguenza. Per uscire dal ciclo principale basterà porre done = True
in qualche istruzione al suo interno.pygame.quit()
che chiude
correttamente tutti i moduli di pygame ed esce senza provocare errori.Facciamo ancora qualche aggiustamento in modo da ottenere uno "scheletro" di base:
import pygame
from pygame.locals import *
# inizializzazione
pygame.init()
screen = pygame.display.set_mode((800, 600))
# ciclo principale (si esce quando done diventa True)
done = False
while not done:
# riceve tutti gli eventi arrivati dal sistema
for ev in pygame.event.get():
# se l'evento e' la fine del programma pone done = True
if ev.type == QUIT:
done = True
# fine del ciclo e termine del programma
pygame.quit()
Commentiamo i cambiamenti:
import
: questa serve ad importare tutti i nomi degli
eventi (MOUSEMOTION, KEYUP, QUIT ...), definiti nel sottomodulo locals
, senza bisogno di anteporre
pygame
. Notate che vi avevo raccomandato di non usare questo tipo di sintassi per la
import
, ma i programmatori in Python sono soliti fare un'eccezione in casi come questo, per evitare di dover
scrivere nomi troppo lunghi. Quindi ci adeguiamo alla prassi comune: da ora in poi i nostri programmi cominceranno con queste due
istruzioni import
.eventlist
): mediante il ciclo for
facciamo variare la variabile ev
su tutti gli
eventi ottenuti dalla get()
. Il ciclo principale contiene le righe dalla #12 alla
#15 (lo riconoscete per l'indentazione).if
, leggendo l'attributo type
.
Per il momento l'unico evento che ci interessa è il QUIT
, che provoca uscita dal programma: tutti gli altri saranno
ignorati (provate).QUIT
anzichè
pygame.QUIT
.Salvate quest'ultimo esempio come prova_eventi.py: lo riprenderemo più volte nel seguito per espanderlo con nuove funzionalità.
Ricaricate ora il nostro esempio, salvatelo come prova_eventi1.py ed aggiungete queste righe:
. . .
for ev in pygame.event.get():
# se l'evento e' la fine del programma pone done = True
if ev.type == QUIT:
done = True
# se l'evento e' il click del mouse lo scrive su IDLE
elif ev.type == MOUSEBUTTONDOWN:
print("Hai fatto click")
. . .
Ora il programma reagisce anche all'evento MOUSEBUTTONDOWN, avvisandoci quando clicchiamo sulla sua finestra con un messaggio sulla finestra di IDLE. Nei prossimi esercizi faremo altre modifiche.
elif
nel ciclo principale, attenti all'indentazione.random
, che contiene le funzioni per generare i numeri
casuali (ricordate che la funzione randrange(n)
restituisce un numero casuale tra 0 e n - 1). Nel ciclo principale
dovete monitorare l'evento MOUSEBUTTONDOWN; quando accade dovete:
r, g, b
assegnando loro un numero casuale tra 0 e 255;screen
usando il metodo fill()
(mettendo come parametro la tripla dei
colori (r, g, b)
);flip()
.Fine della lezione