17: IL TESTO IN PYGAME

SCRIVERE SULLA FINESTRA DI PYGAME

Abbiamo già visto che per scrivere qualcosa sulla finestra di pygame non possiamo usare la print() di Python, che continua a scrivere sulla finestra di IDLE. Essendo pygame una libreria orientata alla grafica, anche il testo viene trattato come se fosse un'immagine: in pratica quando vogliamo scrivere del testo dovremo "disegnare" la nostra scritta su una Surface.

Per fare questo esiste un oggetto apposito, l'oggetto Font. La prima cosa da fare è quindi creare una variabile di tipo Font: anche in questo caso, come per le immagini e i suoni, il Font deve essere "caricato" a partire da un file che ne contiene i dati codificati. Abbiamo due possibilità:

Entrambe le funzioni si trovano nel sottomodulo font. Facciamo qualche esempio:


font1 = pygame.font.Font(None, 24)
font2 = pygame.font.SysFont("Times New Roman", 12)
font3 = pygame.font.SysFont("Arial", 32, bold=True)
font4 = pygame.font.SysFont("Verdana", 12, italic=True)

La #1 carica il font di default di pygame, di grandezza 24 pt, la #2 carica il Times New Roman 12 pt (attenti alle maiuscole e minuscole!), la #3 il font Arial 32 pt neretto e la #4 il Verdana 12 pt corsivo. Notate nelle ultime due chiamate i parametri opzionali chiamati con il loro nome (keyword arguments) in modo da poter scrivere solo i parametri che ci interessano senza tener conto della loro posizione nella funzione.

Una volta caricato un font, per scrivere si usa il metodo render(), che è così definto:

render(text, antialias, color, background=None) -> Surface

Il metodo accetta quindi quattro parametri:

Parametro Tipo di dato Significato
text string Il testo da scrivere. Attenzione, la string deve essere unica e non possiamo quindi usarne molte come facciamo nella print(). Se dobbiamo scrivere molte string di seguito dobbiamo concatenarle in una sola con l'addizione tra string
antialias booleano Se è True viene usato l'antialiasing: questo renderà il testo più leggibile ma impegnerà più risorse di calcolo, perchè userà anche colori intermedi tra lo sfondo e il testo. A meno che non dobbiate fare moltissime scritte conviene mettere True per avere un effetto migliore
color tuple (3 valori) o string Il colore del testo (una tripla di interi in formato RGB o il suo nome)
background tuple (3 valori) o string Il colore dello sfondo (opzionale): se lo omettiamo lo sfondo risulterà trasparente

Il metodo restituisce una Surface delle opportune dimensioni che contiene il testo; potremo poi trattarla come sappiamo già fare. Anche qui è meglio vedere subito un esempio:

NUOVO PROGRAMMA: testo1.py

import pygame
from pygame.locals import *

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

# carichiamo il font e lo assegniamo alla variabile fnt
fnt = pygame.font.SysFont("Times New Roman", 24)

# scriviamo "Hello world!" e otteniamo la Surface surf_text
surf_text = fnt.render("Hello world!", True, "yellow")

# disegniamo surf_text sullo schermo
screen.blit(surf_text, (300, 300))
pygame.display.flip()

# ciclo principale
done = False
while not done:
    for ev in pygame.event.get():
        if ev.type == QUIT:
            done = True
pygame.quit()

Questo semplice programmino scrive "Hello world!" in giallo su una finestra blu. Nella riga #9 abbiamo caricato il font e lo abbiamo assegnato alla varibile fnt, nella #12 usiamo la render(), che ci restituisce la Surface surf_text con la scritta. Nella #15 disegniamo la Surface nella finestra principale. Nel ciclo degli eventi non c'è bisogno di fare nessun aggiornamento perchè l'immagine è fissa.

ESERCIZIO 17.1: Sperimentate sul programma precedente, variando il tipo di font, la sua grandezza, il colore e la posizione della scritta. Usate anche il font di default di pygame, il corsivo e il neretto, ed indicate un secondo colore per lo sfondo.
ESERCIZIO 17.2: Centrate la scritta sullo schermo indipendentemente dalle dimensioni del font. Dovete ottenere dalla Surface il Rect corrispondente, porre il suo centro uguale al centro dello schermo (la Surface screen) ed usare il Rect per posizionare surf_text nella blit() (vedi qui).
ESERCIZIO 17.3: Riprendete il programma quadrato_mobile.py (vedi qui), salvatelo come scritta_mobile.py e fate muovere (e rimbalzare) la scritta "Ciao!".
SOLUZIONI

