PyGame tutorials
Scimpanzé riga per riga
by Pete Shinners
Revision 2.2, June 17, 2004
Introduzione
Fra gli esempi di pygame ve ne è uno semplice, chiamato “scimpanzé”(chimp). Questo esempio simula una scimmia da colpire mentre si muove nello schermo con promesse di ricchezza e ricompense. L'esempio in sé è molto semplice, e un po' stringato nel controllo degli errori del codice. Mostra molte delle potenzialità di pygame, come la creazione di finestre, il caricamento di immagini e suoni, la visualizzazione di testi TTF, gestione del mouse e degli eventi basilari.
Il programma e le immagini possono essere trovati all'interno della distribuzione standard dei sorgenti di pygame. Con la versione1.3 di pygame questo esempio è stato completamente riscritto, per aggiungere un paio di funzionalità e un corretto controllo degli errori. Questo ha circa duplicato la lunghezza originale del codice, ma ci dà molte cose in più da osservare, oltre che codice che consiglio di riutilizzare per i vostri progetti.
Questo tutorial percorrerà il codice blocco per blocco. Spiegando come funziona. Sarà inoltre detto come il codice potrebbe essere migliorato e quale controllo degli errori potrebbe aiutare.
Questo è un eccellente tutorial per coloro che danno il loro primo sguardo a del codice pygame. Una volta che pygame è completamente installato, potete trovare la demo “chimp” nella directory degli esempi ed eseguirla.
Importazione dei moduli
Questo è il codice che importa tutti i moduli necessari al programma. Verifica inoltre la disponibilità di alcuni moduli pygame opzionali.
import os, sys import pygame from pygame.locals import * if not pygame.font: print 'Warning, fonts disabled' if not pygame.mixer: print 'Warning, sound disabled'
Per prima cosa importiamo i moduli standard “os” e “sys”. Questo ci permette di creare percorsi di file indipendentemente dalla piattaforma.
Nella riga successiva importiamo il pacchetto pygame. Quando pygame viene importato, importa tutti i moduli pygame disponibili. Alcuni moduli pygame sono opzionali e, se non vengono trovati, il loro valore è settato a “None”.
C'è un modulo speciale di pygame chiamato “locals”. Questo modulo contiene un sotto set di moduli pygame. I componenti di questo modulo sono costanti e funzioni frequentemente usate che è utile mettere nelle variabili globali del vostro programma. Questi moduli locali includono funzioni come “Rect” per creare un oggetto rettangolo, e molte costanti tipo “QUIT,HWSURFACE” che sono usate per interagire con il resto di pygame. L'importazione dei moduli locali nelle variabili globali, come mostrato qui, è interamente opzionale. Se scegliete di non importarle, tutti i membri di “locals” sono sempre disponibili nel modulo pygame.
Per ultima cosa, decidiamo di stampare un bell'avvertimento se i i moduli per i font e i suoni non sono disponibili.
Risorse per il caricamento
Qui abbiamo due funzioni che possiamo usare per caricare immagini e suoni. Analizzeremo ogni funzione separatamente in questa sezione.
def load_image(name, colorkey=None):
fullname = os.path.join('data', name)
try:
image = pygame.image.load(fullname)
except pygame.error, message:
print 'Cannot load image:', name
raise SystemExit, message
image = image.convert()
if colorkey is not None:
if colorkey is -1:
colorkey = image.get_at((0,0))
image.set_colorkey(colorkey, RLEACCEL)
return image, image.get_rect()
Questa funzione prende il nome di una immagine da caricare. Accetta facoltativamente anche un argomento per settare la colorkey (codice del colore) dell'immagine. Le colorkey sono usate in grafica per rappresentare il colore dell'immagine che è trasparente.
Per prima cosa questa funzione crea un percorso completo al file. In questo esempio tutte le risorse son in una subdirectory “data”. Usando la funzione os.path.join verrà creato un percorso che funzioni su qualsiasi piattaforma.
Poi carichiamo l'immagine usando la funzione pygame.image.load. Inseriamo questa funzione in un blocco try/except, così se troviamo un problema nel caricare l'immagine, ne possiamo uscire più elegantemente. Dopo che l'immagine è caricata, facciamo una importante chiamata alla funzione convert(). Questa fa una nuova copia della Surface e converte il formato del suo colore e la sua profondità per accordarsi al display. Questo significa blitting l'immagine allo schermo prima possibile.
Infine settiamo la colorkey dell'immagine. Se l'utente fornisce un argomento per la colorkey utilizzeremo questo valore come colorkey dell'immagine. Questo sarà solitamente un colore RGB, come (255,255,255) per il bianco. Potete anche fornire il valore -1 come colorkey. In questo caso la funzione cercherà il colore nel margine in alto a sinistra, e lo utilizzerà come colorkey.
def load_sound(name):
class NoneSound:
def play(self): pass
if not pygame.mixer:
return NoneSound()
fullname = os.path.join('data', name)
try:
sound = pygame.mixer.Sound(fullname)
except pygame.error, message:
print 'Cannot load sound:', wav
raise SystemExit, message
return sound
Adesso la funzione per caricare un suono. La prima cosa che questa funzione fa è verificare che il modulo pygame.mixer sia stato importato correttamente. Se non è così, crea una piccola istanza di classe con un semplice metodo di riproduzione. Questo si comporterà in modo simile al normale oggetto Sound da far girare il gioco senza alcun controllo extra degli errori.
Questa funzione è simile a quella per caricare le immagini, ma gestisce differenti problemi. Per prima cosa creiamo un percorso completo all'immagine del suono, e carica il file del suono all'interno di un blocco try/except. Infine ritorniamo semplicemente il Sound object caricato.
Classe Game Object
Qui creiamo due classi per rappresentare l'oggetto nel gioco. Quasi tutta la logica del gioco finisce in queste due classi. Le analizzeremo qui separatamente.
class Fist(pygame.sprite.Sprite):
"""moves a clenched fist on the screen, following the mouse"""
def __init__(self):
pygame.sprite.Sprite.__init__(self) #call Sprite initializer
self.image, self.rect = load_image('fist.bmp', -1)
self.punching = 0
def update(self):
"move the fist based on the mouse position"
pos = pygame.mouse.get_pos()
self.rect.midtop = pos
if self.punching:
self.rect.move_ip(5, 10)
def punch(self, target):
"returns true if the fist collides with the target"
if not self.punching:
self.punching = 1
hitbox = self.rect.inflate(-5, -5)
return hitbox.colliderect(target.rect)
def unpunch(self):
"called to pull the fist back"
self.punching = 0
Qui creiamo una classe per rappresentare il pugno del giocatore. Deriva dalla classe Sprite inclusa nel modulo pygame.sprite. La funzione init viene chiamata sono create nuove istanze della classe. La prima cosa che facciamo è essere sicuri di chiamare la funzione init per la nostra classe di base. Questo permette all' init di Sprite di preparare l'oggetto a mo' di sprite. Questo gioco usa uno dei gruppi di classi per disegnare sprite. Queste classi possono disegnare sprite con un attributo “immagine” e “rect”. Semplicemente cambiando questi due attributi verrà disegnata l'immagine corrente nella posizione corrente.
Tutte le sprite hanno un metodo update(). Questa funzione è tipicamente chiamata una volta per frame. É qui che dovete mettere il codice che muove e aggiorna le variabili per la sprite. Il metodo update() per muovere il pugno colpisce la locazione del puntatore del mouse. Offre anche la posizione del pugno durante il pugno.
Le due seguenti funzioni punch() e unpunch() cambiano lo stato del pugno. Il metodo punch() ritorna anche un valore “True” se il pugno colpisce la sprite bersaglio.
class Chimp(pygame.sprite.Sprite):
"""moves a monkey critter across the screen. it can spin the
monkey when it is punched."""
def __init__(self):
pygame.sprite.Sprite.__init__(self) #call Sprite intializer
self.image, self.rect = load_image('chimp.bmp', -1)
screen = pygame.display.get_surface()
self.area = screen.get_rect()
self.rect.topleft = 10, 10
self.move = 9
self.dizzy = 0
def update(self):
"walk or spin, depending on the monkeys state"
if self.dizzy:
self._spin()
else:
self._walk()
def _walk(self):
"move the monkey across the screen, and turn at the ends"
newpos = self.rect.move((self.move, 0))
if not self.area.contains(newpos):
if self.rect.left < self.area.left or \
self.rect.right > self.area.right:
self.move = -self.move
newpos = self.rect.move((self.move, 0))
self.image = pygame.transform.flip(self.image, 1, 0)
self.rect = newpos
def _spin(self):
"spin the monkey image"
center = self.rect.center
self.dizzy += 12
if self.dizzy >= 360:
self.dizzy = 0
self.image = self.original
else:
rotate = pygame.transform.rotate
self.image = rotate(self.original, self.dizzy)
self.rect = self.image.get_rect(center=center)
def punched(self):
"this will cause the monkey to start spinning"
if not self.dizzy:
self.dizzy = 1
self.original = self.image
La classe chimp svolge un lavoro un poco maggiore della classe pugno, ma niente di più complesso. Questa classe muoverà la scimmia avanti e indietro per lo schermo. Quando riceve un pugno, he will spin around to exciting effect . Questa classe è anch'essa derivata dalla classe base Sprite, e è inizializzata allo stesso modo di fist. Quando inizializzata, la classe setta 'attributo “area” come dimensione dello schermo.
La funzione update per lo scimpanzè guarda semplicemente al corrente stato “dizzy”, che ritorna True quando la scimmia rotola per effetto di un pugno. Richiama entrambi i metodi _spin e _walk. Queste funzioni sono prefissate con un underscore (il carattere _ ). Questo è solo un idioma standard di python che suggerisce che questi due metodi dovrebbero essere usati dalla sola classe madre. Potremmo arrivare a dargli il doppio underscore ( ), che direbbe a python che il metodo è realmente privato e limitato alla classe madre, ma non necessitiamo di tanta protezione.
Il metodo walk crea una nuova posizione per la scimmia cambiando il parametro rect con una data posizione. Se la nuova posizione sconfina i limiti dello schermo, annulla il movimento. Inoltre esegue una copia speculare dell'immagine usando il metodo pygame.transform.flip. Questo è un effetto grezzo che fa sembrare che la scimmia stia cambiando direzione.
Il metodo spin è chiamato quando la scimmia è nello stato “dizzy”. L'attributo dizzy viene usato per salvare l'ammontare corrente della rotazione. Quado la scimmia è ruotata del tutto (360 gradi) ripristina l'immagine alla versione non ruotata. Prima di chiamare la funzione transform.rotate, vedrete che il codice fa un riferimento locale alla funzione chiamata semplicemente “rotate”. Non c'è bisogno di farlo per questo esempio, viene fatto qui solo per mantenere più contenuta la lunghezza delle prossime righe. Notate che chiamando la funzione rotate, stiamo ruotando sempre l'immagine originale della scimmia. Quando ruota, c'è una certa perdita di qualità. Ruotate continuamente la stessa immagine e la qualità peggiorerà ogni volta. Inoltre, quando un'immagine viene ruotata la sua dimensione cambierà. Questo perchè ruotando gli angoli dell'immagine la renderanno più grande. Ci assicuriamo che il centro della nuova immagine corrisponda al centro della vecchia, così da non muoverla.
L'ultimo metodo è punched() che ci dice se la sprite sta entrando nello stato dizzy. Questo farà rotolare l'immagine della scimmia. Crea anche una copia dell'immagine corrente, chiamata “original”.
Inizializzare il tutto
Prima di poter fare qualcosa con pygame, ci assicuriamo che i moduli siano inizializzati. In questo caso apriremo una semplice una finestra. Adesso ci troviamo nella funzione main() del programma, che fa funzionare tutto.
pygame.init()
screen = pygame.display.set_mode((468, 60))
pygame.display.set_caption('Monkey Fever')
pygame.mouse.set_visible(0)
La prima riga per inizializzare pygame richiede un po' di sforzo da parte nostra. Controlla i moduli di pygame importati e prova a inizializzarli uno per uno. É possibile tornare indietro e controllare se i moduli hanno fallito l'inizializzazione, ma non ci disturbiamo qui. È anche possibile prendere maggiormente il controllo e inizializzare ogni modulo a mano. Questo tipo di controllo spesso non è necessario, ma è disponibile se lo desiderate.
Ora settiamo la modalità mostra grafica. Notate che il modulo pygame.display viene usato per controllare tutte le opzioni di visualizzazione. In questo caso stiamo solo richiedendo una finestra scarna e semplice. C'è un intero tutorial separato per settare la modalità grafica, ma se davvero non ci importa, pygame sceglierà per noi qualcosa che vada bene. Pygame sceglierà la migliore profondità di colore, visto che non ne abbiamo indicata una.
Per ultimo settiamo il titolo della finestra e disattiviamo il cursore del mouse per la nostra finestra. Molto semplice da fare, e adesso abbiamo una piccola finestra nera pronta per i nostri comandi. Usualmente il cursore mouse rimane visibile, quindi c'è davvero bisogno di disattivarlo.
Creare lo sfondo
Il nostro programma avrà un testo di sfondo. Sarebbe bello per noi creare una singola superficie per rappresentare lo sfondo e usare quella a ripetizione. Il primo passo è creare la superficie.
background = pygame.Surface(screen.get_size()) background = background.convert() background.fill((250, 250, 250))
Questo crea la nuova superficie per noi, della stessa dimensione della finestra. Notate la chiamata extra a convert() dopo aver creato la superficie. Il convert() senza argomenti ci assicura che la dimensione sia dello stesso formato della finestra, il che ci fornisce il risultato più rapido.
Riempiamo anche il nostro sfondo con bel colore bianco. Il metodo fill() prende come argomento una tupla RGB.
Mettere il testo nello sfondo, centrato
Adesso che abbiamo una superficie di sfondo, mettiamo il testo da visualizzarvi. Lo facciamo solo se vediamo che il modulo pygame.font è stato importato correttamente. Altrimenti saltiamo semplicemente questo passaggio.
if pygame.font:
font = pygame.font.Font(None, 36)
text = font.render("Pummel The Chimp, And Win $$$", 1, (10, 10, 10))
textpos = text.get_rect(centerx=background.get_width()/2)
background.blit(text, textpos)
Come potete vedere, ci sono un paio di cose da fare per ottenere questo. Per prima cosa creiamo l'oggetto font e lo visualizziamo sulla superficie. Cerchiamo poi il centro di questa nuova superficie e lo incolliamo sullo sfondo.
Il carattere è creato con il costruttore del modulo Font(). Di solito passerete il nome di un carattere esistente a questa funzione, ma anche None, che usa il font predefinito. Il metodo costruttore necessita anche della dimensione del carattere da visualizzare.
Adesso visualizziamo il font sulla nuova superficie. La funzione render crea una nuova superficie della misura appropriata per il nostro testo. In questo caso diciamo anche un testo con anti alias (per un bell'effetto smooth) e di usare un grigio scuro.
Poi abbiamo bisogno di trovare la posizione centrale sul nostro schermo. Creiamo un oggetto rect delle dimensioni del testo, che ci permette di assegnarlo facilmente al centro scermo.
Infine usiamo blit (un copia incolla) per incollare il testo sull'immagine di sfondo.
Visualizzare lo sfondo mentre finisce l'installazione
Abbiamo ancora una finestra nera sullo schermo. Visualizziamo il nostro sfondo mentre aspettiamo che le altre risorse vengano caricate.
screen.blit(background, (0, 0)) pygame.display.flip()
Questo copierà in nostro sfondo sullo schermo. Il blit si spiga da solo, ma cosa è questo continuo uso di flip?
°In pygame, i cambiamenti non sono subito visibili sullo schermo. Di norma, uno schermo va aggiornato nelle aree che sono cambiate per far sì che si vedano. With double buffered displays the display must be swapped (or flipped) for the changes to become visible. In questo caso la funzione flip() lavora bene perchè gestisce semplicemente la finestra intera e entrambe singlebuffered and doublebufferes surfaces.
Preparare l'oggetto Game
Qui creiamo tutti gli oggetti che il gioco deve avere.
whiff_sound = load_sound('whiff.wav')
punch_sound = load_sound('punch.wav')
chimp = Chimp()
fist = Fist()
allsprites = pygame.sprite.RenderPlain((fist, chimp))
clock = pygame.time.Clock()
Attualmente usiamo un gruppo speciale di sprite chiamato RenderPlain. Questo gruppo può disegnare tutte le sprite che contiene sullo schermo. Viene chiamato RenderPlain perché ci sono anche gruppi più avanzati di Render. Ma per il nostro gioco, necessitiamo solo di una visualizzazione semplice. Creiamo il gruppo chiamato “allsprites” passando una lista con tutti gli sprites che dovrebbero appartenere al gruppo. Potremmo aggiungere o rimuovere dopo le sprites da questo gruppo, ma non ne avremo bisogno qui.
L'oggetto clock che creiamo sarà usato per aiutare il controllo del framerate del nostro gioco. Lo useremo nel mainloop del nostro gioco per essere sicuri che non giri troppo velocemente.
MainLoop
Non molto qui, solo un ciclo infinito.
while 1:
clock.tick(60)
Tutti i giochi girano con una specie di loop. L'ordine solito di cose è controllare lo stato degli input dell'utente, muovere e aggiornare tutti gli oggetti e poi visualizzarli a schermo. Vedrete che questo esempio non è differente.
Facciamo anche una chiamata all'oggetto orologio, che ci assicura che il nostro gioco non compia più di 60 cicli al secondo, e che non super quindi i 60 Hz.
Gestire tutti gli eventi input
Questo è un esempio estremamente semplice di coda degli eventi.
for event in pygame.event.get():
if event.type == QUIT:
return
elif event.type == KEYDOWN and event.key == K_ESCAPE:
return
elif event.type == MOUSEBUTTONDOWN:
if fist.punch(chimp):
punch_sound.play() #punch
chimp.punched()
else:
whiff_sound.play() #miss
elif event.type == MOUSEBUTTONUP:
fist.unpunch()
Per prima cosa otteniamo tutti gli eventi disponibili da pygame e cicliamo ognuno di essi. I primi due controlli vedono se l'utente è uscito dal gioco, o ha premuto il tasto Esc. In questi casi ritorniamo dalla funzione main() e il gioco termina.
Poi controlliamo per vedere se il bottone del mouse è stato premuto o rilasciato. Se il bottone è stato premuto, chiediamo se il primo oggetto ha colpito la scimmia. Riproduciamo il suono appropriato e se la scimmia è stata colpita, gli diciamo di iniziare a girare (chiamando l metodo punched()).
Aggiornare le sprites
allsprites.update()
I gruppi di sprite hanno un metodo update(), che semplicemente chiama l'update per tutte le sprites che contiene. Ogni oggetto si muoverà, in base allo stato in cui si trova. La scimmia si muoverà nella direzione opposta, o inizierà a girare se colpita.
== Disegnare l'intera scena ==
Adesso che tutti gli elementi sono nel posto giusto è tempo di disegnarli.
screen.blit(background, (0, 0)) allsprites.draw(screen) pygame.dis
La prima chiamata blit disegnerà lo sfondo sull'intero schermo. Questo cancella tutto quello che abbiamo visto dai frame precedenti (visibilmente inefficiente, ma sufficiente per questo gioco). Poi chiamaiamo il metodo draw() del contenitore di sprites. Dal momento che il contenitore di sprites è una istanza del gruppo “DrawPlain”, sa come disegnare la nostre sprites. Per ultima cosa, we flip() the contents of pygame's software double buffer to the screen. Questo rende tutto visibile nello stesso momento.
Game over
L'utente è uscito, tempo di pulire.
Pulire il gioco corrente in pygame è semplicissimo. Infatti visto che tutte le variabili sono automaticamente distrutte, non dobbiamo fare nulla.