Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Gestire sequenze di sprite ed eventi

Programmare le reazioni delle animazioni a comandi ed eventi
Programmare le reazioni delle animazioni a comandi ed eventi
Link copiato negli appunti

In questa lezione iniziamo a popolare la scena con spari, nemici ed esplosioni. Per questo avremo bisogno di metodi e variabili che ci consentano di aggiungere in modo rapido altri oggetti allo stage. In primo luogo creiamo una funzione che ci consente di creare una istanza della classe BitmapAnimation dato lo SpriteSheet ed alcune sue proprietà fondamentali:

Aggiungiamo in coda a 1945.js:

function setup_oggetto(sprite_sheet, nome, anim, x, y, speed) {
  var bmpSeq = new BitmapAnimation(sprite_sheet);
  bmpSeq.regX = bmpSeq.spriteSheet.frameWidth/2|0;
  bmpSeq.regY = bmpSeq.spriteSheet.frameHeight/2|0;
  bmpSeq.gotoAndPlay(anim);
  bmpSeq.name = nome;
  bmpSeq.x = x;
  bmpSeq.y = y;
  bmpSeq.vX = 0;
  bmpSeq.vY = 0;
  bmpSeq.speed = speed;
  return bmpSeq;
}

Poi aggiungiamo nella 'sezione variabili globali':

var sparo;             // animazione dello sparo
var sparo_nemico;      // animazione dello sparo nemico
var nemico;            // animazione dell'aereo nemico
var nemici = [];       // array dei nemici presenti a video
var spari_nemici = []; // array degli spari nemici presenti a video
var nemici_uccisi = 0; // conteggio dei nemici abbattuti

Carichiamo anche lo SpriteSheet relativo agli sprite 33x33, aggiungiamo in coda alla 'sezione di caricamento degli sprite':

var altri_sprite_sheet = new SpriteSheet({
      images: ["sprites/other_sprites.png"],
      frames: { width:33, height:33},
      animations: { gun: 0,
                    enemy_fly: [1,3, 'enemy_fly'],
                    enemy_twist: [4,8],
                    enemy_fly_back: [9,10, 'enemy_fly_back'],
                    boom: [11,16, false],
                    enemy_gun: 17
                  }
});

Gestire gli spari

Ora possiamo utilizzare la funzione setup_oggetto per modificare il modo in cui creiamo l'aereo ed utilizzarla anche per i restanti oggetti della scena, ovverosia lo sparo dell'aereo protagonista, l'aereo nemico ed il suo sparo. Riscriviamo la 'sezione di creazione delle animazioni' come segue:

// SEZIONE DI CREAZIONE DELLE ANIMAZIONI
aereo = setup_oggetto(aereo_sprite_sheet, 'aereo', 'fly', Math.round(w/2), h-50, 5);
aereo.direction = 0;
aereo.spari = [];
sparo = setup_oggetto(altri_sprite_sheet, 'sparo', 'gun', 0, h-60, 10);
nemico = setup_oggetto(altri_sprite_sheet, 'nemico', 'enemy_fly', 0, 0, 5);
sparo_nemico = setup_oggetto(altri_sprite_sheet, 'sparo_nemico', 'enemy_gun', 0, 0, 10);

consistentemente all'aereo istanziamo anche un array 'spari' per contenere tutte le animazioni sparo dell'aereo.

Ora che abbiamo caricato tutti gli sprite che ci servono possiamo procedere ad implementare la gestione dello sparo, per fare questo ci avvantaggiamo di un altra funzione 'di servizio': clona_oggetto, che prende una BitmapSequence e la duplica, consentendoci di modificarne anche alcune proprietà. Aggiungiamo questo semplice metodo in coda a 1945.js:

function clona_oggetto(oggetto_base, x, y, vX, vY, direction) {
  var nuovo_oggetto = oggetto_base.clone();
  if(x != null) nuovo_oggetto.x = x;
  if(y != null) nuovo_oggetto.y = y;
  if(vX != null) nuovo_oggetto.vX = vX;
  if(vY != null) nuovo_oggetto.vY = vY;
  if(direction != null) nuovo_oggetto.direction = direction;
  return nuovo_oggetto;
}

