16: WORKSHOP - ALIENS (SECONDA PARTE)

I NEMICI

Nella lezione precedente (vedi qui) abbiamo realizzato il movimento della nostra astronave e dei proiettili. Introduciamo ora le astronavi aliene alle quali dovremo sparare. Ci accorgiamo che anche per i nemici dovremo usare la stessa tecnica usata per i proiettili, creandoli e distruggendoli nel ciclo principale. A differenza dei proiettili, però, i nemici dovranno comparire ad intervalli casuali e in posizioni casuali, scendendo dall'alto verso il basso. Saranno quindi necessarie le funzioni del modulo random.

ESERCIZIO 16.1: Operazioni preliminari

Dovremo creare un nuovo nemico ogni volta che il timer scatta: sarà quindi necessario monitorare nel Ciclo degli eventi anche l'evento TIMERSHOT.

ESERCIZIO 16.2: Le astronavi aliene Anche questa volta il posizionamento è la parte più difficile. Il nemico dovrà inizialmente essere in alto, fuori dallo schermo (possiamo quindi assegnare al lato basso del Rect la coordinata y = -1); la coordinata x deve invece essere casuale, ottenuta di nuovo con la funzione randrange(). L'astronave dovrà comunque essere situata entro lo schermo, quindi dovete calcolare i due parametri (minimo e massimo) da scrivere nella funzione. Potete aiutarvi con questo disegno.
Rect alieni

Infine facciamo muovere i nostri nemici verso il basso.

ESERCIZIO 16.3: Nella sezione Logica del gioco aggiungete un ciclo for identico a quello usato per muovere i proiettili: le astronavi si devono muovere verso il basso di 2 pixel per ciclo, e devono essere eliminate dalla lista aliens quando spariscono sotto il lato inferiore di screen. Nel ciclo potete usare la variabile alien come contatore.

Ecco il programma fino a questo punto. Ora si dovrebbero muovere l'astronave, i proiettili e le astronavi aliene.


import pygame, random
from pygame.locals import *
from os.path import join

# --- Inizializzazione
pygame.init()
screen = pygame.display.set_mode((800, 600))
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()

# --- 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"))

# --- 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 = (320, 500)

# --- Inizio del gioco
pygame.mixer.music.play(-1)
TIMERSHOT = pygame.event.custom_type()
pygame.time.set_timer(TIMERSHOT, random.randrange(400, 1501))

# --- 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:
            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, screen.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 < screen.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 > screen.get_height():
            alien.kill()
		
    # --- Aggiornamento dello schermo
    screen.blit(surf_back, (0, 0))
    all_sprites.draw(screen)  
    pygame.display.flip()

    clk.tick(30)

# --- Uscita
pygame.quit()

DISTRUGGIAMO I NEMICI

Quando centriamo i nemici essi ovviamente devono sparire insieme al proiettile, con il rumore di un'esplosione. Grazie alle funzioni di pygame che controllano le collisioni tra Sprite tutto questo diventa semplicissimo. Possiamo usare infatti la pygame.sprite.groupcollide() (vedi qui), controllando se qualche Sprite del Group bullets collide con qualche Sprite del Group aliens. Inoltre, impostando a True entrambi i parmetri dokill possiamo fare in modo che pygame cancelli automaticamente gli Sprite in collisione.

ESERCIZIO 16.4: Nella sezione Logica del gioco, dopo aver mosso tutti gli oggetti, inserite queste righe (indentate correttamente!)

    if pygame.sprite.groupcollide(bullets, aliens, True, True):
        sound_expl.play()
   

Come abbiamo detto, la groupcollide() restituisce un dizionario di Python contenente tutte le collisioni trovate. Se non ci sono collisioni restituirà un dizionario vuoto (una sequenza vuota, sia essa string, lista ,tuple, ecc, viene sempre considerata equivalente al valore booleano False in Python).

FINE DEL GIOCO

Infine dobbiamo far finire il nostro gioco: possiamo stabilire che avremo perso se:

Se siete arrivati sin qui questi due comportamenti non dovrebbero essere difficili da implementare: ricordiamo che per terminare il gioco basta porre a True la variabile done che controlla il ciclo principale.

ESERCIZIO 16.5: Abbiamo già scritto una riga che controlla se un alieno è uscito dallo schermo, per eliminarlo dal Group aliens; trovate questa riga e modificatela in modo che (anzichè eliminare lo Sprite dal Group) il ciclo principale si interrompa.
ESERCIZIO 16.6: Per la collisione tra la nostra astronave ed uno degli alieni possiamo usare la pygame.sprite.spritecollide(), indicando come primo parametro lo Sprite spr_ship, come secondo il Group aliens e come terzo True, in modo che l'astronave nemica sia cancellata dallo schermo. In entrambi i casi fate anche suonare il Sound sound_end.

Potete fare una prova, ma vedrete che quando perderete non sentirete il suono finale, perchè il programma esce dal ciclo principale e termina immediatamente, con un effetto piuttosto antipatico. Possiamo però ovviare a questo inconveniente facendo aspettare pygame finchè tutti i suoni non sono terminati:

ESERCIZIO 16.7: Modificate la sezione Uscita in questo modo (i numeri di riga si riferiscono all'ultimo schema):

.   .   .
# --- Uscita
pygame.mixer.music.stop()
while pygame.mixer.get_busy():
    clk.tick(30)
pygame.quit()
   
Nella #84 fermiamo la colonna sonora. Il ciclo while nella #85 chiama la funzione get_busy() del sottomodulo mixer, che restituisce True se qualche suono del mixer è in esecuzione e False altrimenti. In pratica questo ciclo continua a far correre l'orologio clk (nella #86) fino a quando tutti i suoni sono finiti, quindi esce e Python esegue la quit() che ferma pygame.

ESERCIZIO 16.8: Date gli ultimi ritocchi: nella sezione Risorse sonore potete modificare il volume dei vari suoni, che sono un po' troppo forti rispetto alla colonna sonora (vedi qui). Inoltre potete facilmente capire che, così com'è, il gioco si presta alla facile strategia di sparare continuamente proiettili muovendo a destra e sinistra la nave. Potete limitare il numero massimo di proiettili contemporaneamente sullo schermo: trovate nel ciclo degli eventi l'if che determina lo sparo del proiettile e aggiungete con un and la terza condizione che la lunghezza del Group bullets sia minore di 8. In questo modo quando sono presenti 8 proiettili sullo schermo la nostra nave smetterà di sparare finchè uno di essi non sia uscito dallo schermo o esploso contro un alieno, rendendo il gioco più interessante.
SOLUZIONI

Bene, abbiamo finito. Potreste lamentarvi del fatto che non c'è un punteggio o qualche scritta iniziale o finale. Questo sarà l'argomento della prossima lezione.

Fine della lezione