In questo sottomodulo di pygame sono raggruppate alcune funzioni che servono a manipolare le immagini e che possono essere molto utili. Tutte queste funzioni agiscono su una Surface, che compare come primo parametro, e ne restituiscono un'altra, che dovremo assegnare ad una variabile. In questo modo la Surface di partenza non viene modificata dalla funzione (a meno che non la riassegniamo a sè stessa). Vediamo le più importanti (nella tabella ho omesso alcuni parametri opzionali per semplicità, se volete approfondire ecco il link alla documentazione ufficiale del modulo https://www.pygame.org/docs/ref/transform.html):
Funzione | Significato |
---|---|
flip(Surface, xbool, ybool) |
Esegue un ribaltamento dell'immagine, sinistra-destra se xbool è True
o alto-basso se lo è ybool (anche tutti e due insieme) |
scale(Surface, (width, height)) |
Scala l'immagine in modo da portarla alle dimensioni richieste. width e height devono
essere numeri interi altrimenti provocherà un errore. |
smoothscale(Surface, (width, height)) |
Come la precedente, usa un algoritmo che da risultati più precisi ma si può usare solo con colori a 24-32 bit.
In genere conviene usare questa al posto della scale() . |
rotate(Surface, angle) |
Ruota l'immagine dell'angolo angle (in gradi). Un angolo positivo corrisponde ad una rotazione antioraria
e viceversa. Se l'angolo è di 90°, 180° o 270° restituisce una Surface con le stesse dimensioni di quella di partenza,
altrimenti esse saranno diverse (e bisognerà usare get_rect() o qualche altro metodo simile per conoscerle) |
rotozoom(Surface, angle, scale) |
Ruota e scala l'immagine contemporaneamente. scale è un numero float che determina il fattore di scala (2.0
= dimensioni raddoppiate,etc.). |
scale2x(Surface) |
Raddoppia velocemente entrambe le dimensioni dell'immagine. |
Come ho detto queste funzioni prendono una Surface come parametro e ne restituiscono un'altra trasformata, lasciando invariata la Surface originale. Se vogliamo modificare una Surface dobbiamo quindi riassegnare il risultato della funzione alla Surface stessa; ad esempio:
surf = pygame.transform.smoothscale(surf, (100, 200))
porta le dimensioni della Surface surf
a 100 x 200 pixel.
Ecco un esempio del primo caso. Le funzioni scale()
e smoothscale()
sono utili per adattare le dimensioni
di un'immagine ad una determinata base ed altezza. Per esempio possiamo usarle per inserire come sfondo un'unica grande immagine,
quando non ha le dimensioni giuste per la nostra finestra.
. . .
surf_back = pygame.image.load(os.path.join("Backgrounds", "desert_02.jpg").convert()
. . .
screen,blit(surf_back, (0, 0))
. . .
La prima va inserita all'inizio del programma, possibilmente vicino a quella che carica l'immagine del panda; la seconda deve
invece sostituire la fill()
che colora di verde lo schermo. Dovreste ormai essere in grado di trovare queste istruzioni
da soli e di capire che cosa fanno le nuove istruzioni.Provate a lanciare il programma, e ... vedrete che qualcosa non va! Infatti lo sfondo è un'immagine di 2048x1536 pixel, ed è troppo grande per la nostra finestra.
surf_back
, quest'altra istruzione che
la porta alle giuste dimensioni.
surf_back = pygame.transform.smoothscale(surf_back, screen.get_size())
(per l'uso del metodo get_size()
guardate qui)Lanciando il programma vediamo che ora lo sfondo "entra" perfettamente nella finestra, con una perdita di qualità accettabile. Possiamo quindi usare un'istruzione simile per portare un'immagine alle dimensioni volute, tenendo sempre conto che in caso di variazioni molto grandi o molto piccole nelle dimensioni rischiamo di degradare eccessivamente la nostra immagine originale.
Vediamo ora un esempio più complicato, in cui faremo ruotare e cambiare dimensioni al nostro panda. In questo esempio useremo la Surface originale come base per le trasformazioni, ed un'altra Surface per le immagini trasformate.
Potete copiare il programma qui sotto oppure (ve lo consiglio) partire ancora dal programma panda.py, salvarlo con il nuovo nome e modificarlo. Lanciando il programma il panda, oltre a muoversi, presenterà anche un movimento rotatorio e varierà la sua dimensione.
import pygame
from pygame.locals import *
from os.path import join
pygame.init()
screen = pygame.display.set_mode((800, 600))
clk = pygame.time.Clock()
# carica le immagini
surf_image = pygame.image.load(join("Sprites", "panda.png")).convert_alpha()
surf_back = pygame.image.load(join("Backgrounds", "desert_02.jpg")).convert()
surf_back = pygame.transform.smoothscale(surf_back, screen.get_size())
# altre variabili
angle = 0.0
angle_inc = 5
scale = 1.0
scale_inc = 0.1
n = 0
# posizione e velocita' del panda
center_panda = [100, 100]
vel_panda = [2, 2]
# ciclo principale
done = False
while not done:
# sottociclo degli eventi
for ev in pygame.event.get():
if ev.type == QUIT:
done = True
# trasformazione dell'immagine
angle = (angle + angle_inc) % 360
scale += scale_inc
n = (n + 1) % 4
if n == 0:
scale_inc *= -1
surf_panda = pygame.transform.rotozoom(surf_image, angle, scale)
rect_panda = surf_panda.get_rect()
rect_panda.center = center_panda
# movimento
if rect_panda.left < 0 or rect_panda.right > screen.get_width():
vel_panda[0] *= -1
if rect_panda.top < 0 or rect_panda.bottom > screen.get_height():
vel_panda[1] *= -1
center_panda[0] += vel_panda[0]
center_panda[1] += vel_panda[1]
# aggiornamento dello schermo
screen.blit(surf_back, (0, 0))
screen.blit(surf_panda, rect_panda)
pygame.display.flip()
clk.tick(30)
pygame.quit()
Ci sono, rispetto al vecchio programma, parecchie differenze che è meglio analizzare in dettaglio:
surf_image
): questo perchè la "vera" Surface che rappresenta il panda sarà ottenuta all'interno
del ciclo principale, applicando delle trasformazioni a questa Surface che rimane come "base" di partenza (come ho detto, se
applicassimo successivamente delle trasformazioni ad una Surface già trasformata la qualità degraderebbe rapidamente).angle
è l'angolo di rotazione, angle_inc
l'incremento dell'angolo (ad ogni ciclo il panda ruoterà di
5 gradi in senso antiorario), scale
il fattore di scala, scale_inc
l'incremento del fattore di scala
(ad ogni ciclo più o meno il 10%) e n
un contatore che ci servirà per ingrandire e rimpicciolire l'immagine.topleft
per modificare la posizione del panda, bensì il centro del Rect, che faremo muovere
come abbiamo già visto nelle lezioni precedenti. La variabile center_panda
rappresenterà quindi
il centro (x, y) della nostra immagine. Notate che ho usato una lista di due elementi per poterla aggiornare ad ogni
ciclo.angle_inc
(5 gradi per ogni ciclo), e nella
#35 sommiamo analogamente il fattore di scala con l'incremento. Le righe successive servono per ottenere
un andamento oscillante: l'assegnazione nella riga 36 fa sì che il contatore n
assuma in
sequenza i valori 0, 1, 2, 3, 0 ,1 ,2 ,3 ... e quindi (#37 e #38) ogni quattro
cicli l'incremento scale_inc
passerà da positivo a negativo e viceversa. In questo modo l'immagine
si ingrandirà e rimpicciolirà alternativamente ogni quattro cicli.rotozoom()
per ottenere, a partire dall'immagine originale, una nuova Surface surf_panda
, nella #41
otteniamo il Rect dalla nuova Surface e nella #42 lo posizioniamo in modo che il suo attributo
center
coincida con la nostra posizione center_panda
.center_panda
).C'è però un problema, che potete facilmente notare se lasciate l'animazione attiva per qualche tempo: a volte il panda, anzichè rimbalzare, sembra "strisciare" contro una parete rimanendovi attaccato per un certo tempo prima di staccarsene.
Capite il motivo di questo strano comportamento? Se ci pensate le cause sono semplici da individuare: dato che il Rect che contiene il panda varia le sue dimensioni ad ogni ciclo, può succedere che il programma si accorga che stia urtando una parete e cambi il verso della sua velocità. Nel ciclo successivo, però, il Rect potrebbe essere aumentato di dimensioni e quindi urtare di nuovo la parete anche se il suo centro si è allontanato. Allora il programma invertirebbe ancora la velocità "spingendolo" di nuovo contro la parete e così via. Fortunatamente non è difficile correggere questo errore modificando le righe che controllano il rimbalzo. Come fareste? Potete pensarci un po' e poi provate il prossimo esercizio:
if
che controllano il rimbalzo:
screen
e la
componente orizzontale è positivascreen
e la
componente verticale è positivaDovete utilizzare quindi due if
piuttosto complessi, dopodichè il problema dovrebbe essere risolto.
Per finire vi propongo alcune funzioni scritte da me che potrebbero essere utili, soprattutto nella fase iniziale o finale di un
gioco, per ottenere degli effetti sulle immagini. Dato che contengono dei cicli temporizzati (con la wait()
)
non sono adatte ad essere inserite nel ciclo principale, perchè lo rallenterebbero. La prima funzione,
flash_surface()
, produce un lampeggiamento di un'immagine accendendola e spegnandola più volte. I parametri sono:
Parametro | Tipo di dato | Significato |
---|---|---|
surf_little |
Surface | La nostra immagine, che faremo lampeggiare |
center |
dupla di coordinate (x, y) | Il centro del Rect nel quale posizioneremo l'immagine |
surf_big |
Surface | La Surface di sfondo sulla quale disegneremo surf_little (tipicamente sarà la nostra screen ). |
def flash_surface(surf_little, center, surf_big):
rect = surf_little.get_rect()
rect.center = center # centra surf_little
surf_big_old = surf_big.copy() # fa una copia dello sfondo originale
for i in range(10): # ripete 10 volte (i = 0 ... 9)
surf_big.blit(surf_big_old, (0, 0)) # disegna lo sfondo
pygame.display.flip()
pygame.time.wait(80)
surf_big.blit(surf_little, rect) # disegna l'immagine
pygame.display.flip()
pygame.time.wait(80)
La funzione rotate_surface()
fa fare tre giri alla Surface surf_little
. I parametri hanno lo stesso
significato di quelli della funzione precedente (l'immagine ruoterà attorno al punto center
).
def rotate_surface(surf_little, center, surf_big):
surf_big_old = surf_big.copy()
for i in range(55): # ripete 55 volte (i = 0 ... 54)
surf = pygame.transform.rotate(surf_little, i * 20)
# ruota di 20 gradi (20 x 54 = tre giri)
rect = surf.get_rect() # trova il Rect e lo posiziona
rect.center = center
surf_big.blit(surf_big_old, (0, 0))
surf_big.blit(surf, rect)
pygame.display.flip()
pygame.time.wait(20)
Infine la funzione resize_surface()
provoca un effetto di ingrandimento, partendo dalla Surface surf_little
rimpicciolita di 10 volte ed ingrandendola fino alle sue dimensioni originali.
def resize_surface(surf_little, center, surf_big):
w_min = surf_little.get_width() / 10 # larghezza iniziale (10 volte piu' piccola)
h_min = surf_little.get_height() / 10 # altezza iniziale
w_step = (surf_little.get_width() - w_min) / 20 # incrementi ad ogni passo (1/20 della differenza)
h_step = (surf_little.get_height() - h_min) / 20
surf_big_old = surf_big.copy() # fa una copia dello sfondo originale
for i in range(21): # ripete 21 volte (i = 0 ... 20)
w = int(w_min + i * w_step) # trova la nuova larghezza
h = int(h_min + i * h_step) # trova la nuova altezza
surf = pygame.transform.smoothscale(surf_little, (w, h))
# scala l'immagine
rect = surf.get_rect() # trova il Rect e lo posiziona
rect.center = center
surf_big.blit(surf_big_old, (0, 0))
surf_big.blit(surf, rect)
pygame.display.flip()
pygame.time.wait(20)
Scrivete ora un programma che mostri questi effetti grafici.
surf_text
ed assegnate alla variabile center
il centro della nostra finestra (cioè screen
).resize_surface(), flash_surface(), rotate_surface()
), provando gli effetti grafici sulla
surf_text
. Tra una chiamata e l'altra inserite una pausa di 2 secondi (sempre usando la
wait()
).Non ho spiegato dettagliatamente il funzionamento delle funzioni, ma dai commenti dovreste riuscire a capirlo. Potete poi provare a modificarle.
flash_surface()
modificate il numero di lampeggi e la velocità del lampeggio, nella
rotate_surface()
modificate il numero dei giri, l'angolo e la velocità di rotazione, nella
resize_surface()
modificate il numero dei passi e la velocità dell'effetto.Fine della lezione