Ora possiamo agganciare alla pressione della barra spaziatrice l'aggiunta allo stage di una copia della variabile sparo alla stessa posizione in cui si trova l'aereo. Per fare questo dobbiamo modificare la 'sezione controllo tastiera' aggiungendo in coda la seguente chiamata a funzione:

.up(['space'], function(){
    var nuovo_sparo = clona_oggetto(sparo, aereo.x, null, null, sparo.speed, -1);
    aereo.spari.push( nuovo_sparo );
    stage.addChild(nuovo_sparo);
})

Se mandiamo in esecuzione quanto sviluppato finora otterremo che alla pressione della barra spaziatrice viene disegnato un nuovo proiettile che resta completamente fermo invece di scorrere verso l'alto. Questo perché non abbiamo aggiunto alcuna funzione all'interno del tick() che gestisca il movimento di questi oggetti.

Visto che dovremo far muovere anche gli aerei nemici ed i rispettivi spari creiamo una funzione che accetti un elenco di oggetti ed aggiorni per ciascuno la posizione in accordo con le proprietà vY (velocità sull'asse y) e direction. Inoltre dovremo far attenzione a rimuovere gli elementi della collezione che sorpassano i bordi del nostro canvas.

Per lavorare sulle collezioni in modo semplice ed efficace facciamo uso di alcuni dei metodi messi a disposizone da underscore.js come:

  • _.map, che ritorna la collezione in ingresso dopo che ogni elemento è stato elaborato attraverso la funzione definita come argomento
  • _.compact, che rimuove dalla collezione gli elementi con valore null, false o undefined

Aggiungiamo in coda a 1945.js:

function muovi_oggetti(oggetti){
    return _.compact(_.map(oggetti, function(sp){
        sp.y += sp.vY * sp.direction;
        return (sp.y >= 0 && sp.y < h) ? sp : ( stage.removeChild(sp) && false );
    }));

La variabile y di ogni oggetto viene incrementata in accordo con velocità e direzione e se l'oggetto supera i bordi del canvas viene invocata la funzione removeChild che provvede alla rimozione dell'oggetto dalla scena; in più la funzione ritorna false e questo comporta la rimozione dell'oggetto anche dall'array di ritorno.

Non ci resta che aggiungere nella 'sezione aggiornamento oggetti' la chiamata a muovi_oggetti con parametro aereo.spari:

aereo.spari = muovi_oggetti(aereo.spari);

Testiamo in un browser il risultato dei nostri sforzi:

Figura 5. L'aereo che spara
(clic per ingrandire)


L'aereo che spara

Gestire gli aerei nemici

Per implementare i nemici dobbiamo innanzitutto predisporre un meccanismo che ci consenta di crearne di nuovi con una frequenza casuale e incrementale, in modo che più si prosegua nel gioco più il sopravvivere diventi difficoltoso.

L'idea è quella di aggiungere il codice necessario alla creazione di un nuovo aereo all'interno della funzione tick(): aggiungiamo quindi in coda alla 'sezione aggiornamento oggetti':

// SEZIONE CREAZIONE NEMICI
var difficolta = Ticker.getTicks()/5000;
if( Math.random() > 0.95 - ( difficolta > 0.60 ? 0.60 : difficolta )){
    var nuovo_nemico = clona_oggetto(
	    nemico,
	    Math.round(Math.random() * (w - 33)),
		null, null, nemico.speed, 1);
	nuovo_nemico.turn_point = Math.round(Math.random() * h/2.0 + h/4.0);
	nemici.push(nuovo_nemico);
	stage.addChild(nuovo_nemico);
}

Con Ticker.getTicks otteniamo il numero di cicli eseguiti dalla funzione, il fattore di difficoltà crescente è questo valore diviso 5000.

Quindi diamo il via alla comparsa di un nuovo aereo nemico con una probabilità base del 5% ad ogni ciclo, incrementata del valore di difficoltà fino ad un massimo del 40% di comparsa ad ogni ciclo.

Se siamo nella condizione di creare un nuovo nemico:

  • cloniamo l'oggetto nemico e posizioniamolo nella parte alta del canvas, ad una posizione casuale sull'asse delle ascisse;
  • poiché vogliamo che l'aereo nemico esegua un mezzo giro della morte e poi torni verso l'alto, impostiamo turn_point, una variabile anch'essa random, che identifica il punto nel quale sarà effettuata questa manovra
  • aggiungiamo l'aereo allo stage ed all'array nemici

Mandiamo in esecuzione e appariranno gli aerei nemici nella parte alta del canvas con cadenza variabile. Non ne abbiamo ancora gestito il movimento, quindi rimarranno immobili.

Il movimento dei nemici non è semplice come quello dei proiettili. Se torniamo alla definizione dello spritesheet notiamo che abbiamo impostato ben 4 sequenze (escluso enemy_gun):

  • enemy_fly
  • enemy_twist
  • enemy_fly_back
  • boom

Le prime tre, che compongono l'intera sequenza di volo dovranno susseguirsi l'un l'altra, in particolare l'animazione enemy_twist dovrà essere eseguita in corrispondenza della variabile turn_point. L'ultima sequenza, boom , dovrà essere richiamata solo in caso di collisione con un proiettile lanciato dal nostro aereo.

Fortunatamente easel.js ci consente di associare ad ogni oggetto un callback, ovverosia una funzione da chiamare alla fine di ogni sequenza di animazione; questo si rivela molto comodo per gestire animazioni complesse come quella in oggetto.

Aggiungiamo alla 'sezione di aggiunta allo stage':

nemico.onAnimationEnd= animazione_nemico;

Definiamo poi la funzione animazione_nemico che deve accettare un parametro, che verrà valorizzato da easel.js con l'oggetto che ha completato la sequenza di animazione:

function animazione_nemico(nm) {
  switch(nm.currentAnimation) {
    case 'enemy_fly':
      if(nm.y > nm.turn_point) {
	    var nuovo_sparo = clona_oggetto(sparo_nemico, nm.x, nm.y + 5, null, -1 * sparo.speed, -1);
		spari_nemici.push(nuovo_sparo);
		stage.addChild(nuovo_sparo);
		nm.direction = 0;
		nm.gotoAndPlay('enemy_twist');
	  }
	break;
    case 'enemy_twist':
      nm.direction = -1;
      nm.gotoAndPlay('enemy_fly_back');
    break;
    case 'boom':
      nemici_uccisi = nemici_uccisi + 1;
      nemici = _.without(nemici, nm);
      stage.removeChild(nm);
    break;
  }
}

la variabile currentAnimation contiene il nome della sequenza di animazione appena completata.

Se l'animazione appena conclusa è enemy_fly e non è stato ancora raggiunto il punto dove eseguire la manovra allora non è necessario modificare alcun parametro, l'aereo nemico deve continuare la sua discesa.

Se viceversa è stato raggiunto il turn_point devono accadere due cose: in primis dobbiamo creare un proiettile nemico ed aggiungerlo alla scena alle stesse coordinate dell'aereo nemico, poi dobbiamo invocare l'esecuzione della sequenza enemy_twist ricordandoci di valorizzare la direzione a 0 in modo che l'aereo non continui a muoversi verso il basso mentre esegue la manovra.

Se l'animazione appena conclusa è invece enemy_twist l'aereo deve invertire la propria direzione (direction = -1) e cominciare a risalire verso l'alto eseguendo l'animazione enemy_fly_back.

Infine, in caso l'animazione eseguita sia boom è necessario aumentare il conteggio dei nemici uccisi e rimuovere l'aereo sia dall'array nemici (con il comodo metodo _.without), sia dalla scena.

Non ci resta che aggiungere nella 'sezione aggiornamento oggetti' il controllo di avanzamento per aerei nemici e proiettili:

nemici = muovi_oggetti(nemici);
spari_nemici = muovi_oggetti(spari_nemici);

e provare il tutto all'interno del browser:

Figura 6. Arrivano i nemici
(clic per ingrandire)


Arrivano i nemici

Ti consigliamo anche