Cerchiamo ora di applicare tutto quello che abbiamo imparato in modo da ottenere qualche programma più completo. In questa lezione realizzeremo il classico effetto della pallina e della racchetta. Potete partire dallo "scheletro" di programma che ho preparato qui sotto: l'ho diviso in sezioni logiche mediante i commenti in modo da dare un ordine a quello che dobbiammo scrivere.
Aprite un nuovo file nel vostro editor e copiate ed incollate il programma qui sotto.
import pygame
from pygame.locals import *
from os.path import join
# --- Inizializzazione
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("")
clk = pygame.time.Clock()
# --- Risorse grafiche
# --- Risorse sonore
# --- Altre variabili
# --- Ciclo principale
done = False
while not done:
# --- Ciclo degli eventi
for ev in pygame.event.get():
if ev.type == pygame.QUIT:
done = True
# --- Logica del gioco
# --- Aggiornamento dello schermo
pygame.display.flip()
clk.tick(30)
# --- Uscita
pygame.quit()
Per prima cosa modifichiamo la caption della nostra finestra in "Pong". Carichiamo poi le risorse grafiche e sonore.
surf_ball
e surf_bar
(ricordo che con i
file .png conviene usare la convert_alpha()
perchè potrebbero esserci problemi con la trasparenza). Dato che dovremo
muoverle e verificarne le collisioni, ricaviamo da esse i rispettivi Rect, assegnandoli alle variabili rect_ball
e
rect_bar
.sound_bounce
e
sound_hit
.
Potete ora lanciare il programma, almeno per sincerarvi di non aver commesso errori (state attenti ad indicare correttamente i percorsi dei files o otterrete un errore Couldn't open file). Ora passiamo a disegnare le nostre immagini.
topleft
nel punto (300, 200); fate lo stesso per la racchetta nel punto (300, 560)screen
con il
colore (60, 160, 60) (un verde chiaro) e facciamo il blit()
della pallina e della racchetta su di esso.
Se tutto funziona dovremmo vedere la pallina e la racchetta. La pallina però è decisamente troppo piccola rispetto alla racchetta. Cerchiamo di rimediare:
scale2x()
del
sottomodulo transform
: vedi qui.Dopo queste modifiche il programma dovrebbe essere più o meno così (naturalmente è possibile che la posizione di qualche riga sia diversa). Lanciandolo si dovrebbero vedere la pallina e la racchetta fermi.
import pygame
from pygame.locals import *
from os.path import join
# --- Inizializzazione
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Pong")
clk = pygame.time.Clock()
# --- Risorse grafiche
surf_ball = pygame.image.load(join("Sprites", "Pong", "ball_blue.png")).convert_alpha()
surf_ball = pygame.transform.scale2x(surf_ball)
rect_ball = surf_ball.get_rect()
rect_ball.topleft = (300, 200)
surf_bar = pygame.image.load(join("Sprites", "Pong", "bar2_blue.png")).convert_alpha()
rect_bar = surf_bar.get_rect()
rect_bar.topleft = (300, 560)
# --- Risorse sonore
sound_bounce = pygame.mixer.Sound(join("Effects", "SFX", "beep_8.wav"))
sound_hit = pygame.mixer.Sound(join("Effects", "SFX", "hit_1.wav"))
# --- Altre variabili
# --- Ciclo principale
done = False
while not done:
# --- Ciclo degli eventi
for ev in pygame.event.get():
if ev.type == QUIT:
done = True
# --- Logica del gioco
# --- Aggiornamento dello schermo
screen.fill((60, 160, 60))
screen.blit(surf_ball, rect_ball)
screen.blit(surf_bar, rect_bar)
pygame.display.flip()
clk.tick(30)
# --- Uscita
pygame.quit()
Muovere la pallina non dovrebbe essere molto difficile, in quanto abbiamo già visto come fare nelle lezioni precedenti. Potete partire dal programma panda.py che potete rivedere qui.
vel_ball
assegnandole
il valore [3, 3].sound_bounce
ad ogni rimbalzo.Ora vogliamo muovere la racchetta con i due tasti freccia a destra e a sinistra. Una prima semplice idea
potrebbe essere questa: ci basta monitorare l'evento KEYDOWN; quando accade verifichiamo tramite l'attributo key
dell'Event se è stata premuta la freccia a sinistra (K_LEFT) o a destra (K_RIGHT). Nel primo caso spostiamo la racchetta 4 pixel a
sinistra, nel secondo a destra.
key
.
Se non avete fatto errori la racchetta si dovrebbe muovere, ma la giocabilità è piuttosto scarsa: dobbiamo continuamente premere e rialzare i tasti freccia, facciamo molta fatica e la racchetta si muove lentamente. Sarebbe meglio se premendo un tasto la racchetta iniziasse a muoversi, e si fermasse solo quando lo rialziamo.
Ottenere questo comportamento è un po' più difficile. Consideriamo che pygame ci mette a disposizione due eventi: KEYDOWN e KEYUP. In altre parole riconosce quando un tasto si abbassa e quando si alza, ma nell'intervallo tra il KEYDOWN e il KEYUP non riceve nessun evento: quindi il programma non sa, in un certo momento, quali tasti sono abbassati, e dovrà "ricordare" mediante una variabile se in quel momento qualche tasto è premuto.
Dobbiamo perciò dividere il nostro compito in due parti:
pressed
impostandola a None
(lo standard di Python per indicare "nessun valore").
. . .
# --- Ciclo degli eventi
for ev in pygame.event.get():
if ev.type == QUIT:
done = True
elif ev.type == KEYDOWN and ev.key in (K_LEFT, K_RIGHT):
pressed = ev.key
elif ev.type == KEYUP and ev.key == pressed:
pressed = None
. . .
pressed
è uguale a K_LEFT o K_RIGHTIl ciclo degli eventi che abbiamo scritto va analizzato attentamente (i numeri di riga si riferiscono allo schema precedente, se avete aggiunto delle righe i vostri dovrebbero essere diversi):
elif
nella #34 è vero se l'evento ev
è un KEYDOWN e se il tasto è la
freccia a destra o a sinistra. Notate che avremmo anche potuto scrivere:
. . .
elif ev.type == KEYDOWN:
if ev.key == K_LEFT or ev.key == K_RIGHT:
. . .
ma la nostra unica riga è più compatta. Due if
consecutivi si possono unire tramite l'operatore booleano
and
, e l'operatore in
permette di confrontare facilmente una variabile con più valori mettendoli in una
lista o tuple.if
è vero assegniamo nella #35 a pressed
il valore del tasto
premuto.elif
nella #36 è vero se stiamo rilasciando lo stesso tasto che era già stato premuto.pressed
a None
per indicare che
nessuna delle due frecce è premuta.Alla fine del ciclo degli eventi pressed
conterrà quindi K_LEFT o K_RIGHT (se è premuto un tasto
freccia) oppure None
(non è premuto nessun tasto), e quindi il nostro programma ricorderà in ogni momento quale
tasto è premuto. Potremo quindi muovere (dopo il ciclo, nella sezione Logica del gioco)
la racchetta come sappiamo già fare (aumentate/diminuite la x del suo Rect di 4 pixel)
Se non avete commesso errori la racchetta dovrebbe ora muoversi nel modo che noi vogliamo. Qualcuno potrebbe pensare che nella
#36 basterebbe scrivere elif event.type == KEYUP:
(cioè basterebbe controllare se un tasto è stato
rilasciato). Questo funziona finchè premiamo un tasto per volta, ma nei videogiochi si premono spesso più tasti insieme: provate a
modificare la #36 come vi ho detto, premete più tasti insieme e guardate cosa succede.
Il programma a questo punto dovrebbe essere più o meno così (vi mostro solo la parte modificata):
. . .
# --- Altre variabili
vel_ball = [3, 3]
pressed = None
# --- Ciclo principale
done = False
while not done:
# --- Ciclo degli eventi
for ev in pygame.event.get():
if ev.type == pygame.QUIT:
done = True
elif ev.type == KEYDOWN and ev.key in (K_LEFT, K_RIGHT):
pressed = ev.key
elif ev.type == KEYUP and ev.key == pressed:
pressed = None
# --- Logica del gioco
if rect_ball.left < 0 or rect_ball.right > screen.get_width():
vel_ball[0] *= -1
sound_bounce.play()
if rect_ball.top < 0 or rect_ball.bottom > screen.get_height():
vel_ball[1] *= -1
sound_bounce.play()
rect_ball.x += vel_ball[0]
rect_ball.y += vel_ball[1]
if pressed == K_LEFT:
rect_bar.x -= 4
elif pressed == K_RIGHT:
rect_bar.x += 4
. . .
and
una ulteriore condizione: nella #50 dobbiamo spostare a sinistra la racchetta se
pressed == K_LEFT
e la x del lato sinistro del suo rect è maggiore di 0. Modificate in maniera
analoga la #52.
Infine modifichiamo ancora la sezione Logica del gioco in modo da far rimbalzare la pallina sulla racchetta.
if
in due blocchi distinti (eliminando l'or
).
done
uguale a
True per uscire dal ciclo principale.Lanciate il programma: la pallina dovrebbe tristemente uscire quasi subito dalla parte in basso: dobbiamo quindi aggiungere subito
il rimbalzo sulla racchetta. Ma questo è molto facile se usiamo il metodo colliderect()
(vedi
qui) dell'oggetto Rect. Basta verificare con un if
se i due Rect
rect_bar
e rect_ball
collidono. Ricordate che dovete usare la sintassi per gli oggetti:
uno dei due Rect va scritto a sinistra del punto, mentre l'altro va scritto come argomento della funzione.
sound_hit
.
Adesso la pallina dovrebbe rimbalzare, dando finalmente un senso compiuto ai nostri sforzi: c'è però ancora un piccolo baco nel nostro programma, una situazione che non avevamo previsto. Provate a colpire la pallina lateralmente (cioè a farla passare e poi muovere la racchetta sopra di essa mentre è ancora in campo): cosa succede? I due Rect si sovrappongono e quindi la velocità viene invertita. Ma nel ciclo successivo i due Rect saranno ancora sovrapposti e quindi si innescherà una serie di rimbalzi su e giù che noi non vogliamo. Una soluzione piuttosto semplice, simile a quella che abbiamo già visto qui, è di far rimbalzare la pallina solo se i due Rect collidono e la sua velocità verticale è positiva (cioè sta andando verso il basso).
and
un'ulteriore condizione
all'if
che controlla se la pallina deve rimbalzare sulla racchetta.
SOLUZIONI
A questo punto abbiamo un programma molto semplice ma funzionante (ecco qui la mia versione finale). Per il momento ci fermiamo qui, vi do comunque qualche suggerimento nel caso vgliate sperimentare un po' per conto vostro:
pygame.quit()
, una scritta "GAME OVER"
usando qualcuno degli effetti che abbiamo visto qui (caricate uno dei files che trovate
nella cartella Sprites, chiamate la funzione con i parametri appropriati ed inserite poi 2 secondi di pausa prima
di chiudere il programma).Fine della lezione