Se vi servono più scritte in diversi colori potete usare lo stesso Font, cambiando i parametri della render(). Se invece vi servono scritte di grandezze diverse avete due possibilità: o creare diversi oggetti Font con grandezze diverse o usare la funzione pygame.transform.scale() (che abbiamo già visto qui) per ingrandire o rimpicciolire la Surface. Il primo metodo è più preciso perchè, come già ho detto, scalare una Surface può spesso portare a delle distorsioni nell'immagine risultante.

ESERCIZIO 17.4: Scrivete "Hello world!" nel font Times New Roman, 96pt in giallo, e sotto "All is OK" in Arial, 48pt, bianco (allineate le due righe usando i Rect, usate due oggetti Font diversi).
SOLUZIONI

Se invece vi serve lo stesso font, prima normale e poi corsivo (o neretto), potete usare alcuni metodi dell'oggetto Font, aggiunti a partire dalla versione 2 di pygame:

Metodo Esempio Significato
set_underline(bool) fnt.set_underline(True) Abilita o disabilita il testo sottolineato
get_underline() fnt.get_underline() Restituisce True se la prossima render() scriverà il testo sottolineato
set_bold(bool) fnt.set_bold(False) Abilita o disabilita il testo in neretto
get_bold() fnt.get_bold() Restituisce True se la prossima render() scriverà il testo in neretto
set_italic(bool) fnt.set_italic(True) Abilita o disabilita il testo in corsivo
get_italic() fnt.get_italic() Restituisce True se la prossima render() scriverà il testo in corsivo

La documentazione ufficiale avverte però che queste funzioni usano delle trasformazioni di pygame sui caratteri, e quindi non danno gli stessi risultati del font originale corsivo o neretto.

Modifichiamo infine il programma testo1.py per ripassare come vanno concatenate le string e i numeri: per immettere il nostro nome e la nostra età useremo ancora la finestra di IDLE (quando lanciamo il programma dobbiamo cliccare sulla finestra di IDLE e scrivere quello che ci chiede).


import pygame
from pygame.locals import *

name = input("Come ti chiami? ")
age = input("Quanti anni hai? ")

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

# carichiamo il font e lo assegniamo alla variabile fnt
fnt = pygame.font.SysFont("Times New Roman", 12)

# scriviamo la stringa s
s = name + " ha " + age + " anni"
surf_text = fnt.render(s, True, "yellow")
.   .   .

Notate nella #15 l'uso dell'addizione tra string e degli spazi (quando concateniamo le string gli spazi non vengono aggiunti automaticamente come fa la print()). Notate anche che nella #5 abbiamo immesso direttamente l'età come string, e non come numero (altrimenti avremmo dovuto scrivere age = int(input("Quanti anni hai? ")) e poi riconvertire il numero in string con str(age)).

USIAMO LA TASTIERA DEL COMPUTER

Vediamo adesso come scrivere usando direttamente la tastiera del computer. E' chiaro che in questo caso il testo sarà dinamico, cambiando a seconda dei tasti che premiamo. Quindi dovremo monitorare il solito evento KEYDOWN nel ciclo degli eventi: ogni volta che premiamo un tasto dovremo riconoscere il tasto premuto, trasformare la lettera in una Surface con un oggetto Font e disegnare la Surface sullo schermo.

Dobbiamo ripassare bene (vedi qui) l'evento KEYDOWN. Ricordiamo che esso ha due distinti attributi che permettono di identificare il tasto: l'attributo unicode è una string che contiene la lettera premuta (o è vuota se il tasto non è stampabile), mentre l'attributo key è un codice numerico che identifica univocamente ogni tasto, anche quelli non stampabili. Ecco un primo esempio:

NUOVO PROGRAMMA: testo2.py

import pygame
from pygame.locals import *

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

# carichiamo il font e lo assegniamo alla variabile fnt
fnt = pygame.font.SysFont("Times New Roman", 432)

# inizializziamo una string vuota
s = ""

# ciclo principale
done = False
while not done:
    # sottociclo degli eventi
    for ev in pygame.event.get():
        if ev.type == QUIT:
            done = True
        elif ev.type == KEYDOWN:
            s = ev.unicode
           
    # scriviamo s e otteniamo la Surface surf_text   
    surf_text = fnt.render(s, True, "yellow")
    
    # disegniamo surf_text sullo schermo
    screen.fill((0, 0, 160))                       
    screen.blit(surf_text, (100, 100))
    pygame.display.flip()   
	
    clk.tick(30)
	
pygame.quit()

Analizziamo il programma:

ESERCIZIO 17.5: Anche questa volta la lettera stampata non è centrata. Stampatela al centro dello schermo come abbiamo visto nell'Esercizio 17.2.
SOLUZIONI

