Realizzare Puzzle Bobble (Bust a Move) [6 di ?]

La realizzazione del nostro gioco va avanti, ma ancora non somiglia molto al risultato che vi aspettavate… niente paura: oggi faremo un grosso passo avanti introducendo alcune modifiche di cui si sente già la mancanza.

  • Imposteremo le dimensioni definitive al quadro di gioco.
  • Disegneremo i vari livelli, in modo da poter decidere la posizione iniziale delle Sfere di ogni livello.
  • Faremo in modo di avere in gioco soltanto sfere di colore utile, come nell’originale Puzzle Bobble.
  • Al termine di un livello di gioco passeremo al successivo.

Sembra troppo per un articolo solo? In realtà se avete seguito tutti gli articoli fin qui il grosso del lavoro è già fatto, quindi non scoraggiatevi e andiamo avanti.

Dimensioni del quadro di gioco.

Nell’originale Puzzle Bobble il quadro di gioco ha delle dimensioni ben precise, non si utilizza tutto lo spazio disponibile per contenere le sfere. La dimensione del gioco originale è pari alla larghezza di 8 Sfere affiancate, per fare in modo che anche il nostro quadro di gioco sia di queste dimensioni è sufficiente una piccola modifica al file Walls.as, all’interno del Metodo drawWalls:

public function drawWalls(e:Event) {
    stage.addEventListener(Event.RESIZE, drawWalls);
    this.graphics.clear();
    this.graphics.beginFill(0x000000, 0.1);
    this.graphics.drawRect((_diameter*4)-_diameter/2,0,_diameter/2,stage.stageHeight);
    this.graphics.drawRect((_diameter*-4),0,_diameter/2,stage.stageHeight);
    this.graphics.endFill();
    this.x = stage.stageWidth/2;
}

In questo modo le due pareti laterali saranno distanti tra loro non più di 8 volte il diametro delle Sfere.

Livelli di gioco.

All’inizio di ogni Stage lo schermo contiene già delle Sfere in una posizione specifica. Per memorizzare queste mappe di livello utilizzeremo un Array, ma prima dobbiamo fare alcune considerazioni.

  • Abbiamo in gioco 8 colori diversi, possiamo utilizzare un numero da 1 a 8 per indicare la posizione di una specifica Sfera all’interno della mappa iniziale.
  • Abbiamo la necessità di inserire delle posizioni vuote all’interno della mappa, queste saranno contrassegnate dal numero zero.
  • Possiamo utilizzare il numero 9 come jolly: indicheremo con questo numero la presenza di una Sfera, in una precisa posizione, ma di un colore a caso tra quelli disponibili.

Deciso questo non ci rimane che procurarci il disegno di uno dei livelli del gioco originale e provare a riprodurne il disegno:

Il primo livello del gioco originale.

Il primo livello del gioco originale.

Lo schema da riprodurre è abbastanza semplice, non ci rimane che associare un numero ad ogni colore e trascrivere la posizione di ogni Sfera:

In ActionScript è possibile andare a capo e inserire spazi in maniera arbitraria, questo ci permette di trascrivere l’Array nell’ordine esatto in cui verranno visualizzate le Sfere. Questo ordinamento è molto comodo per il compito che dobbiamo svolgere. Ecco che aspetto ha l’Array del primo livello all’interno del codice ActionScript:

var stage1:Array = [
    1,1,2,2,3,3,4,4,
     1,1,2,2,3,3,4,
    3,3,4,4,1,1,2,2,
     3,4,4,1,1,2,2
    ];

Visto che gli spazi non vengono interpretati, il codice qui sopra poteva essere scritto anche su una singola riga:

var stage1:Array = [1,1,2,2,3,3,4,4,1,1,2,2,3,3,4,3,3,4,4,1,1,2,2,3,4,4,1,1,2,2];

In questo modo però perdiamo la comodità di poter leggere “a colpo d’occhio” una mappa.

Trascrivere tutti i livelli di gioco.

Adesso che abbiamo compreso il meccanismo proviamo a disegnare altri modelli: io ho già trascritto i primi 5 livelli del gioco originale:

Questi 5 Array saranno contenuti a loro volta in un unico Array, che rappresenta l’elenco dei Livelli disponibili nel gioco.

Per una gestione più semplice di questi dati ho deciso che utilizzeremo una Classe soltanto per loro, questa Classe conterrà un’unica Proprietà Statica, l’elenco dei livelli (appunto).

Ecco il codice della Classe che ho chiamato RoudMaps:

