Realizzare Snake Game [parte 4]

Oggi, finalmente, vedremo qualcosa muoversi… è giunto il momento di realizzare la Classe principale del gioco: si chiamerà SnakeGame.

Per funzionare, questa Classe avrà bisogno di tutto quello che abbiamo fatto fino ad ora:

  • La Classe Player per creare il Serpente.
  • La Classe Wall per creare i bordi del quadro di gioco.
  • La Classe Cherry per creare il cibo del Serpente.

A loro volta queste tre Classi hanno bisogno della Classe Square per disegnare la propria grafica.

Se non avete già scritto le quattro Classi che ci servono dovrete prima leggere le lezioni precedenti:

  1. Realizzare Snake Game Parte 1
  2. Realizzare Snake Game Parte 2
  3. Realizzare Snake Game Parte 3

Iniziamo subito a scrivere quello che abbiamo imparato fino a questo momento:

package snake{
    import flash.display.Sprite;
    import flash.utils.Timer;
    import flash.events.*;
    import flash.text.TextField;
    import snake.*;
    public class SnakeGame extends Sprite {
    }
}

Come le precedenti questa Classe appartiene al package snake ed estende la CLasse Sprite. Vediamo cosa stiamo importando:

    import flash.display.Sprite;
    import flash.utils.Timer;
    import flash.events.*;
    import flash.text.TextField;
    import snake.*;
  • La Classe Sprite ci serve perché dobbiamo estenderla.
  • La Classe Timer è utile per gestire il “tempo di gioco”, così da poter gestire i movimenti del Serpente ad intervalli di tempo stabiliti.
  • Tutte le Classi del Package events, perché ne utilizzaremo qualcuna ma non sappiamo ancora quali (l’asterisco indica che stiamo importando tutto il contenuto del Package, è un carattere jolly).
  • La Classe TextField perché ci servirà una casella di testo in cui inserire il punteggio.
  • Tutto il contenuto del Package snake, ovvero le Classi che abbiamo creato nelle lezioni precedenti.

Adesso inizializziamo le Variabili che ci serviranno. Dentro le parentesi graffe della Classe scriviamo:

    private var _w:Number=30;
    private var _h:Number=30;
    private var _initLength:uint = 3;
    private var _player:Player;
    private var _initDir:String = 'E';
    private var _t:Timer;
    private var _perimeter:Sprite;
    private var _cherry:Cherry;
    private var _score:int=0;
    private var _scoreDisplay:TextField = new TextField();
  • _w è la larghezza dell’Area di Gioco, espressa in Step (uno Step equivale alla grandezza del quadratino base che stiamo usando).
  • _h è l’altezza dell’Area di Gioco, anche questa espressa in Step.
  • _initLength è la linghezza del Serpente all’inizio del gioco.
  • _player è l’Istanze del Serpente (infatti il suo Tipo è Player, la Classe che abbiamo scritto nella precedente lezione).
  • _initDir è la direzione in cui deve muoversi il Serprente all’inizio del gioco (‘E’ sta per ‘Destra’, come abbiamo stabilito nella lezione precedente).
  • _t è un Oggetto di Tipo Timer, sarà lui a tenere il Tempo di Gioco (la velocità del gioco dipenderà dal valore di delay di questo Oggetto).
  • _perimeter è uno Sprite in cui disegneremo tutte le Istanze della Classe Wall che servono per rappresentare il perimetro dell’Area di Gioco.
  • _cherry è l’Istanza della Classe Cherry che sarà rappresentata sullo schermo.
  • _score è il punteggio, naturalmente si inizia da Zero punti.
  • _scoreDisplay è una casella di testo che utilizzeremo per visualizzare il punteggio.

Metodo Costruttore

public function SnakeGame() {
    // Creazione Area di Gioco
    _perimeter = new Sprite();
    for (var hi:uint = 0; hi < _h; hi++) {
        for (var wi:uint = 0; wi < _w; wi++) {
            if (hi==0||hi==_h-1||wi==0||wi==_w-1) {
                var b:Wall = new Wall();
                b.x=wi*b.w;
                b.y=hi*b.h;
                _perimeter.addChild(b);
            }
        }
    }
    this.addChild(_perimeter);
    // Fine Creazione Area di Gioco

    // Creazione Personaggio
    _player=new Player(_initLength,_initDir);
    _player.addEventListener("hit his own tail", gameOver);
    this.addChild(_player);
    // Fine Creazione Personaggio

    // Creazione Ciliegia
    _cherry = new Cherry();
    randomPlace(_cherry);
    this.addChildAt(_cherry, 0);
    // Fine Creazione Ciliegia

    // Creazione Punteggio
    _scoreDisplay.y=_perimeter.y+_perimeter.height-20;
    _scoreDisplay.x=10;
    addChild(_scoreDisplay);
    displayScore();
    // Fine Creazione Punteggio

    // Creazione Timer
    _t=new Timer(200);
    _t.addEventListener(TimerEvent.TIMER, timeOn);
    // Fine Creazione Timer

    // Creazione Listener Play/Pause
    stage.addEventListener(KeyboardEvent.KEY_DOWN, stageKeyDown);
    // Fine Creazione Listener Play/Pause
}