Se, anzichè stampare solo l'ultimo carattere, voleste stampare tutti i caratteri premuti, dovete fare solo una piccola modifica alla riga #22: anzichè sostituire il vecchio valore di s con il nuovo carattere in ev.unicode dovete sommarli (potete usare l'operatore +=).

ESERCIZIO 17.6: Modificate il programma come spiegato. Per stampare string più lunghe modificate la dimensione del carattere portandola a 120 pt.
SOLUZIONI

C'è ancora qualche problema, però. Il nostro programma intercetta anche i tasti non stampabili, come Esc, Ctrl, Invio; alcuni di essi, come ho detto, producono una string unicode vuota, mentre altri (che erano usati nelle telescriventi agli albori dell'informatica) sono stampati come un quadratino. Vorremmo probabilmente che il programma ignorasse i caratteri non stampabilli.

Per migliorare il nostro programma dobbiamo chiedere aiuto non a pygame, ma a Python stesso: l'oggetto <str> di Python (cioè la nostra comune string) ha dei metodi che ci aiutano a riconoscere i caratteri presenti nella string stessa. Eccone alcuni:

.isdigit() Restituisce True se la string è composta unicamente da numeri
.isalpha() Restituisce True se la string è composta unicamente da lettere
.isupper() Restituisce True se la string è composta unicamente da lettere maiuscole
.islower() Restituisce True se la string è composta unicamente da lettere minuscole
.isprintable() Restituisce True se la string è composta unicamente da caratteri stampabili

Ripeto che queste funzioni sono metodi dell'oggetto str (string), quindi vanno chiamati usando la sintassi con il punto. Ad esempio, se ev è un evento KEYDOWN

if ev.unicode.isprintable():

controlla se la string ev.unicode contiene un carattere stampabile. Notate di nuovo la "concatenazione" dell'operatore punto: l'istruzione applica il metodo isprintable() all'attributo unicode dell'oggetto ev.

ESERCIZIO 17.7: Modificate il programma in modo che ignori i caratteri non stampabili; dovrete modificate la riga dove verificate l'evento KEYDOWN, aggiungendo con un and anche la condizione che ev.unicode sia stampabile.
ESERCIZIO 17.8: Usando le altre funzioni della tabella sopra fate in modo che il programma permetta di inserire solo numeri, o solo lettere, o solo lettere maiuscole.
SOLUZIONI

IMPLEMENTIAMO IL BACKSPACE

Il nostro programma precedente potrebbe essere usato in un videogioco, ad esempio per far inserire al giocatore il proprio nome. Manca però ancora una cosa, cioè qualsiasi possibilità di correggere la scritta se dovessimo sbagliare. Cerchiamo di implementre quindi il BackSpace, cioè la possibilità di cancellare l'ultima lettera inserita.

Per controllare se l'utente ha premuto BackSpace non possiamo più utilizzare l'attributo unicode ma dobbiamo usare l'attributo key, che permette di identificare anche i tasti di controllo.

Ricordo che qui http://www.pygame.org/docs/ref/key.html potete trovare l'elenco di tutti i codici dei tasti. A noi interessa il K_BACKSPACE: se esso viene premuto dobbiamo cancellare l'ultimo carattere; ecco il ciclo degli eventi modificato.


.   .   .
        elif ev.type == KEYDOWN:                # e' stato premuto un tasto
            if ev.key == K_BACKSPACE:           # se e' il backspace ...
                s = s[0:-1]                     # slice notation
            elif ev.unicode.isprintable()       # altrimenti contolliamo se e' stampabile ...
                s += ev.unicode                 # ... e lo aggiungiamo alla nostra stringa
.   .   .

Nella riga #23 ho usato due sintassi che sono proprie di Python: gli indici negativi (s[-1] è l'ultimo carattere della string, s[-2] il penultimo e così via) e la slice notation (che significa "notazione a fettine": s[0:-1] è la string s dal primo carattere al penultimo). Se non conoscete bene queste sintassi potete consultare la Lezione 1 del mio tutorial intermedio. In pratica nella riga #23 prendiamo tutta la string s meno l'ultimo carattere e la riassegniamo a sè stessa (se il primo indice è zero si può anche omettere, quindi avremmo potuto anche scrivere s[:-1]).

ESERCIZIO 17.9: Provate a cambiare s[:-1] con s[1:] e vedete cosa succede. Perchè?
ESERCIZIO 17.10: Vogliamo che premendo i tasti funzione F1 - F4 la stringa venga stampata in colori diversi. Procedete così: Vi ricordo che in genere nei computer portatili i tasti funzione sono dedicati a controllare particolari funzionalità del computer. Per far riconoscere al programma un tasto funzione dovete premerlo insieme al tasto <Fn> in basso a sinistra.
SOLUZIONI

Fine della lezione