Introduzione al modulo Sprite
La versione 1.3 di pygame introduce un nuovo modulo, pygame.sprite. Questo modulo è scritto in python ed include alcune classi di alto livello utili per gestire gli oggetti di un gioco. Utilizzando a pieno le potenzialità di questo modulo potrete gestire e disegnare facilmente i componenti dei vostri giochi. Inoltre le classi contenute in questo modulo sono molto ottimizzate ed è probabile che se le utilizate il vostro gioco risulterà più efficiente.
Il modulo sprite è molto generico, il che lo rende adatto a creare quasi ogni tipo di gioco. Tuttavia questa sua flessibilità richiede uno sforzo maggiore per il suo apprendimento. Il reference manual può aiutarvi ma probabilmente avrete bisogno di qualche spiegazione supplementare sull'utilizzo di questo modulo.
Molti degli esempi di pygame (come quello dello scimpanzè o quello degli alieni) sono stati aggiornati per utilizzare questo modulo. Potreste voler dare prima uno sguardo a questi esempi per farvi un'idea di cosa fa il modulo sprite. Il modulo chimp ha anche un suo proprio tutorial che lo spiega riga per riga.
Notate che questa introduzione richiede un po' di esperienza nella programmazione con python e un po' di familiarità con le diverse fasi della creazione di un semplice gioco. Occasionalmete sarà usato il termine “referenza”. le referenze non sono altro che variabili python ed è possibile avere anche più referenze che puntano allo stesso oggetto.
Lezione di storia
Il termine sprite deriva dalla terminologia dei computer più datati. Queste macchine non erano in grado di cancellare e ridisegnare la grafica abbastanza velocemente da far girare un gioco ma avevano un hardware speciale che gestiva i giochi come “oggetti che devono essere animati molto velocemente”. Questi oggetti erano appunto chiamati sprite e nonostante avessero delle particolari limitazioni potevano essere disegnati rapidamente. Generalmente gli sprite esistevano all'interno di speciali overlay buffers. Ai giorni nostri i computer sono abbastanza potenti da poter gestire la grafica senza bidogno di hardware dedicato, tuttavia il termine sprite è ancora utilizzato per indicare praticamente qualsiasi cosa vanga animata in un gioco 2D.
Le Classi
Il modulo sprite contiene due classi principali. La prima è la classe Sprite che dovrebbe essere utilizzata come classe madre per tutti gli oggetti del vostro gioco. Di per sé questa classe non fa nulla ma include alcuni metodi che aiutano a gestire gli oggetti del gioco. L'altra classe è Group. Questa classe è un contenitore per oggetti Sprite. Ci sono diversi tipi di classe/contenitore ed alcune di queste possono per esempio disegnare tutti gli oggetti in esse contenuti.
This is all there really is to it. Cominceremo con una descrizione di quello che queste due classi possono fare e poi parleremo di come utilizzarle.
La classe Sprite
Come abbiamo detto in precedenza la classe Sprite è pensata per essere la classe base di tutti gli oggetti del vostro gioco. Non è possibile utilizzarla direttamente poiché contiene soltanto alcuni metodi che aiutano a lavorare con le diverse classi/contenitore. Ogni oggetto Sprite tiene traccia dei gruppi a cui appartiene. Il costruttore di questa classe accetta come argomento un oggetto Group (o una lista di oggetti Group) che rappresenta il/i gruppo/i a cui l'oggetto Sprite dovrà appartenere. I metodi add() e remove() permettono di modificare la lista dei gruppi a cui un oggetto Sprite appartiene. Il metodo groups() ritorna la lista dei gruppi che contengono l'oggetto Sprite.
E' buona norma considerare un oggetto Sprite come “valido” o “attivo” finchè appartiene ad uno o più gruppi. Quando rimuovete un oggetto Sprite da tutti i gruppi a cui appartiene, questo verrà automaticamente eliminato da pygame (a meno che non abbiate una referenza all'oggetto da qualche altra parte). Il metodo kill() rimuove un oggetto Sprite da tutti i gruppi a cui appartiene. Questo è molto comodo quando ad esempio mettete insieme tanti piccoli giochi e rimuovere un oggetto può risultare problematico. La classe Sprite ha anche un metodo alive() che restituisce True se l'oggetto appartiene ancora a qualche gruppo.
La classe Group
La classe Group è un semplice contenitore. Come la classe Sprite ha dei metodi add() e remove() per aggiungere o rimuovere oggetti Sprite. E' possibile anche passare un oggetto Sprite o una lista di oggetti Sprite come argomento al costruttore della classe Group.
Questa classe ha pochi altri metodi come empty() che rimuove tutti gli oggetti Sprite dal gruppo e copy() che restituisce una copia esatta del gruppo e di tutti i suoi membri. C'è anche il metodo has() che restituisce True se il gruppo non è vuoto.
Un'altra funzione che userete frequentemente è il metodo sprites() che restituisce un oggetto iterabile (una lista o un generatore) contenente tutti gli oggetti Sprites del gruppo.
Come scorciatoia la classe Group ha anche un metodo update() che richiama il metodo update() di ogni oggetto Sprite presente nel gruppo passando ad ognuno gli stessi argomenti. Solitamente in un gioco avrete bisogno di funzioni per aggiornare gli oggetti. Potete fare ciò applicando i vostri metodi su ogni elemento restituito da Group.sprites(), ma generalmente il metodo Group.update() è una scorciatoia abbastanza utilizzata. Notate anche che la classe Sprite ha un metodo update() “stupido” che accetta ogni tipo di argomento e non restituisce nulla.
Infine è possibile utilizzare sugli oggetti Group alcuni metodi builtin come len() o l'operatore “truth” che permette di scrivere “if miogruppo:” per verificare se il gruppo non è vuoto.
Usare insieme le due classi
A questo punto potrebbe sembrare che queste classi non facciano molto di più di quanto si potrebbe fare con una semplice lista e gli oggetti del vostro gioco. Ma ci sono invece dei grandi vantaggi nell'usare insieme Sprite e Group. Un oggetto Sprite può appartenere a tutti i gruppi che volete. Ricordate che non appena un oggetto Sprite viene rimosso da tutti i gruppi a cui appartiene viene automaticamente eliminato (tranne se evete una referenza all'oggetto da qualche altra parte).
Il primo grande vantaggio è quello di avere un metodo semplice per classificare i vari oggetti Sprite. Per esempio supponiamo di voler realizzare il gioco Pacman. Potremmo creare un gruppo per ogni tipo di oggetto presente nel gioco (Fantasmi, Pac e Palline). Quando Pac mangia una pallina possiamo cambiare lo stato di tutti gli oggetti Fantasma semplicemente operando su un oggetto Group. Questa operazione è molto più veloce che iterare su una lista contenete tutti gli oggetti del gioco e controllare quali di questi sono fantasmi.
Aggiungere e rimuovere sprites e gruppi gli uni agli altri è un'operazione più veloce che utilizzare delle liste per contenere ogni cosa. I gruppi possono essere utilizzati per svolgere la funzione di semplici attributi. Ad esempio in un gioco anziché tenere traccia di un attributo “vicino_al_giocatore” per ogni nemico, potreste aggiungere tutti i nemici ad un gruppo separato così quando avete bisogno sapere quali nemici si trovano vicino al giocatore basterà accedere al gruppo. Altrimenti dovreste scorrere la lista dei nemici e controllare uno per uno l'attributo “vicino_al_giocatore”. Se poi al gioco aggiungete una modalità multigiocatore anziché definire tanti attributi “vicino_al_giocatore2”, “vicino_al_giocatore3” etc... basterà inserire i nemici in gruppi diversi.
Un altro importante vantaggio nell'utilizzo di Sprite e Group è che i gruppi permettono di eliminare gli oggetti in maniera semplice. In un gioco dove molti oggetti ne referenziano altri, eliminare un oggetto può risultare problematico dato che bisogna eliminare tutte le referenze a quell'oggetto. Supponiamo di avere un oggetto che ne insegue un altro. L'inseguitore potrebbe appartenere ad un gruppo che referenzia l'oggetto inseguito. Se quest'ultimo viene distrutto non avremo bisogno di notificare all'inseguitore di terminare l'inseguimento poiché l'inseguitore capirà da sè che il suo gruppo è vuoto e forse deve cercare un altro oggetto da inseguire.
Un'altra cosa da ricordare è che aggiungere e rimuovere oggetti Sprite da un gruppo è un'operazione molto veloce. You may be best off aggiungendo molti gruppi per contenere e organizzare gli oggetti del vostro gioco. Anche se alcuni di questi gruppi rimagono vuoti per la maggior parte del tempo, non vi è alcuna penalità nel gestire il vostro gioco in questo modo.
I diversi tipi di gruppo
Gli esempi e le motivazioni portate finora sull'utilizzo di Sprite e Group sono solo la punta dell'iceberg. Un altro vantaggio è che il modulo Sprite contiene diversi tipi di classe Group. Queste classi lavorano esattamente come un normale oggetto Group ma aggiungono delle funzionalità. Ecco una lista delle classi Group incluse nel modulo sprite.
- E' la classe base, senza fronzoli, di cui abbiamo già parlato. Molte altre classi/gruppo derivano da questa.
- Può contenere un solo oggetto Sprite. Quando ne aggiungete uno nuovo quello vecchio viene dimenticato. Di conseguenza questo gruppo contiene sempre uno o zero oggetti Sprite.
- Questo è un gruppo standard derivato da Group. Ha un metodo draw() che disegna sullo schermo (o su un oggetto Surface) tutti gli oggetti Sprite contenuti nel gruppo. Ma per fare ciò tutti gli oggetti che appartengono al gruppo devono avere due attributi “image” e “rect” usati per sapere cosa blittare e dove blittarlo.
E' derivato dal gruppo RenderPlain e aggiunge un metodo clear() che cancella tutti gli oggetti Sprite precedentemente disegnati. Utilizza un'immagine di sfondo per riempire le aree dove si trovano gli oggetti Sprite.
Questa è la cadillac dei gruppi di rendering. E' derivata dalla classe RenderClear, ma modifica il metodo draw() per restituire una lista di oggetti Rect che rappresentano le aree dello schermo modificate.
Questa è la lista dei gruppi disponibili ma nulla vi impedisce di crearne altri. Discuteremo di queti gruppi anche nella prossima sezione. Si tratta in fondo solo di codice python e quindi potete anche creare classi derivate e aggiungere quello che volete. In futuro spero verranno aggiunte molti altri gruppi a questa lista. Ad esempio una classe GroupMulti che si comporta come la classe GroupSingle ma con un dato numero di oggetti. Oppure un gruppo SuperRender che cancella gli oggetti Sprite disegnati senza bisogno di un'immagine di sfondo (potrebbe ricavarne una copiando lo schermo prima di blittare). Chissà, in futuro potremmo aggiungere molte altre classi utili.