package pb {
    public class RoundMaps {
        public static const stages:Array = [
            [
            1,1,2,2,3,3,4,4,
             1,1,2,2,3,3,4,
            3,3,4,4,1,1,2,2,
             3,4,4,1,1,2,2
            ],
            [
            0,0,0,7,7,0,0,0,
             0,0,0,5,0,0,0,
            0,0,0,5,0,0,0,0,
             0,0,0,4,0,0,0,
            0,0,0,1,0,0,0,0,
             0,0,0,6,0,0,0,
            0,0,0,5,0,0,0,0,
             0,0,0,4
            ],
            [
            4,0,0,0,0,0,0,4,
             1,4,3,2,1,4,3,
            2,0,0,0,0,0,0,2,
             3,2,1,4,3,2,1,
            0,0,0,1,0,0,0,0,
             0,0,0,4,0,0,0,
            0,0,0,1
            ],
            [
            0,7,7,0,0,3,3,0,
             0,6,0,0,0,5,0,
            0,1,0,0,0,5,0,0,
             0,4,0,0,0,3,0,
            0,5,0,0,0,5,0,0,
             0,4,0,0,0,3,0,
            0,6,0,0,0,5,0,0,
             0,4,0,0,0,1
            ],
            [
            0,9,0,9,0,9,0,9,
             9,0,9,0,9,0,9,
            9,0,9,0,9,0,9,0,
             0,9,0,9,0,9,0,
            0,9,0,9,0,9,0,0,
             9,0,9,0,9,0,0,
            0,0,9,0,9,0,0,0,
             0,0,0,9
            ]
        ]

        public function RoundMaps() {
            // nulla...
        }
    }
}

In questa Classe la Proprietà stages è una costante, questo significa che non può essere cambiata, inoltre è statica, il che significa che appartiene all’intera Classe e non avremo bisogno di instaziare nessun Oggetto per aver accesso a quella costante… per questo motivo il Metodo costruttore è vuoto.

Quando avremo bisogno di accedere a questi dati sciveremo semplicemente così:

RoundMaps.stages;

Utilizzare gli Array mappa per disegnare i vari livelli.

Il Metodo per disegnare i vari livelli in base ai valori che abbiamo trascritto farà parte della Classe BubbleGrid, è lì dentro che teniamo tutta la gestione delle Sfere del gioco. Questo Metodo riceverà un Array di numeri e utilizzerà il Metodo già presente addBubble per inserire le Sfere sul quadro di gioco, ecco come:

public function drawStage(bubbleList:Array):void {
    var row:uint = 0;
    var column:uint = 0;
    var bx:Number = 0;
    var by:Number = 0;
    for (var i:uint = 0; i < bubbleList.length; i++) {
        if (bubbleList[i] > 0) {
            bx = (column*_radius) + _radius + (stage.stageWidth/2) - (_radius*4.5);
            by = (row * EQUILATERAL_TRIANGLE_HEIGHT * _radius) + (_radius/2);
            var bubbleColorIndex:uint = bubbleList[i]-1;
            if (bubbleColorIndex >= Main.colors.length) {
                /// Se il numero non corrisponde ad uno dei colori noti generiamo un colore a caso
                bubbleColorIndex = Math.floor(Math.random()*Main.colors.length);
            }
            var b:Sphere = new Sphere(Main.colors[bubbleColorIndex], _radius);
            /// Aggiungiamo la Sfera alla Griglia grazie al "vecchio" Metodo addBubble
            addBubble(bx, by, b, true);
        }
        column++;
        if ((column > 7 && row % 2 == 0) || (column > 6 && row % 2 != 0)) {
            /// Andiamo a capo dopo 8 Sfere nelle righe pari e dopo 7 Sfere nelle righe dispari
            row++;
            column = 0;
        }
    }
}

Il Metodo addBubble funziona alla perfezione, con una piccola modifica: un parametro in più, di tipo Booleano, che ci serve a distinguere questo caso, in cui vogliamo costruire il quadro di gioco, dal caso più comune, ovvero la fase di gioco vera e propria.

Quando inviamo questo quarto Parametro con valore true il Metodo addBubble sa che non deve raggruppare le Sfere e farle esplodere, mentre questo comportamento sarà mantenuto durante la fase di gioco.

Ecco come è diventato il Metodo addBubble:

public function addBubble(bx,by, bubble:Sphere, starting:Boolean = false) {
    var inputPoint = new Point(bx,by);
    var position:Point = getGridCoords(inputPoint);
    var bubbleName = 'Sphere_' + String(position.x) + '_' + String(position.y);
    if (! this.getChildByName(bubbleName)) {
        bubble.name = bubbleName;
        bubble.x = position.x;
        bubble.y = position.y;
        this.addChild(bubble);
        dispatchEvent(new Event('placedBubble'));
        _bubbleList.push(bubble);
        if (starting) {
            bubble.lifeStatus = 'sleeping';
        } else {
            bubble.lifeStatus = 'alive';
        }
        checkGroups(bubble);
        markIsolatedBubbles();
        dropIsolatedBubbles();
    } else {
        var bbx = bx + 1;
        var bby = by + EQUILATERAL_TRIANGLE_HEIGHT;
        bubble.lifeStatus = 'alive';
        addBubble(bbx,bby, bubble);
    }
    checkStageStatus();
}

