Aiuto! Come faccio a far muovere un'immagine?
Molte persone che si affacciano alla programmazione e alla grafica spendono un bel pò di tempo cercando di capire come far muovere un'immagine sullo schermo. Se non si comprendono alcuni concetti questa operazione può risultare molto confusionaria. Non siete i primi ad essersi bloccati su questo punto; farò del mio meglio per spiegarvi il meccanismo passo dopo passo. Cercheremo di terminare questa sezione mostrando i metodi per rendere le vostre animazioni efficienti.
Notate che non è mia intenzione insegnarvi a programmare in python in questo articolo, ma solo introdurvi ad alcune delle basi di pygame.
Solo pixel sullo schermo
In pygame esiste una cosa chiamata Surface (superfice) del display. Questa non è altro che un'immagine visibile sullo schermo e questa immagine è formata da tanti pixel. Il modo comune di modificare questi pixel è chiamare la funzione blit() che copia i pixel da un'immagine all'altra.
Questa è la prima cosa da capire. Quando copiate un'mmagine sullo schermo tramite la funzione blit() ciò che fate in realtà è semplicemente cambiare il colore dei pixel. I pixel non vengono né aggiunti né spostati, stanno già sullo schermo e noi cambiamo semplicemente il loro colore. In pygame anche queste immagini che vengono copiate sullo schermo sono Surfaces (superfici), ma non sono in alcun modo connesse alla Surface del display, infatti anche quando queste vengono copiate sullo schermo voi conservate ancora una copia unica dell'immagine originale.
Dopo questa breve descrizione avrete forse già intuito cosa bisogna fare per “muovere” un'immagine sullo schermo. Al momento non muoviamo proprio nulla, ma più semplicemente copiamo l'immagine in una nuova posizione. Tuttavia prima di poter fare ciò abbiamo disogno di “pulire” il display. Altrimenti l'immagine sarà visibile in due punti diversi sullo schermo. Rimuovendo rapidamente l'immagine e ridisegnandola in una nuova posizione diamo l'illusione del movimento
Nel resto del tutorial divideremo questo processo in passi più semplici, spiegando anche qualè il modo migliore di muovere più immagini contemporaneamente sullo schermo. Probabilmente avrete già delle domande del genere “Come faccio a rimuovere un'immagine prima di ridisegnarla in un'altra posizione?” Forse siete ancora totalmente persi? Bene il resto del tutorial vi chiarirà le idee.
Facciamo un passo indietro
Forse i concetti di pixel ed immagine vi sono ancora un po' estranei? La buona notizia è che per alcune delle prossime sezioni useremo del codice che fa tutto quello che ci serve ma non usa i pixel. Creeremo una piccola lista contenete 6 numeri, immaginando che siano 6 fantastici oggetti grafici che possiamo ammirare sullo schermo. Al momento potrebbe sembrare sorprendente quanto questo esempio rappresenti esattamente ciò che più avanti faremo con la grafica.
E allora iniziamo creando la nostra lista/schermo e riempiendola con una meravigliosa sequenza di 1 e 2
1 >>> screen = [1, 1, 2, 2, 2, 1]
2 >>> print screen
3 [1, 1, 2, 2, 2, 1]
Ora abbiamo creato il nostro sfondo, ma non sarebbe eccitante se non disegnassimo anche un giocatore sullo schermo. Creimo allora un improbabile eroe rappresentato dal numero 8, posizioniamolo nel mezzo della mappa e vediamo come appare.
1 >>> screen[3] = 8
2 >>> print screen
3 [1, 1, 2, 8, 2, 1]
Questo potrebbe essere il massimo che siete riusciti a fare se vi siete avventurati subito nella produzione di qualcosa di grafico con pygame. Avete qualche bella immagine sullo schermo, ma non potete muoverla in nessun modo. Forse ora che il nostro schermo non è altro che una lista di numeri è più facile capire come far muovere l'eroe?
Facciamo muovere il nostro eroe
Prima di poter muovere il nostro personaggio abbiamo bisogno di tenere traccia del suo posizionamento. Nella precedente sezione, quando abbiamo disegnato il nostro eroe, abbiamo scelto una posizione arbitraria. Ora invece proveremo a strutturare il tutto un pò meglio.
1 >>> playerpos = 3
2 >>> screen[playerpos] = 8
3 >>> print screen
4 [1, 1, 2, 8, 2, 1]
Ora è abbastanza facile muovere il personaggio in un'altra posizione. Cambiamo semplicemente il valore di playerpos e lo ridisegnamo di nuovo.
1 >>> playerpos = playerpos - 1
2 >>> screen[playerpos] = 8
3 >>> print screen
4 [1, 1, 8, 8, 2, 1]
Oops. Adesso ci sono due eroi sullo schermo. Uno nella vecchia posizione e uno in quella nuova. Questa è esattamente la ragione per cui abbiamo bisogno di rimuovere l'eroe dalla sua vecchia posizione prima di ridisegnarlo. Per far ciò dobbiamo riportare gli elementi della lista ai valori che avevano prima che l'eroe venisse disegnato. Questo significa che dobbiamo tenere traccia del valore degli elementi sullo schermo prima che arrivi il nostro eroe a modificarli. Ci sono molti modi per fare ciò ma il più semplice è quello di conservare una copia dello sfondo. Ciò significa che ci tocca modificare ulteriormente il nostro piccolo gioco.
Creare una mappa
Ciò che vogliamo fare è creare una lista separata che costituirà il nostro sfondo. Dopodichè copieremo ogni elemento dalla lista/sfondo alla lista/schermo e a questo punto potremo disegnare il nostro eroe.
1 >>> background = [1, 1, 2, 2, 2, 1]
2 >>> screen = [0]*6 #a new blank screen
3 >>> for i in range(6):
4 ... screen[i] = background[i]
5 >>> print screen
6 [1, 1, 2, 2, 2, 1]
7 >>> playerpos = 3
8 >>> screen[playerpos] = 8
9 >>> print screen
10 [1, 1, 2, 8, 2, 1]
Potrebbe sembrare un sacco di lavoro extra, ma in realtà non siamo lontani da dove eravamo l'ultima volta che abbiamo provato a far muovere l'eroe, solo che adesso abbiamo le informazioni necesarie per farlo muovere correttamente.
Facciamo muovere il nostro eroe (seconda parte)
Questa volta sarà semplice muovere l'eroe. Prima di tutto rimuoviamo l'eroe dalla sua posizione precedente, copiando gli opportuni valori dallo sfondo allo schermo, e dopo disegnamo il personaggio nella sua nuova posizione.
1 >>> print screen
2 [1, 1, 2, 8, 2, 1]
3 >>> screen[playerpos] = background[playerpos]
4 >>> playerpos = playerpos - 1
5 >>> screen[playerpos] = 8
6 >>> print screen
7 [1, 1, 8, 2, 2, 1]
Tutto qua! L'eroe si è spostato di uno spazio verso sinistra. Possiamo usare lo stesso codice per farlo spostare di nuovo.
1 >>> screen[playerpos] = background[playerpos]
2 >>> playerpos = playerpos - 1
3 >>> screen[playerpos] = 8
4 >>> print screen
5 [1, 8, 2, 2, 2, 1]
Eccellente! Questo è esattamente ciò che chiamiate smooth animation. Con una serie di piccole modifiche faremo lavorare questo codice direttamente con la grafica.
Definizione: “blit”
Nelle prossime sezioni trasformeremo il nostro codice in maniera che utilizzi la grafica reale anziché le liste. Quando faremo apparire degli oggettio grafici sullo schermo useremo spesso il termine blit (ndt: quando usato come verbo tradurremo volgarmente blittare
) Se siete nuovi al mondo della grafica probabilmente non avete familiarità con questo termine.
BLIT: Fondamentalmente blittare vuol dire copiare i pixel da un'immagine su un'altra. Una definizione più formale è copiare un'array di dati in un altro bitmapped array. Oppure, molto più similmente a quanto abbiamo fatto in precedenza con la nostra lista/schermo, blittare può essere definito come assegnare i colori ai pixel di un'immagine.
Altre librerie grafiche usano termini come bitblt oppure solo blt che hanno comunque il medesimo significato di blit. Ad essere onesti il processo di blit non è proprio così semplice come l'abbiamo descritto perchè bisogna tener conto di altri fattori quali il formato dei pixel, clipping, scanline pitches, trasparenze ed altri effetti speciali.
Passiamo dalle liste allo schermo
Sarà tutto più comprensibile se manteniamo il codice dell'esempio precedente e cerchiamo di farlo funzionare con pygame. Supponiamo di aver caricato alcuni oggetti grafici denominati “terrain1”, “terrain2”, “terrain3” ... e “playerimage”. Mentre prima assegnavamo dei valori agli elementi di una lista ora blitteremo delle immagini su uno schermo. Un altro cambiamento importante è che mentre prima usavamo un solo indice per indicare la posizione del nostro personaggio adesso useremo due coordinate poiché lo schermo è una superfice bidimensionale. Supporremo che ognuno degli oggetti grafici del nostro gioco misuri 10 pixel di ampiezza.
1 >>> background = [terrain1, terrain1, terrain2, terrain2, terrain2, terrain1]
2 >>> screen = create_graphics_screen()
3 >>> for i in range(6):
4 ... screen.blit(background[i], (i*10, 0))
5 >>> playerpos = 3
6 >>> screen.blit(playerimage, (playerpos*10, 0))
Hmm, questo codice dovrebbe sembrarvi molto familiare e dovreste anche averne inteso il funzionamento nonché la somiglianza con ciò che in precedenza abbiamo fatto con le liste. L'unico piccolo lavoro extra è stato trasformare la posizione del giocatore in coordinate bidimensionali. Per ora ci siamo arrangiati con "(playerpos*10, 0)", ma in seguito faremo certamente di meglio. Adesso facciamo compiere al nostro eroe un piccolo spostamento. Il seguente codice non dovrebbe presentare sorprese.
1 >>> screen.blit(background[playerpos], (playerpos*10, 0))
2 >>> playerpos = playerpos - 1
3 >>> screen.blit(playerimage, (playerpos*10, 0))
Con questo codice abbiamo mostrato come disenare un semplice sfondo con sopra l'immagine di un personaggio. Poi abbiamo spostato il personaggio di uno spazio verso sinistra. Cosa faremo nel seguito? La prima cosa che vogliamo fare è trovare un metodo più chairo per rappresentare lo sfondo e la posizione del personaggio. Then perhaps a bit of smoother, real animation.
Coordinate sullo schermo
Per far apparire un oggetto sullo schermo dobbiamo dire alla funzione blit() dove posizionarlo. In pygame indichiamo sempre le posizioni sotto forma di coordinate (X,Y) dove X rappresenta la distanza in pixel dal margine destro del display e Y la distanza dal margine superiore. Le coordinate dell'angolo in alto a sinistra sono (0,0). Per muoverci un po' verso destra potremmo posizionarci su (10,0) e poi per muoverci verso il basso potremmo scendere a (10,10). Quando blittiamo un'immagine sullo schermo le coordinate che forniamo rappresentano il punto dove verrà posizionato l'angolo in alto a destra della nostra immagine.
Pygame ha un contenitore Rect molto conveniente per gestire le coordinate. L'oggetto Rect rappresenta fondamentalmente un rettangolo con l'angolo in alto a destra nelle coordinate prescelte ed ha un sacco di metodi che ne agevolano il posizionamento. Nel nostro prossimo esempio rappresenteremo la posizione degli oggetti grafici come oggetti Rect.
Sappiate inoltre che molte funzioni in pygame accettano argomenti di tipo Rect e che tutte queste funzioni possono accettare alternativamente anche una tupla di quattro elementi (x_angolo_in_alto_a_sinistra, y_angolo_in_alto_a_sinistra, larghezza, altezza). Anche la funzione blit() può accettare un oggetto Rect come argomento per il posizionamento; nel caso infatti utilizza come coordinate la posizione dell'angolo in alto a sinistra del rettangolo.
Cambiare lo sfondo
In tutte le sezioni precedenti abbiamo rappresentato lo sfondo come un lista contenente diversi oggetti grafici. Questo è un buon modo per creare un tile-based game, ma noi vogliamo un gioco a scorrimento. Per rendere il tutto più semplice useremo come sfondo una singola immagine abbastanza grande da coprire tutto il display. In questo modo quando cancelliamo i nostri oggetti grafici dallo schermo (prima di ridisegnarli) non dovremo fare altro che ridisegnare la parte di sfondo cancellata.
Possiamo dire alla funzione blit() di disegnare solamente una sottosezione di un'immagine, passando come terzo argomento un oggetto di tipo Rect.
Notate anche che quando finiamo di disegnare gli oggetti sul display chiamiamo la funzione pygame.display.update() che rende visibile sullo schermo tutto quello che abbiamo disegnato.
Movimento fluido
Per far si che un oggetto dia l'impressione di muoversi in maniera scorrevole dobbiamo spostarlo un po' alla volta. Ecco un'esempio di oggetto che si muove in maniera fluida lungo lo schermo. Poiché questo codice si basa su ciò che abbiamo spiegato finora dovrebbe apparire abbastanza semplice.
1 >>> screen = create_screen()
2 >>> player = load_player_image()
3 >>> background = load_background_image()
4 >>> screen.blit(background, (0, 0)) #draw the background
5 >>> position = player.get_rect()
6 >>> screen.blit(player, position) #draw the player
7 >>> pygame.display.update() #and show it all
8 >>> for x in range(100): #animate 100 frames
9 ... screen.blit(background, position, position) #erase
10 ... position = position.move(2, 0) #move player
11 ... screen.blit(player, position) #draw new player
12 ... pygame.display.update() #and show it all
13 ... pygame.time.delay(100) #stop the program for 1/10 second
Ecco qua! Questo è tutto il codice di cui avete bisogno per far muovere un oggetto lungo lo schermo. We can even use a pretty background character. Uno dei vantaggi di realizzare lo sfondo in questo modo, sta nel fatto che l'immagine del giocatore viene disegnata correttamente sullo sfondo anche se contiene delle trasparenze o delle sezioni ritagliate.
In questo codice abbiamo visto anche una chiamata a pygame.time.delay() alla fine di ogni iterazione. Questa serve a rallentare leggermente il nostro programma, che altrimenti verrebbe eseguito così velocemente che non avreste il tempo di vedere nulla.
Allora? Come proseguiamo?
Bene. Si spera che questo articolo sia riuscito a spiegare tutto ciò che era stato promesso. Tuttavia , al punto in cui siamo, il nostro codice non è pronto per il prossimo gioco commerciale. Come possiamo ottenere più oggetti in movimento sullo schermo? Quali sarebbero queste funzioni misteriose tipo load_player_image()? Come si fa per raccogliere l'input dell'utente o per relizzare un'animazione la cui durata vada oltre le 100 iterazioni? Prenderemo l'esempio precedente e lo trasformeremo in una fantastica creazione orientata agli oggetti che renderà le vostre mamme fiere di voi.
Le funzioni misteriose
Potete trovare informazioni complete su questo tipo di funzioni in altri tutorial e reference manual. Il modulo pygame.image ha una funzione load() che fa esattamente ciò che ci aspettiamo. Le linee di codice per caricare le immagini diventano così:
1 >>> player = pygame.image.load('player.bmp').convert()
2 >>> background = pygame.image.load('liquid.bmp').convert()
Come potete vedere è abbastanza semplice. La funzione load() accetta come argomento un nome di file e ritorna un oggetto Surface con l'immagine caricata. Dopo aver caricato le immagini facciamo una chiamata al metodo convert() che ritorna un nuovo oggetto Surface il cui formato dei pixel è lo stesso di quello del nostro schermo. Quando le immagini hanno lo stesso formato pixel dello schermo vengono blittate molto velocemente, altrimenti la funzione blit() sarà più lenta perchè dovrà occuparsi anche della coversione.
Come avrete capito le funzioni load() e convert() ritornano oggetti di tipo Surface. Questo vuol dire che con una sola linea di codice abbiamo generato due oggetti Surface. In altri linguaggi di programmazione questo provocherebbe un memory leak ma fortunatamnte python è abbastanza furbo da evitare ciò, e pygame provvederà a eliminare gli oggetti Surface non utilizzati.
L'altra funzione miosteriosa che abbiamo visto nell'esempio precedente era create_screen(). In pygame è semplice creare una nuova finestra per la grafica. Ecco il codice per creare una finestra 640x480
1 >>> screen = pygame.display.set_mode((640, 480))
Se non specifichiamo altri argomenti pygame sceglierà automaticamente la profondità del colore e il formato pixel migliori per il nostro hardware.
Catturiamo l'Input
Abbiamo un disperati bisogno di modificare il ciclo principale del nostro programma per raccogliere alcuni input dell'utente (come ad esempio il segnale di chiusura dell'applicaione). Tutti i programmi grafici usano questo modello basato sugli eventi. I programmi rispondono ad eventi come la pressione di un tasto o il movimento del mouse. Questa volta anziché terminare il programma dopo 100 iterazioni aspetteremo che sia l'utente a decidere di terminare l'applicazione. Ecco come dovrebbe apparire il codice.
1 >>> while 1:
2 ... for event in pygame.event.get():
3 ... if event.type in (QUIT, KEYDOWN):
4 ... sys.exit()
5 ... move_and_draw_all_game_objects()
Questo codice entra in un ciclo infinito e controlla se ci sono input dell'utente. Usciamo dl programma se l'utente preme un tasto oppure chiude la finestra. Dopo aver controllato la presenza di eventi muoviamo a disegnamo tutti gli oggetti grafici che vogliamo.
Muovere più immagini contemporaneamente
Qui le cose cambiano un pò. Diciamo per esempio che vogliamo far muovere sullo schermo dieci oggetti differrenti. Un buon modo di realizzare ciò è usare le classi. Creeremo una classe che rappresenta un oggetto del gioco e che avrà un metodo per muoversi. Poi creeremo tanti oggetti quanti ne vogliamo vedere sullo schermo. Il metodo per muoversi dovrà funzionare in modo che ogni volta che viene chiamato l'oggetto si sposta di un passo. Questo è il codice python per creare la nostra classe.
1 >>> class GameObject:
2 ... def __init__(self, image, height, speed):
3 ... self.speed = speed
4 ... self.image = image
5 ... self.pos = image.get_rect().move(0, height)
6 ... def move(self):
7 ... self.pos = self.pos.move(0, self.speed)
8 ... if self.pos.right > 600:
9 ... self.pos.left = 0
Abbiamo due metodi in questa classe. init() costruisce il nostro oggetto lo posiziona e setta la sua velocità. Il metodo move() invece muove l'oggetto di un passo e se va troppo lontano lo riposiziona a sinistra.
Mettiamo tutto insieme
Ora che abbiamo i nostri oggetti possiamo metterli insieme nel nostro gioco. Ecco come appare il ciclo principale del nostro programma.
1 >>> screen = pygame.display.set_mode((640, 480))
2 >>> player = pygame.image.load('player.bmp').convert()
3 >>> background = pygame.image.load('background.bmp').convert()
4 >>> screen.blit(background, (0, 0))
5 >>> objects = []
6 >>> for x in range(10): #create 10 objects
7 ... o = GameObject(player, x*40, x)
8 ... objects.append(o)
9 >>> while 1:
10 ... for event in pygame.event.get():
11 ... if event.type in (QUIT, KEYDOWN):
12 ... sys.exit()
13 ... for o in objects:
14 ... screen.blit(background, o.pos, o.pos)
15 ... for o in objects:
16 ... o.move()
17 ... screen.draw(o.image, o.pos)
18 ... pygame.display.update()
19 ... pygame.time.delay(100)
Questo è il codice per animare 10 oggetti. L'unico punto che richiede una spiegazione sono i due cicli che usiamo per rimuovere e ridisegnare tutti gli oggetti. Se vogliamo fare le cose per bene abbiamo bisogno di cancellare tutti gli oggetti prima di ridisegnarli. Nel nostro programma di prova potrebbe non essere importante ma when objects are overlapping, usare due cicli diventa importante.
Adesso camminate con le vostre gambe
Quale sarà il prossimo passo sulla vostra strada verso l'apprendimento? Pima di tuto giocate un po' con questo esempio. La versione completa del codice di questo esempio è disponibile nella directory degli esempi di pygame sotto il nome “moveit.py”. Leggete il codice , giocateci, eseguitelo, imparatelo!
Alcune cose su cui potresti lavorare potrebbero essere: avere diversi tipi di oggetto, trovare un modo di eliminare definitivamente un oggetto, passare al metodo display.update() una lista delle aree dello schermo che sono state modificate.
Ci sono inoltre anche tutorial ed esempi in pygame che coprono questi argomenti. Quindi se siete pronti per imparare, iniziate a leggere.
Sentitevi inoltre liberi di scrivere nella mailing list o nella chatroom di pygame e porre le vostre domande. There's always folks on hand who can help you out with this sort of business.
E per finire divertitevi, lo scopo di un gioco è proprio questo!