In tutti gli esempi realizzati finora abbiamo sempre fatto coincidere l'area di gioco con l'intera Surface screen
.
Nella realtà le cose vanno, di solito, diversamente: solo una parte della finestra principale sarà dedicata al gioco vero e proprio,
e ci saranno altre aree per le info (punteggio, numero di vite...), menù di scelta, etc. Ora che abbiamo imparato a scrivere sulla
finestra di pygame possiamo realizzare anche noi una cosa simile.
Per dividere lo schermo in sottofinestre useremo più Surface secondarie, disegnando gli Sprite o le
scritte su di esse, e facendo alla fine la blit()
delle Surface secondarie su quella principale screen
.
Questa tecnica è piuttosto laboriosa per il computer (che deve fare molte più blit()
), ma è molto comoda per noi,
perchè quando disegniamo su una Surface possiamo usare le coordinate relative ad essa (l'angolo in alto a
sinistra della Surface ha coordinate (0, 0), etc.) senza bisogno di sapere dove quella Surface sarà poi posizionata nella finestra
principale.
In questo modo, se volessimo cambiare la disposizione delle nostre sottofinestre, dovremmo modificare solo le poche righe di codice
che posizionano le Surface sullo schermo, mentre il disegno su ogni Surface rimarrebbe immutato. Naturalmente sarebbe
anche possibile continuare a disegnare tutto direttamente su screen
, cosa che affaticherebbe di meno il computer ma
sarebbe più complicata per noi. Potrete provarci quando diventerete esperti programmatori in pygame.
Modifichiamo ora il nostro programma aliens.py in modo da suddividere la finestra in un'area di gioco, un'area info ed un titolo. Ecco uno schema della suddivisione
Facciamo alcune osservazioni:
surf_title
conterrà il logo del gioco, e sarà statica: potremo quindi disegnarla una sola volta
prima del ciclo principale.surf_play
e surf_info
serviranno rispettivmente per l'area di gioco e per il
punteggio. Esse dovranno essere aggiornate ad ogni ciclo facendo la blit()
su screen
.play_topleft
e info_topleft
.screen
dovrà avere dimensioni maggiori dei soliti 800x600 pixel: questo può portare dei
problemi a seconda delle dimensioni dello schermo del vostro computer, perchè la finestra potrebbe non essere completamente
visibile. Ci occuperemo della cosa nel primo esercizio.Riaprite il vecchio programma (con le modifiche fatte negli esercizi della Lezione 16), salvatelo con il nuovo nome e cominciate a modificarlo:
pygame.display.set_mode()
apprendiamo che in realtà il primo parametro size
ha il valore di default
(0, 0). Se omettiamo il parametro otteniamo una finestra delle stesse dimensioni dello schermo del nostro computer, che però è
senza la barra superiore con il titolo ed il pulsante per chiuderla. Cambiate quindi l'istruzione: se durante le vostre prove
il programma dovesse bloccarsi e doveste chiuderlo potete passare alla finestra di IDLE (in Windows dovete premere <ALT> + <TAB>) e
scrivere pygame.quit()
.
surf_title |
Caricate il file grafico "Sprites/Aliens_text.png" (usate la convert_alpha() per attivare la
trasparenza) |
surf_play |
Create, con il costruttore pygame.Surface() , una Surface con le stesse dimensioni di
surf_back (per trovarle entrambe con una sola funzione potete usare il metodo get_size()
che restituisce direttamente la dupla (x, y) (vedi la
documentazione ufficiale) |
surf_info |
Create, con il costruttore, una Surface larga 250 pixel ed alta come la surf_play |
fnt |
Caricate, con la funzione SysFont() , il font Arial da 48 pt, assegnandolo alla variabile fnt |
In queste ultime lezioni cercheremo di adottare uno stile un po' più professionale eliminando il più possibile i cosiddetti "magic numbers" sostituendoli con delle costanti (vedi l'approfondimento qui), anche a costo di scrivere qualche riga di codice in più. Create quindi un'altra sezione Costanti tra le sezioni Risorse sonore e Altre variabili con un commento simile agli altri.
Nome | Valore | Significato |
---|---|---|
TIMERSHOT |
. . . | Spostate qui la riga che avevate già scritto nella sezione Inizio del gioco |
BACKCOLOR |
(10, 160, 10) |
Il colore dello sfondo (verde scuro) |
TEXTCOLOR |
(255, 255, 40) |
Il colore del testo (giallo) |
SHIP_TOPLEFT |
(320, 500) |
La posizione iniziale della nostra nave. Modificate la riga nella sezione Altre variabili che assegna la posizione iniziale della nostra nave |
PLAY_TOPLEFT |
tuple di due interi |
Il punto che useremo come secondo parametro nella blit() per posizionare surf_text |
INFO_TOPLEFT |
tuple di due interi |
Il punto che useremo come secondo parametro nella blit() per posizionare surf_info
|
PLAY_TOPLEFT
e INFO_TOPLEFT
guardando la
figura sopra (ricordate che vanno tra parentesi perchè sono delle tuple di Python); per la larghezza e l'altezza delle varie Surface
usate i metodi get_width()
e get_height()
in modo da rendere indipendenti le istruzioni dalla larghezza
delle Surface (che potreste anche non conoscere).
. . .
# --- Aggiornamento dello schermo
surf_play.blit(surf_back, (0, 0))
all_sprites.draw(surf_play)
screen.blit(surf_play, PLAY_TOPLEFT)
screen.blit(surf_info, INFO_TOPLEFT)
pygame.display.flip()
. . .
Nelle #3 e #4 disegniamo lo sfondo e gli Sprite non più su
screen
ma sulla surf_play
. Nella #5 disegniamo l'intera Surface
surf_play
sulla finestra principale, usando la variabile PLAY_TOPLEFT
come angolo in alto a sinistra.
Se volessimo cambiare il posizionamento della Surface ci basterebbe cambiare il valore di PLAY_TOPLEFT
, lasciando
tutto il resto del codice invariato. Analogamente facciamo nella #6 per la Surface surf_info
,
che per il momento è inutilizzata.
A questo punto potete lanciare il programma, che non dovrebbe andare in crash. Ma ci sono molte cose che non vanno: tutto lo schermo è nero, vediamo alcune astronavi nemiche "disegnate a metà" a destra, la nostra astronave può uscire dall'area di gioco, e a volte il gioco finisce improvvisamente anche se noi abbiamo eliminato tutte le navi nemiche!
. . .
screen.fill(BACKCOLOR)
rect_title = surf_title.get_rect()
rect_title.midtop = (surf_play.get_width() // 2 + 20, 20)
screen.blit(surf_title, rect_title)
. . .
La #2 colora tutto lo schermo di verde. Le altre tre righe servono per posizionare e disegnare la
surf_title
che contiene il logo del gioco. Durante il ciclo principale non ci sarà più bisogno di aggiornare
queste aree.
Ora, se lanciamo il programma, l'effetto è più gradevole: vediamo nettamente le tre Surface (la surf_info
è
ancora tutta nera), ma il gioco non funziona ancora. Il motivo è semplice: nel ciclo principale si "dava per scontato" che
l'area di gioco coincidesse con tutta la Surface screen
. Ora invece screen
è diventata più grande,
mentre le dimensioni dell'area di gioco sono rimaste uguali (e coincidono con quelle di surf_play
). Avremo
perciò astronavi aliene che sono generate fuori della surf_play
e che noi non vediamo.
screen
, e sostituitele con
istruzioni che usano larghezza ed altezza di surf_play
. Questo è quello che succede quando
un programma non è ben progettato dall'inizio: introdurre una nuova funzionalità comporta la modifica di molte righe di programma
separate tra loro, con una difficile ricerca. I programmatori esperti (presto lo sarete anche voi!) sanno come evitare queste
situazioni. Io ho contato 3 righe da modificare, riuscite a trovarle anche voi?
Bene, ora il gioco dovrebbe funzionare correttamente, senza navi che escono dallo schermo. Rimane da implementare il punteggio
e da disegnarlo sulla Surface surf_info
. Ci sarà naturalmente bisogno di una variabile score
per
memorizzare il punteggio; possiamo stabilire che per ogni nemico abbattuto esso aumenti di 10 punti.
score
a 0if
che controlla se un proiettile ha
colpito un nemico (lo riconoscerete perchè viene fatto partire il Sound sound_expl
). Nel corpo
dell'if
aggiungete una riga che incrementi score
di 10 (usate l'operatore +=
)
. . .
# --- Aggiornamento dello schermo
surf_play.blit(surf_back, (0, 0))
all_sprites.draw(surf_play)
screen.blit(surf_play, PLAY_TOPLEFT)
surf_info.fill(BACKCOLOR)
surf_info.blit(fnt.render("Score: " + str(score), True, TEXTCOLOR), (0, 0))
screen.blit(surf_info, INFO_TOPLEFT)
pygame.display.flip()
. . .
Abbiamo aggiunto solo due righe: nella #6 coloriamo la surf_info
dello stesso colore
dello sfondo, mentre nella #7 scriviamo il punteggio in giallo (andate a ripassare i parametri della
render()
qui).
Vi faccio notare che il colore con cui coloriamo lo sfondo di surf_info
nella riga #6
dell'esempio precedente deve essere lo stesso con cui all'inizio coloriamo tutta la finestra screen
(nell'Esercizio 19.5): usando la costante BACKCOLOR
questo si ottiene
automaticamente senza che noi dobbiamo preoccuparcene nel caso volessimo cambiare colore.
A questo punto abbiamo finito questa terza parte, vi lascio il programma realizzato da me per confrontarlo con il vostro. Nel prossimo capitolo aggiungeremo altri effetti grafici.
import pygame, random
from pygame.locals import *
from os.path import join
# --- Inizializzazione
pygame.init()
screen = pygame.display.set_mode()
pygame.display.set_caption("Aliens")
clk = pygame.time.Clock()
# --- Risorse grafiche
surf_back = pygame.image.load(join("Backgrounds", "Space.png")).convert()
surf_ship = pygame.image.load(join("Sprites", "SpaceArt", "player.png")).convert_alpha()
surf_alien = pygame.image.load(join("Sprites", "SpaceArt", "enemyShip.png")).convert_alpha()
surf_bullet = pygame.image.load(join("Sprites", "SpaceArt", "laserGreen.png")).convert_alpha()
surf_title = pygame.image.load(join("Sprites", "Aliens_text.png")).convert_alpha()
surf_play = pygame.Surface(surf_back.get_size())
surf_info = pygame.Surface((250, surf_play.get_height()))
fnt = pygame.font.SysFont("Arial", 48)
# --- Risorse sonore
sound_laser = pygame.mixer.Sound(join("Effects","smc-wwvi", "synthetic_laser.ogg"))
sound_expl = pygame.mixer.Sound(join("Effects", "smc-wwvi", "synthetic_gunshot_2.ogg"))
sound_end = pygame.mixer.Sound(join("Effects", "smc-wwvi", "big_explosion.ogg"))
pygame.mixer.music.load(join("Music", "SoundImage", "Techno-Gameplay_Looping.ogg"))
# --- Costanti
TIMERSHOT = pygame.event.custom_type()
BACKCOLOR = (10, 160, 10)
TEXTCOLOR = (255, 255, 40)
SHIP_TOPLEFT = (320, 500)
PLAY_TOPLEFT = (20, 40 + surf_title.get_height())
INFO_TOPLEFT = (40 + surf_play.get_width(), 40 + surf_title.get_height())
# --- Altre variabili
pressed = None
all_sprites = pygame.sprite.Group()
bullets = pygame.sprite.Group()
aliens = pygame.sprite.Group()
spr_ship = pygame.sprite.Sprite(all_sprites)
spr_ship.image = surf_ship
spr_ship.rect = surf_ship.get_rect()
spr_ship.rect.topleft = SHIP_TOPLEFT
score = 0
# --- Inizio del gioco
pygame.mixer.music.play(-1)
pygame.time.set_timer(TIMERSHOT, random.randrange(400, 1501))
screen.fill(BACKCOLOR)
rect_title = surf_title.get_rect()
rect_title.midtop = (surf_play.get_width() // 2 + 20, 20)
screen.blit(surf_title, rect_title)
# --- 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
elif ev.type == KEYDOWN and ev.key == K_SPACE and len(bullets) < 8:
bullet = pygame.sprite.Sprite(all_sprites, bullets)
bullet.image = surf_bullet
bullet.rect = surf_bullet.get_rect()
bullet.rect.midbottom = spr_ship.rect.midtop
sound_laser.play()
elif ev.type == TIMERSHOT:
pygame.time.set_timer(TIMERSHOT, random.randrange(400, 1501))
alien = pygame.sprite.Sprite(all_sprites, aliens)
alien.image = surf_alien
alien.rect = surf_alien.get_rect()
alien.rect.bottomleft = (random.randrange(0, surf_play.get_width() - alien.rect.width + 1), -1)
# --- Logica del gioco
if pressed == K_LEFT and spr_ship.rect.left > 0:
spr_ship.rect.x -= 5
elif pressed == K_RIGHT and spr_ship.rect.right < surf_play.get_width():
spr_ship.rect.x += 5
for bullet in bullets:
bullet.rect.y -= 4
if bullet.rect.bottom < 0:
bullet.kill()
for alien in aliens:
alien.rect.y += 2
if alien.rect.top > surf_play.get_height():
sound_end.play()
done = True
if pygame.sprite.groupcollide(bullets, aliens, True, True):
score += 10
sound_expl.play()
if pygame.sprite.spritecollide(spr_ship, aliens, True):
sound_end.play()
done = True
# --- Aggiornamento dello schermo
surf_play.blit(surf_back, (0, 0))
all_sprites.draw(surf_play)
screen.blit(surf_play, PLAY_TOPLEFT)
surf_info.fill(BACKCOLOR)
surf_info.blit(fnt.render("Score: " + str(score), True, TEXTCOLOR), (0, 0))
screen.blit(surf_info, INFO_TOPLEFT)
pygame.display.flip()
clk.tick(30)
# --- Uscita
pygame.mixer.music.stop()
while pygame.mixer.get_busy():
clk.tick(30)
pygame.quit()
Fine della lezione