Come potete vedere l’unica differenza tra una Sfera aggiunta attraverso la procedura di disegno del Livello e una Sfera aggiunta durante la fase di gioco è il valore che viene assegnato alla Proprietà lifeStatus della Sfera stessa: sleeping. Questo valore ci serve per distinguere le Sfere “dormienti” che non devono esplodere anche se si trovano a gruppi di tre o più. Perché un gruppo di Sfere esploda sarà necessario che almeno una delle Sfere abbia il valore della Proprietà lifeStatus impostato ad alive, questo vorrà dire che quella Sfera è stata aggiunta durante la fase di gioco.

Questa modifica richiede un piccolo ritocco al Metodo checkAndExplode e l’aggiunta di un nuovo Metodo notSleeping, ecco il codice:

private function checkAndExplode(groupNum) {
    var bubbleList:Array = new Array();
    for (var e:uint = 0; e<this.numChildren; e++) {
        var bub:Sphere = this.getChildAt(e) as Sphere;
        if (bub.group == groupNum) {
            bubbleList.push(bub);
        }
    }
    if (bubbleList.length >= _minGroupForExplosion && notSleeping(bubbleList)) {
        /// Il gruppo è formato da un numero sufficiente di Sfere e non è dormiente, quindi deve esplodere
        for (var i:uint = 0; i < bubbleList.length; i++) {
            killBubble(bubbleList[i], 'exploding');
        }
    }
}
private function notSleeping(bubbleList:Array):Boolean {
    /// Restituisce TRUE se il Gruppo di Sfere non è dormiente
    for (var i:uint = 0; i < bubbleList.length; i++) {
        if ((bubbleList[i] as Sphere).lifeStatus != 'sleeping') {
            return true;
        }
    }
    /// Restituisce FALSE se il gruppo di Sfere è dormiente
    return false;
}

Con queste modifiche siamo sicuri di poter disegnare un Livello di gioco senza che questo cada giù a pezzi. Adesso facciamo un ultimo passo per migliorare il nostro gioco.

Mostrare un Livello per volta, in ordine.

Per poter giocare un Livello alla volta aggiungiamo una Proprietà alla Classe Main, questa Proprietà sarà il numero corrispondente al Livello attualmente visualizzato:

private var _currentStage:uint = 0;/// Lo Stage attualmente in gioco

All’inizio del gioco utilizziamo questa Proprietà per disegnare il primo livello di gioco:

_grid.drawStage(RoundMaps.stages[_currentStage]);

Ogni volta che completiamo un Livello incrementiamo il valore di questa Proprietà e ri-disegnamo il Livello. Questo controllo lo possiamo inserire momentaneamente come parte del Metodo createBubble:

public function createBubble(e:MouseEvent = null) {
    if (_grid.numChildren < 1) {
    /// Il quadro di gioco è vuoto, disegnamo il Livello successivo.
        _currentStage ++;
        if (_currentStage >= RoundMaps.stages.length) {
            _currentStage = 0;
        }
        _grid.drawStage(RoundMaps.stages[_currentStage]);
    }
    var myColor:String = Main.colors[Math.floor(Math.random() * Main.colors.length)];
    _cannon.createSphere(myColor);
}

In questo modo, ogni volta che completiamo un Livello passeremo immediatamente al successivo.

Ottenere soltanto Sfere di colore opportuno.

Una cosa molto fastidiosa, a questo punto, è che le Sfere a disposizione del Cannone sono di un colore casuale. Una caratteristica del gioco Puzzle Bobble prevede che il Cannone si carichi scegliendo il colore della prossima Sfera tra quelli ancora in gioco, così da non trovarci nella situazione di riempire nuovamente il quadro quando lo abbiamo quasi terminato.

Per fare in modo che anche il nostro Cannone si comporti nello stesso modo è sufficiente modificare una singola riga nel Metodo createBubble: quella in cui generiamo il colore della prossima Sfera:

    var myColor:String = (_grid.getChildAt(Math.floor(Math.random()*_grid.numChildren)) as Sphere).color;
    // non più così: Main.colors[Math.floor(Math.random() * Main.colors.length)];

Invece di prendere un colore a caso tra tutti i colori disponibili prendiamo un colore a caso tra tutte le Sfere sul quadro di gioco, semplice, no?

Ecco come appare il nostro gioco al termine della sesta sessione:

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

Scarica i file

I file di questa lezione sono disponibili cliccando sul pulsante qui sotto:

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.

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.