Le varie operazioni compiute da questo metodo sono separate per permetterci di analizzarle singolarmente. Cominciamo dalla prima:

Creazione Area di Gioco

    // Creazione Area di Gioco
    _perimeter = new Sprite();
    for (var hi:uint = 0; hi < _h; hi++) {
        for (var wi:uint = 0; wi < _w; wi++) {
            if (hi==0||hi==_h-1||wi==0||wi==_w-1) {
                var b:Wall = new Wall();
                b.x=wi*b.w;
                b.y=hi*b.h;
                _perimeter.addChild(b);
            }
        }
    }
    this.addChild(_perimeter);
    // Fine Creazione Area di Gioco

Il codice qui sopra inizializza lo Sprite _perimeter e inserisce al suo interno tante Istanze della Classe Wall. Modificando soltanto questa porzione di codice saremo in grado di creare labirinti sempre diversi. Mi sono tenuto semplice e ho deciso di inserire soltanto un perimetro tutto intorno all’Area di Gioco, come in questa immagine:

Quadro Snake Per Tutorial

Basta un piccolo cambiamento in questa porzione di codice per ottenere un quadro molto più difficile da giocare, per esempio:

            /// Condizione originale:
//          if (hi==0||hi==_h-1||wi==0||wi==_w-1) {
            /// Condizione modificata:
            if (hi==0||hi==_h-1||wi==0||wi==_w-1||(hi==Math.floor(_h/2) && wi%2 == 0)) {

Il cambiamento qui sopra produce questo effetto:

Variante di gioco

Creazione Personaggio

La Creazione del Serpente è semplicissima, tutto il lavoro lo abbiamo già fatto nella lezione precedente, adesso non ci rimene che instanziarlo, collegargli un EventListener e posizionarlo sullo schermo, in questo modo:

    // Creazione Personaggio
    _player=new Player(_initLength,_initDir);
    _player.addEventListener("hit his own tail", gameOver);
    this.addChild(_player);
    // Fine Creazione Personaggio

Il nome dell’Evento hit his own tail è supportato dalla Classe Player, questo Evento si verifica quando il Serpente urta la sua stessa coda. Possiamo utilizzarlo perché dentro la Classe Player abbiamo utilizzato la direttiva dispatchEvent passando esattamente lo stesso nome, la riga di codice che abbiamo utilizzato è questa:

    dispatchEvent(new Event("hit his own tail"));

Ho volutamente utilizzato degli spazi all’interno del nome affinché si vedesse che è possibile utilizzare una stringa qualunque.

Creazione della Ciliegia

    // Creazione Ciliegia
    _cherry = new Cherry();
    randomPlace(_cherry);
    this.addChildAt(_cherry, 0);
    // Fine Creazione Ciliegia

Tre righe per:

  1. Instanziare la Classe Cherry.
  2. Assegnarle delle coordinate a caso interne all’Area di Gioco (utilizziamo una funzione che scriveremo tra poco, si chiamerà randomPlace).
  3. Posizionarla sullo schermo sotto tutto il resto (utilizzando addChildAt anziché addChild).

Creazione del testo per il Punteggio

La casella di testo l’abbiamo già: si chiama _scoreDisplay. Assegnamo una posizione x e y in base alla grandezza di _perimeter e poi richiamiamo una Funzione che aggiorna il punteggio (anche se all’inizio è sempre Zero).

    // Creazione Punteggio
    _scoreDisplay.y=_perimeter.y+_perimeter.height-20;
    _scoreDisplay.x=10;
    addChild(_scoreDisplay);
    displayScore();
    // Fine Creazione Punteggio

La Funzione displayScore non esiste ancora, ma ci stiamo arrivando…

Creazione del Timer

Un Timer è un Oggetto che ci permette di eseguire operazioni a intervalli di tempo specificati. Per utilizzarlo dobbiamo impostare un valore di delay (ritardo) e assegnargli un EventListener. Il valore di delay è espresso in Millisecondi.

    // Creazione Timer
    _t=new Timer(200);
    _t.addEventListener(TimerEvent.TIMER, timeOn);
    // Fine Creazione Timer

Il metodo timeOn sarà richiamato ad ogni intervallo, ovvero 5 volte al secondo (200 equivale a 1/5 di secondo).

Più avanti modificheremo il valore di delay di questo Timer, per fare in modo che il gioco diventi più veloce, e più difficile, man mano che i serpente cresce.

Un altro KeyEvent Listener per lo Stage

Anche se la Classe Player ha aggiunto un Listener di Tipo KeyboardEvent.KEY_DOWN possiamo aggiungerne un altro anche qui, infatti si possono collegare innumerevoli Listener, anche dello stesso Tipo, ad uno stesso Oggetto.

    // Creazione Listener Play/Pause
    stage.addEventListener(KeyboardEvent.KEY_DOWN, stageKeyDown);
    // Fine Creazione Listener Play/Pause

La Funzione stageKeyDown avrà il solo compito di fermare e riavviare il Timer quando si preme il tasto ‘P’ sulla tastiera, la scriveremo tra poco.

Trigger per i Listener

Abbiamo utilizzato tre Listener: uno per Eventi da Tastiera, uno per Eventi del Timer e uno per un Evento generico (quando il Serpente urta la propria coda). Dobbiamo scrivere le Funzioni da chiamare.

Play/Pause

private function stageKeyDown(e:KeyboardEvent) {
    switch (e.keyCode) {
        case 80 :
            if (_t.running) {
                _t.stop();
            } else {
                _t.start();
            }
            displayScore();
            break;
        default :
            trace(e.keyCode);
            break;
    }
}

La Funzione stageKeyDown viene eseguita ogni volta che premiamo un tasto. Se il tasto premuto è la lettera P (riconosciuta attraverso il suo codice che è 80) il timer _t viene fermato (se sta scorrendo) o fatto ripartire (se è fermo). Questo basta per avere l’effetto di Play/Pause nel gioco.

Potremmo utilizzare un tasto diverso per la Pausa durante il gioco e per fare iniziare la partita, ma credo che non sarà difficile per voi sperimentare: la funzione vi dice il codice del tasto che premete sulla tastiera nel caso in cui questo non sia la P.

timeOn – lo scorrere del tempo

private function timeOn(e:TimerEvent) {
    if (hitCherry(_player)) {
        randomPlace(_cherry);
        _player.length++;
        _score++;
        if (_score%10==0) {
           _t.delay = _t.delay-10;
        }
        displayScore();
    }
    if (hitWall(_player)) {
        gameOver();
    } else {
        _player.timeOn(e);
    }
}

Il tempo di gioco scorre grazie a questa Funzione, scatenata dal Listener collegato al nostro Timer _t.

Ad ogni esecuzione esegue due controlli:

  1. Se il Serpente ha mangiato una Ciliegia:
    • Sposta la Ciliegia in una posizione a caso.
    • Incrementa di 1 la linghezza del Serpente.
    • Incrementa di 1 il punteggio.
    • Se il punteggio è un multiplo di 10 (10, 20, 30, 40, ecc) accelera il gioco modificando il valore di delay dell’Oggetto Timer _t.
    • Aggiorna la casella di testo che visualizza il punteggio.
  2. Se il Serpente…
    • …ha sbattuto contro il Muro? Richiama la funzione gameOver (la stessa funzione che è collegata all’Evento hit his own tail del nostro _player).
    • …non ha sbattuto? Muove il Serpente richiamando il Metodo timeOn della Classe Player.

Anche se questo Metodo appena scritto si chiama timeOn e il metodo chiamato all’ultima riga si chiama anche lui timeOn non si tratta della stessa cosa: il primo è un Metodo della Classe attuale, si trova nello stesso Contesto in cui stiamo scrivendo e si può utilizzare chiamandolo semplicemente per nome oppure utilizzando la parola this (this.nomeMetodo); il secondo è un Metodo Pubblico di una Classe, per chiamarlo dobbiamo utilizzare il nome dell’Istanza seguito dal punto (nomeIstanza.nomeMetodo).

Game Over

Se il Serpente va a scontrarsi con la propria coda il suo Listener scatena la Funzione gameOver.

Se durante l’esecuzione della funzione timeOn si verifica che il Serpente è andato a sbattere contro il muro viene richiamata la Funzione gameOver.

Nel primo caso viene passato un Parametro, che è l’Evento. Nel secondo caso non viene passato nulla. La Funzione deve poter essere richiamata sia con un Parametro, sia senza niente… per farlo è necessario inserire un valore di Default per il Parametro:

private function gameOver(e:Event = null) {
    stage.removeEventListener(KeyboardEvent.KEY_DOWN, _player.changeDir);
    removeChild(_player);
    _player = new Player(_initLength,_initDir);
    _player.addEventListener("hit his own tail", gameOver);
    addChild(_player);
    _score = 0;
    _t.stop();
    _t.delay = 200;
}

In caso di Game Over dobbiamo riportare tutto alla situazione iniziale, quindi:

  1. Rimuoviamo il Listener da Tastiera che fa cambiare direzione al Serpente.
  2. Rimuoviamo il Serpente, ovunque si trovi.
  3. Re-instanziamo l’Oggetto _player.
  4. Ri-assegnamo il Listener hit his own tail al Serpente (perché quando re-inizializziamo un Oggetto perdiamo tutti i Listener precedentemente associati)
  5. Riposizioniamo il Serpente sullo schermo.
  6. Riportiamo a Zero il punteggio.
  7. Fermiamo il nostro Timer.
  8. Re-impostiamo il valore di delay a 200 (perché frattempo potrebbe essere cambiato).

Altre Funzioni

Il grosso è fatto, ci rimangono da scrivere soltanto poche Funzioni non complicate:

private function randomPlace(element) {
    var rX = Math.floor(Math.random()*(_w-2))+1;
    var rY = Math.floor(Math.random()*(_h-2))+1;
    element.x=rX*element.w;
    element.y=rY*element.h;
    if (_player.hitTail(element)) {
        randomPlace(element)
    }
    trace(element.x, element.y);
}

La Funzione qui sopra si occupa di posizionare un elemento a caso, in uno spazio vuoto del Quadro di Gioco, la utilizziamo per spostare l’istanza della Classe Cherry (la Ciliegia).

private function hitWall(p:Player):Boolean {
    for (var i = 0; i < _perimeter.numChildren; i++) {
        if (p.head.hitTestObject(_perimeter.getChildAt(i))) {
            return true;
        }
    }
    return false;
}
private function hitCherry(p:Player):Boolean {
    if (p.head.hitTestObject(_cherry)) {
        return true;
    }
    return false;
}

Le due Funzioni qui sopra rispondono alle domande La testa del Serpente ha colpito il Muro? Ha colpito la Ciliegia? Sono quasi uguali e rispondono allo stesso modo: True in caso di collisione e False in caso non ci sia stata nessuna collisione.

private function displayScore() {
    _scoreDisplay.text = String(_score);
}

L’ultima Funzione non fa altro che aggiornare la casella del punteggio con il valore della Variabile Score.

Con questo abbiamo finito, potete scaricare tutte le Classi scritte fino a questo momento, oppure godervi il risultato raggiunto.

Inserisci nome e indirizzo email per iscriverti alla mia newsletter e ricevere il file immediatamente.
In breve: i dati inseriti in questo modulo saranno utilizzati per inviarti il link per scaricare il file che desideri, saranno conservati da un servizio esterno che si chiama MailChimp e in qualsiasi momento potrai cancellare la tua iscrizione al seguente link: https://danielealessandra.us7.list-manage.com/unsubscribe?u=546bebc381e525372d2120083&id=326af7d230.

Puoi leggere l'informativa completa cliccando sul link Privacy Policy che trovi dovunque su questa pagina, e comunque visitando in qualsiasi momento l'indirizzo https://www.danielealessandra.com/privacy-policy/
Ho letto e accetto l’informativa sulla privacy.

No flash, please!

Purtroppo Adobe Flash Player non è più benvoluto come una volta, per questo motivo ho disabilitato tutte le applicazioni Flash presenti sul sito. Se vuoi puoi comunque scaricare il file SWF che si trovava qui, clicca qui per far partire il download

Ricordatevi di premere P per iniziare a giocare.

Con questo, il Gioco ha già un aspetto decente… manca un ultimo ritocco: nella prossima lezione vedremo come incorporare i Font direttamente dal codice per fare in modo che il Punteggio abbia un aspetto migliore di quello di adesso.

 

Potrebbero interessarti anche...

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.