Ecco la penultima parte di questa mini guida sulle sprites. Oggi parleremo dell’ultima classe da dover analizzare, ovvero quella del giocatore. Questo oggetto rappresenterà il nostro alter ego che si muoverà sullo schermo e, in questo caso, farà esplodere delle bombe se cammina sopra di esse. La cosa è alquanto semplice, sopratutto perché molte cose si sono già analizzate nella classe precedente, ma non lascierò niente al caso e spiegherò comunque riga per riga (cercando di non essere ripetitivo) per delineare meglio le scelte che ho preso.
Prima di passare al codice vorrei ricordare che padroneggiare questi concetti in due dimensioni (sprites, collisioni ecc…) è fondamentale per poter poi passare al gradino successivo: il 3D.
Codice
class oggetto_sprite(pygame.sprite.Sprite): def __init__(self,nome,altezza,larghezza,xy,screen, num, gruppo): pygame.sprite.Sprite.__init__(self) self.immagini = carica_imm_sprite(nome,altezza,larghezza,num) self.immagine = self.immagini[0] self.coordinate = (int(xy[0]),int(xy[1])) self.screen = screen self.rect = pygame.Rect((int(xy[0]),int(xy[1])),(larghezza,altezza)) self.animazione = False self.anim_corrente = 0 self.tempo_animazione = 0.0 self.passo1 = pygame.mixer.Sound("passo1.wav") self.passo2 = pygame.mixer.Sound("passo2.wav") self.Passi = pygame.mixer.Channel(1) self.gruppo = gruppo self.vita = True def collisione(self): for x in self.gruppo: if self.rect.colliderect(x.rect)== True and self.vita == True: self.vita = False x.update(tempo_passato, "esplodi") x.fine = True def anime(self,frame): if self.animazione == False: self.anim_corrente = 0 self.immagine = self.immagini[frame] return else : if self.tempo_animazione<0.150: self.tempo_animazione += orologio.get_time()/1000. else: self.immagine = self.immagini[self.anim_corrente+frame] if self.Passi.get_sound() is None or self.Passi.get_sound() != self.passo1: if self.Passi.get_busy() == False: self.Passi = self.passo1.play() else: self.Passi.queue(self.passo1) else : if self.Passi.get_busy() == True: self.Passi.queue(self.passo2) if self.anim_corrente < 3: self.anim_corrente +=1 else: self.anim_corrente = 0 self.tempo_animazione = 0 def update(self, direzione,frame): self.animazione = True self.anime(frame) x,y = self.coordinate if direzione == "basso": y += 1*speed*tempo_passato self.coordinate = x,y return if direzione == "alto": y -= 1*speed*tempo_passato self.coordinate = x,y return if direzione == "destra": x += 1*speed*tempo_passato self.coordinate = x,y return if direzione == "sinistra": x -= 1*speed*tempo_passato self.coordinate = x,y return elif direzione == "stop": self.animazione = False self.Passi.stop() def render(self): agg_rect(self) self.collisione() self.screen.blit(self.immagine, self.coordinate)
Classe oggetto_sprite : def _init_
- Per creare il nostro personaggio, come per l’altra classe, abbiamo bisogno del nome (nome) del file da caricare, l’altezza (altezza), la larghezza (larghezza), le coordinate (xy), lo schermo dove renderizzare (screen), il numero di immagini “num” (se necessario) e il gruppo (gruppo). Il gruppo rappresenta tutte le altre sprite al di fuori del personaggio. Vengono memorizzate per definire le collisioni con esse e le eventuali azioni da compiere. Vedremo successivamente come. Da notare che l’oggetto rect in questo caso è pari all’immagine caricata ed inoltre memorizzaimo i passi (suoni che successivamente vedremo come utilizzare) del personaggio e creiamo un canale apposito per loro. Infine abbiamo bisogno di una variabile “vita”, che in questo caso rappresenta il personaggio ancora in gioco, perché non ha ancora fatto esplodere nessuna bomba.
Classe oggetto_sprite : def collisione
- Con questa funzione verifichiamo se il nostro alter ego è passato sopra una delle bombe. Per farlo controlliamo se l’oggetto rect del nostro personaggio collide con una sprite memorizzata nel gruppo. Non dimentichiamo che questo può accadere solo se è ancora in vita (perché in caso contrario si deve andare al fine gioco). Quindi se la bomba viene calpestata si deve richiamare l’animazione dell’esplosione solo sull’oggetto in questione (linea 27) e , visto che abbiamo settato la vita a false, non ci resta che modificare il valore “fine” della bomba esplosa. In questo modo, conclusa l’esplosione, la bomba richiamerà il fine gioco.
Classe oggetto_sprite : def anime
- Se non siamo in tempo di animazione, settiamo la nostra immagine corrente a 0 (cioè la prima) e ritorniamo.
- In caso contrario, gestiamo l’animazione in questo modo (a partire dalla linea 35): controlliamo che il tempo di animazione sia quello giusto, altrimenti non possiamo dare inizio all’animazione, ma bisogna aspettare. Da notare che in questo caso non abbiamo preso i “tps” ma utilizziamo un funzione dell’oggetto Clock (get_time()) che ci restituisce il tempo corrente dell’ultimo Clock.tick chiamato. Visto che è in millisecondi dobbiamo dividere per mille. Il procedimento in poche parole è lo stesso, dipende solo da voi come gestire il problema (quindi ho solo elencato due metodi equivaltenti per avere lo stesso risultato ai fini del gioco).
- Come avrete sicuramente notato, alla funzione passiamo il parametro frame. Questo perché se l’animazione comincia (linea 38), dobbiamo sapere quale serie di immagini dobbiamo far visualizzare. Basta pensare che abbiamo memorizzato i movimenti all’interno di una matrice e quindi una serie di azioni differisce dall’altra per un parametro (frame). Quindi basta aggiornare l’animazione corrente sommando il frame che dobbiamo visualizzare. La figura sottostante vi chiarirà il tutto.
- Quindi l’animazione consiste solo nel fatto di incrementare l’animazione corrente fino ad un massimo di 3 per poi azzerarla. A fine animazione azzeriamo anche il tempo, perché nel prossimo ciclo dobbiamo aspettare altri 0.150 secondi per cambiare frame.
- Da notare invece alla riga 40 la gestione dei passi del personaggio. Per prima cosa controlliamo che il rumore dei passi non sia già in esecuzione. Quindi se il file in esecuzione è diverso dal passo1 (inteso come primo passo da far sentire), mettiamo in coda di esecuzione, nel canale apposito, passo1. Facciamo un ulteriore controllo se il canale è impegnato alla riga 41, perché se è la prima volta che il personaggio si muove, vogliamo far sentire il passo1 per primo. Infine mettiamo in coda il passo2. In questo modo avremo un realistico rumore quando il nostro personaggio camminerà (ricordate di tenere bene a mente i tempi animazione con la lunghezza dei suoni che scegliete per non rovinare il realismo della scena).
Classe oggetto_sprite : def update
- Passandogli il frame, aggiorniamo la sprite memorizzandolo e chiamando l’animazione del frame corrente. Prendiamo successivamente le coordinate attuali del personaggio, perché dovranno essere incrementate a seconda della direzione che abbiamo passato. Infine controlliamo anche se il nostro personaggio si deve fermare, ternimando quindi l’animazione e il rumore dei passi.
Classe oggetto_sprite : def render
- Render si occupa di far visualizzare il nostro alter ego sullo schermo nel seguente ordine: aggiorna le coordinate dell’oggetto rect del personaggio, verifica eventuali collisioni con altri oggetti ed infine renderizza sullo schermo memorizzato (screen) l’immagine attuale a seconda delle coordinate interne alla sprite.
Conclusioni
Nell’ultima parte vedremo come sono formate le funzioni run e notrun, così da provare il tutto. Come avete potuto constatare, gestire una sprite non è poi così difficile, ma sopratutto, si può fare in molti modi diversi a seconda delle necessità e delle indee di chi programma. Spero solo che questi concetti siano chiari e che l’esempio, una volta concluso, sia di vostro gradimento.
PS: il personaggio per ora si muove in direzione diagonale in modo anomalo, più avanti elimineremo anche questo inconveniente.