Creiamo Arkanoid in Flash! – Parte 0: Introduzione

Dopo aver tradotto la guida di Michael James Williams ho deciso di scrivere una guida tutta mia, per iniziare a fare esperienza e pratica in questo campo. Mi piace scrivere, mi piace dare una mano alla gente e qualche tutorial in italiano per flash (anche se si sa, la lingua madre della programmazione è quella inglese) non credo faccia poi tanto male.

L’idea, come avevo spiegato nel post precedente, era quella di ricreare un gioco simile al buon vecchio Arkanoid. Questa “Parte 0” della nostra guida è il nostro punto d’inizio. Farà da base per raccogliere le idee ed avrà una struttura simile ad una pagina di diario piena di idee sparse, nonostante cercherò di tenermi il più possibile sul “lineare”.


Le Dovute Distinzioni

Il punto di partenza della mia ricerca è stato quel gran sito che è Wikipedia. La pagina di Arkanoid già chiarisce molte idee e dobbiamo quindi fare le dovute distinzioni. Molto spesso infatti si fa confusione tra questo gioco ed un altro molto simile: Breakout.

Per cui ecco il primo punto fermo: realizzeremo qualcosa di più vicino ad Arkanoid e non a Breakout. Per cui vi chiederete, le differenze quali sono?

La cara Wikipedia arriva in nostro soccorso (ne riporterò solo alcune che influiranno sul mio gioco):

  • presenza di mattoncini che dobbiamo colpire più volte per distruggere;
  • presenza di oggetti simili a “pillole” che offrono dei bonus alla nostra navicella rilevanti a livello di gameplay;
    • diminuire la velocità della pallina per rendere più facile il gioco (temporaneamente);
    • far diventare magnetica la nostra navicella, in modo tale da prendere la pallina tutte le volte e spedirla dove vogliamo;
    • posizionare un cannone laser sulla navicella.

Ovviamente, in Arkanoid non sono solo questi i power-up che incontreremo. Tuttavia, considerate che stiamo creando un gioco da zero, per cui partiremo prima con queste cose semplici e poi più in là vedremo.


Buttare Giù le Idee

È venuto il momento di decidere un po’ come strutturare il nostro gioco. Ecco cosa ho in mente di realizzare, a livello molto molto astratto:

  1. Una schermata di Preload;
  2. Una schermata Iniziale;
  3. Una schermata di Gioco;
  4. Una schermata di Game Over;

direi che per iniziare va bene: non contate il fatto di avere livelli multipli e.c.c, queste sono cose che vedremo più in la. Quello che voglio creare ora è quindi una “base” su cui poggiare il nostro gioco e quindi lanciarci verso nuove espansioni. Spero d’essere stato chiaro.

Iniziamo a pensare e vediamo quali idee vengono fuori.


Schermata del Preloader

Piccola Nota per il Principiante – Cos’è il Preloader?

Risposta: Il preloader è uno strumento che si occupa di monitorare il caricamento dei contenuti del gioco. Se sono stati caricati tutti rimanda alla schermata di gioco (oppure ad un menù e.c.c), se c’è ancora qualcosa da caricare viene mostrata una schermata che invita l’utente ad attendere la fine delle operazioni. In genere, questa schermata di attesa è integrata da informazioni quali l’avanzamento (in percentuale) del download dei contenuti.

Per la schermata del nostro Preloader ho avuto un idea forse un po’ più simpatica di quella che è la sua visione normale. Giocando nelle sale giochi forse non ricordate questo particolare, ma coloro che hanno rivisto questo capolavoro nel MAME, l’emulatore arcade più conosciuto al mondo, di sicuro hanno presente qualcosa del genere:

Senza titolo-2

Questa che vedete è la schermata di avvio del sistema di gioco di Arkanoid, ovviamente nelle sale giochi. Probabilmente nelle versioni per Super Nintendo questa schermata non sarà presente. La cifra affianco a “Timer: “ come potete immaginare, parte da 10 e quando il sistema è pronto è arrivato a 0. Successivamente quindi parte il gioco.

Ed ecco il nostro preloader!

Invece di usare una semplice percentuale simuleremo proprio il caricamento dell’hardware del gioco :D

In poche parole, le operazioni che il nostro preloader dovrà effettuare saranno le seguenti:

  • verificare il caricamento dei contenuti;
  • calcolare la cifra da far visualizzare nel timer, che ovviamente deve essere ben rapportata considerando che simuleremo un conto alla rovescia.

Vediamo di essere un po’ più chiari nel secondo punto. Abbiamo detto che il nostro “Timer” parte da 10. Ho deciso che 10, che è la prima cifra che visualizzeremo, equivarrà allo 0% di contenuti scaricati. Ogni 10% di contenuto scaricato diminuirà di 1 il timer, fino ad arrivare a 0, momento in cui il caricamento sarà completo.

Ecco quindi i valori del timer con rispettiva percentuale di caricamento del gioco:

  • 10 – 0 – 9%
  • 9 – 10 – 19%
  • 8 – 20 – 29%
  • 7 – 30 – 39%
  • 6 – 40 – 49%
  • 5 – 50 – 59%
  • 4 – 60 – 69%
  • 3 – 70 – 79%
  • 2 – 80 – 89%
  • 1 – 90 – 99%
  • 0 – 100%

ora le cose sono più chiare, vero? :)

Ho preparato anche una bozza della schermata del preload, ovviamente molto semplice, che implementeremo.

loading_screen

^ Clicca per Ingrandire ^

Devo sottolineare una cosa molto importante: la risoluzione originale di Arkanoid prevede un altezza maggiore rispetto alla larghezza. Sugli schermi del nostro pc (considerando tra l’altro che un gioco in flash viene di solito inserito in una pagina web) dobbiamo prendere in considerazione di effettuare un cambio di rotta: per questo motivo ho deciso che la risoluzione del nostro gioco sarà di 640×480 pixel.

Tornando a noi, appena il preloader termina il suo caricamento verrà automaticamente caricata la Schermata Iniziale. Direi che per il Preloader, almeno a livello teorico, ci siamo.


Schermata Iniziale

Per ovvi motivi di copyright non possiamo prendere, copiare ed incollare la schermata iniziale del gioco, ne tantomeno i loghi ufficiali. Per questo motivo ho pensato di elaborare qualcosa di semplice e veloce da realizzare (anche perché ci concentreremo più sulla creazione del gioco in questa guida):

main_screen

^ Clicca per Ingrandire ^

Ho pensato, inoltre, di aggiungerci una piccola animazione: sarà però una cosa che vedremo nel dettaglio dopo, durante la creazione del gioco.

Comunque sia, dobbiamo decidere cosa si può fare da questa schermata: l’unica operazione che potremo fare da qua sarà premere il tasto Invio per procedere verso la schermata di gioco. Potete notare, inoltre, che ho usato un nome personalizzato per il gioco: lo chiameremo FrArkanoid. (poca originalità, lo so ._.)


Schermata di Gioco

Per la schermata di gioco, come potete ben immaginare, ci sono tante cose da vedere per bene ed in maniera approfondita. In questa parte 0, quindi, vedremo solamente una traccia e di sicuro non quello che sarà il lavoro finito.

Anziché creare bozze grafiche, preferisco concentrarmi sulle parole per ora: dobbiamo considerare che la risoluzione è innanzitutto diversa dall’Arkanoid originale. Penso che una cosa del genere di sicuro influirà su alcune scelte di gameplay (per esempio la velocità della pallina).

Inoltre, cosa molto importante, dovremo trovare un modo per gestire i mattoncini, la loro generazione e quindi la loro distruzione. Un po’ di ricerche anche in questo campo non faranno assolutamente male. Magari la matematica ci aiuterà sotto questo punto di vista.

Dovremo inoltre trovare una soluzione per muovere correttamente la pallina: la prima cosa che mi viene in mente è l’equazione della retta

y = mx + q

e probabilmente sarà questa quella che userò. Quando testeremo nello specifico il movimento della pallina capirete come tutte queste lettere influiscono sul gioco ;) Avremo ovviamente un sistema di vite: ne prevedo 3 per il giocatore, esaurite queste si va in Game Over.

Per non complicare troppo le cose, infine, ho deciso di non introdurre quei corpi geometrici che vanno girando per i vari livelli, cambiando a caso la direzione della pallina. Semmai li realizzerò in un articolo a parte dopo questa guida :) Se non avete presente questi corpi di cui parlo, fate finta di niente.

Ed ora la schermata di Game Over.


Schermata di Game Over

La Schermata di Game Over sarà abbastanza semplice ed essenziale. Presenterà un testo che spiegherà che abbiamo perso e se premiamo il tasto Invio entro 10 secondi allora torneremo al livello in cui ci eravamo fermati. Altrimenti, torneremo alla schermata principale.

In questo caso abbiamo poche parole da spendere, anche perché il grosso del lavoro è ancora da fare.

Detto questo direi che c’è poco da dire. Abbiamo visto le prime cose e nel prossimo articolo spiegherò come ho “trovato” in giro per internet le risorse più adatte al nostro scopo.

Alla prossima!

  • Share/Bookmark

Il caro e vecchio Arkanoid! [Nuovi Tutorial in Arrivo]

Alcuni di voi lo ricorderanno, altri no. Lo hanno chiamato in vari modi ma il succo è sempre stato quello. Ultimamente, grazie al MAME, l’ho riscoperto e ci sto giocando minimo una volta al giorno. Parlo dell’immortale Arkanoid, gioco che penso sia uno dei più copiati in tutto l’universo.

Arkanoid

Lo scopo del gioco? Semplice ed intuitivo. Voi siete quella piccola barretta nella zona inferiore dello schermo. Vi potete muovere solo verso destra o verso sinistra e dovete respingere una pallina contro quei blocchi nella zona superiore della schermata.

Rimbalzandoci contro questi si rompono, una volta rotti tutti il livello è terminato. I livelli sono in tutto 33, durante i quali potete prendere anche dei bonus di vario genere per gli effetti più disparati:

  • aumentare la larghezza della barra del giocatore
  • diminuire la velocità della pallina;
  • cambiare livello;
  • aumentare le vite di un unità;

e così via.


Si, e con tutto ciò?

Il succo di questo post è in realtà qualcosa che avevo pensato già da un mesetto. Tradurre la guida di Michael mi ha aiutato molto e mi sento pronto, adesso, per scriverne una mia. Per questo motivo ho deciso di ricreare in maniera abbastanza dettagliata questo splendido capolavoro.

Tuttavia, non voglio usare delle grafiche fatte da me, ma voglio provare a rintracciare un po’ tutti i materiali. Senza ovviamente creare una copia perfetta che pare brutto ;) Insomma, il mio obiettivo è di guidare l’utente passo passo nella creazione del gioco. Non solo in termini di programmazione, ma proprio in tutti i cosiddetti “passi”: dal reperire contenuti (quindi orientarsi un po’ sui siti più adatti) fino alla pubblicazione. Magari su Newgrounds o perché no, creando una pagina applicazione su facebook!

Come al solito, le opportunità sono milioni. Per cui per ora lascio l’annuncio, ma tra qualche tempo sicuro vedrete del movimento da queste parti!

Ciao!

  • Share/Bookmark

Console – Nintendo – Nintendo 64

Il primo amore non si scorda mai. Come soggetto per il mio primo articolo dedicato alle console della mia collezione ho scelto la mitica Nintendo 64, ricevuta alla tenera età di sei anni.

slide11

A mio parere, la N64 è stata una console migliori per il periodo in cui è stata. D’altronde, già che è stata l’unica all’epoca a saltare la generazione 32bit per passare alla 64bit la dice lunga. Tuttavia, nonostante la sua potenza di calcolo e la bellezza dei giochi, è stata fortemente penalizzata.

Si sa, era il periodo della Playstation. I fasti dei tempi della SNES erano ormai finiti e i CD avevano avuto la meglio sulle cartucce. Probabilmente anche per tutelarsi contro la pirateria, la Nintendo fece la scelta errata di rivolegersi ancora ai vecchi supporti.

Le Playstation ormai si vendevano come le pizze il sabato sera, per cui la “sconfitta” fu inevitabile. Esatto, sconfitta tra virgolette, perché comunque le trenta milioni di unità vendute mamma Nintendo se l’è fatte. Fu vista come una sconfitta (e anche io da piccolo l’avevo vista pesantemente come tale) anche perché fino a qualche anno prima le console Nintendo avevano venduto nell’ordine di cento milioni di unità.

Ecco qualche gioco che personalmente consiglio:

  • The Legend of Zelda – Ocarina of Time
  • Super Mario 64
  • Mario Kart 64
  • Mario Party
  • International Superstar Soccer ‘98 (brand che successivamente cambierà nome in PES ;) )
  • Killer Istinct Gold
  • The Legend of Zelda – Majora’s Mask
  • Banjo Kazooie
  • 1080° Snowboarding
  • Super Smash Bros

Maggiori Informazioni su: Wikipedia (IT) – Nintendo 64

  • Share/Bookmark

FlashGameLicense – Vendere i Giochi in Flash

Volevo spendere due parole per presentare/chiedere informazioni su un sito visto di recente. Qualche giorno fa gironzolavo su internet in cerca di qualcosa di nuovo ed ecco che mi ritrovo questo sito: http://www.flashgamelicense.com/.

header

L’idea di fondo non sembra male: una sorta di “marketplace” dove sviluppatori e sponsor si possono incontrare. Sulla carta, si, va sempre tutto bene, ma in pratica? In pratica, sinceramente, non so ancora nulla di certo ma mi è sembrato un sito tutto sommato affidabile.

Per cui, perchè non mettervi in gioco? ;)

Le iscrizioni sono gratuite e inoltre c’è una sezione FAQ che spiega bene un po’ tutte le dinamiche. Forse in futuro lo proverò e scriverò un articolo più approfondito :)

Qualcuno di voi già l’ha provato?

  • Share/Bookmark

Creare un Gioco in Flash – Parte 12: Garbage Collection

Ed eccoci arrivati alla nostra ultima parte. Ormai abbiamo aggiunto tutte le nostre feature: siamo partiti da zero fino ad arrivare ad un gioco praticamente completo di tutto. Nemici, doppio sistema di controllo, livelli multipli, piccole ottimizzazioni qua e la. Insomma, la cosa inizia a farsi più seria di un gioco.

In quest’ultima lezione vorrei parlare di un concetto molto importante: la Garbage Collection. Prima però di effettuare una qualsiasi analisi approfondita, dobbiamo tornare indietro e parlare di qualcosa (in maniera più approfondita ovviamente) alla base di tutto.

Parliamo di variabili!

Nota: se state leggendo a partire da questo capitolo (si, può capitare) prendete i file alla fine della lezione precedente. Altrimenti sapete già cosa fare ;)


Variabili, conosciute o sconosciute?

Quando noi scriviamo qualcosa simile a:

var score:Number = 100;

potete pensare, intuitivamente, di aver creato una casella. Una casella con un “etichetta” (il nome della variabile) con dentro un valore. In questo caso il valore è 100.

AvoiderGame-Part12-D01

Diciamo che è un modo di pensare a questo concetto abbastanza buono, perlomeno per stringhe, booleani e i vari tipi numerici. Ma se noi invece scrivessimo:

var avatar:Avatar = new Avatar();

Presumibilmente, stiamo facendo la stessa cosa. Stiamo creando per caso una casella con scritto “avatar” in corrispondenza dell’etichetta e con un oggetto di tipo Avatar dentro? Beh, non proprio. Il discorso inizia a cambiare. Pensate come a queste due cose, la casella e l’avatar, separate. La casella riporta la sua etichetta per identificare il nome, mentre l’Avatar sembra un qualcosa di “gravitante” nello spazio. Come potete vedere nell’immagine di seguito, le due entità sono collegate:

AvoiderGame-Part12-D02

Cosa vuol dire tutto questo?

Ritorniamo un attimo ai nostri numeri. Considerate questo codice:

var score:Number = 100;
var newScore:Number = score;

Qui, la variabile “newScore” copierà in se stessa il valore della variabile “score”.

AvoiderGame-Part12-D03

Tornando invece a parlare degli avatar, guardate questo codice:

var avatar:Avatar = new Avatar();
var newAvatar:Avatar = avatar;

in questo caso, con il nostro oggetto “gravitante” cosa succede? L’immagine di seguito inizia a spiegare qualcosa, ma potremmo non avere le idee chiare:

AvoiderGame-Part12-D04

Questo può causare molta confusione se provassimo a cambiare dei valori di una delle due variabili. Proviamo il codice seguente (parlando dei nostri cari numeri):

newScore = 250;
trace( "score is:", score );
trace( "newScore is:", newScore );

le cose qui sono quasi ovvie. “newScore” è uguale a 250, “score” è uguale a 100 ed infatti il risultato sarà:
score is: 100
newScore is: 250

Esattamente quello che ci aspettavamo.

Proviamo a fare qualcosa del genere con i nostri avatar adesso, testando queste linee di codice che trovate di seguito.

avatar.scaleY = 1;
newAvatar.scaleY = 2;
trace( "avatar.scaleY is:", avatar.scaleY );
trace( "newAvatar.scaleY is:", newAvatar.scaleY );

(scaleY è una proprietà del Movie Clip, la classe base ;) )

Il codice attualmente scritto produrrà quest’output:

avatar.scaleY is: 2
newAvatar.scaleY is: 2

Mmmmm… c’è qualcosa che non va! Tuttavia, questi risultati hanno un senso se consideriamo la visuale dell’immagine poco sopra, con le due caselle “connesse” ad un unico oggetto. Infondo, stavamo parlando dello stesso oggetto, quindi le modifiche su uno hanno effetto anche sull’altro.

Andiamo avanti. Nelle prime parti di questa guida, avevo detto qualcosa riguardo al null, del fatto che in poche parole serve a cancellare una variabile. Come concetto per iniziare va bene ma quella che facevo era una grossolana semplificazione di un concetto più profondo. Per stringhe, numeri, booleani, la mia spiegazione è valida.

score = null;

che si traduce in

AvoiderGame-Part12-D07

e fino a qui ci siamo. Per altri tipi di oggetti però, come per esempio il nostro onnipresente avatar, le cose cambiano. Ciò che viene annullato è il famoso “collegamento” tra la casella e l’oggetto:

avatar = null;

potrà essere visto come:

AvoiderGame-Part12-D08

Qui la variabile “avatar” non è più collegata con il suo oggetto di classe Avatar, per cui effettuando il trace di una sua variabile (vedi avatar.scaleY). L’oggetto Avatar esiste ancora, così come la variabile “newAvatar”. Per cui, facendo stavolta il trace di “newAvatar.scaleY” avremo come risultato 2, come prima.

Questo può essere uno stratagemma utile, se viene compreso, nel caso dovessimo creare un oggetto in una classe o funzione per passarlo ad un altra classe (o funzione) per farci delle operazioni. È sicuramente meglio del prendere, copiare ed incollare codice magari cancellando anche l’originale.

Tuttavia, questo nostro ragionamento porta ad una interessantissima domanda: cosa succede se impostiamo anche newAvatar come null?

Il risultato può essere intuito come quello in figura:

AvoiderGame-Part12-D09

Ora, il nostro Avatar si ritrova in una terra di nessuno. Abbiamo distrutto tutti i suoi riferimenti e non possiamo accederci più in alcun modo. Questo non toglie che non esiste più: è ancora lì, nella memoria del nostro PC. Detta così sembra quasi una minaccia, anche se in effetti in questo caso non è. Pensate però ad un gioco come il nostro, in cui tutti i nemici potrebbero essere lasciati in memoria, insieme all’avatar, musiche, sfondi, effetti sonori e le varie schermate.

Le cose potrebbero diventare pesanti, ed il gioco inizierebbe ad accusare una forte lentezza. “Inizierebbe” perché abbiamo a disposizione uno strumento potente come il cosiddetto “Garbage Collector”.


Garbage Collection in Flash

Il Flash Player ha un tool incorporato che si occupa di questo compito: il Garbage Collector. Si occupa infatti di “pulire” rimuovendo dalla memoria questi oggetti morti, senza collegamenti. “Ogni tanto” esegue i dovuti controlli sugli oggetti interessati e in caso li elimina. Odio questo termine, “ogni tanto”, perché è decisamente vago: tuttavia, non sappiamo effettivamente di preciso quando questa operazione ha luogo.

Comunque sia, se l’oggetto non ha più collegamenti viene eliminato. In egual modo, se una variabile è collegata ad un oggetto, ma questa variabile è contenuta in un altra senza nessun collegamento, questa viene eliminata. Un caso di questo genere può essere proprio il nostro “avatar”, legato al “playScreen” a sua volta parte della classe documento.

AvoiderGame-Part12-D10

^ Click per Ingrandire ^

Quindi, rimuovendo il riferimento (collegamento) al playScreen in questo modo nella document class:

playScreen = null;

La conseguenza sarà

AvoiderGame-Part12-D12

^ Click per Ingrandire ^

Di conseguenza, il nostro playScreen perderà la sua referenza, ed allo stesso tempo anche il nostro avatar (nonostante sia collegato ad Avatar) viene rimosso dalla memoria in quanto il suo “contenitore” subisce la stessa sorte.

Questo ragionamento, come potete ben immaginare, va esteso idealmente all’infinito. Supponendo per esempio che “avatar” abbia un altro oggetto al suo interno, anche quest’ultimo verrà cancellato. Volendo essere più precisi, si può dire che qualsiasi oggetto non avente un collegamento diretto o indiretto con la classe documento verrà cancellato.

Per maggiori informazioni sull’argomento, posso consigliarvi questi fantastici post di Grant Skinner (in inglese) sul tema. Oltre alla creazione di variabili, ricordate anche che i collegamenti vengono creati anche dalle operazioni di addChild.

Ora, avviate il vostro gioco e tirate la finestra verticalmente, in modo tale da allungarla:

AvoiderGame_Part12_02

AvoiderGame_Part12_03

Esatto, così.

Iniziate a giocare, ricordandovi che il seguente codice viene eseguito:

public function onRequestStart( navigationEvent:NavigationEvent ):void
{
  playScreen = new AvoiderGame();
  playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
  playScreen.x = 0;
  playScreen.y = 0;
  addChild( playScreen );

  menuScreen = null;
}

“menuScreen” viene impostato su null, e quindi:

AvoiderGame_Part12_04

Vedete? Il nostro menuScreen è rimasto ancora lì! Il Garbage Collector non ancora è stato mandato in esecuzione (testate comunque la sua efficacia continuando a giocare per qualche secondo ;) ). Quello che succede è presto spiegato: nonostante abbiamo “nullato” il nostro menuScreen, dobbiamo considerare che abbiamo eseguito l’operazione di addChild per il nostro menu. Questo vuol dire che ha ancora un collegamento alla classe documento.

Per ottimizzare il nostro lavoro, oltre a “nullare” il menuScreen, dobbiamo eseguire l’operazione contraria all’addChild. E, originalità a palate, indovinate come si chiama?

public function onRequestStart( navigationEvent:NavigationEvent ):void
{
  playScreen = new AvoiderGame();
  playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
  playScreen.x = 0;
  playScreen.y = 0;
  addChild( playScreen );

  removeChild( menuScreen );
  menuScreen = null;
}

Esattamente, removeChild. Il risultato a livello di gameplay? Eccolo!

AvoiderGame_Part12_05

Ora le cose vanno moooolto meglio, vero? Beh oddio fino ad un certo punto, considerando che abbiamo perso il controllo della nostra tastiera! Se clicchiamo sull’area di gioco torniamo ad averne il controllo. Cosa diamine e successo?

Ricordate la lezione sette? Avevamo parlato di input di tastiera e vi avevo spiegato come l’oggetto “stage” si occupava di gestire tutto ciò che riguarda l’input da tastiera. Nello specifico, lo stage conteneva una referenza ad un oggetto concentrato (oppure “con il focus”) sulla tastiera. Questo oggetto passava le informazioni di input allo stage e questo le processava di conseguenza.

Per rendere meglio l’idea, immaginate di aver sviluppato un applicazione in flash con delle textbox dove inserire del testo. Cliccando su ognuna di queste, di volta in volta, effettuiamo il “focus” su di esse. Un po’ come quando nel nostro sistema operativo clicchiamo su una finestra e poi su un altra, facendola arrivare in primo piano. Il concetto di focus è essenzialmente questo.

Quando noi abbiamo cliccato sul pulsante per far partire il gioco, abbiamo effettuato il “focus” sulla finestra di gioco. Rimosso l’oggetto dalla memoria, invece, abbiamo rimosso anche il focus dal nostro flash.

Fortunatamente, nonostante tutte queste spiegazioni, basta una sola riga di codice per risolvere il problema. Basta ridare il focus all’oggetto adatto, ovvero il nostro playScreen. L’istruzione stage.focus è quella che ci serve:

public function onRequestStart( navigationEvent:NavigationEvent ):void
{
  playScreen = new AvoiderGame();
  playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
  playScreen.x = 0;
  playScreen.y = 0;
  addChild( playScreen );

  removeChild( menuScreen );
  menuScreen = null;

  stage.focus = playScreen;
}

Salviamo e testiamo tutto. C’è un piccolo problema, il nostro stage è circondato da un “bordo” giallo, decisamente brutto ed antiestetico.

AvoiderGame_Part12_08

È un modo per indicare il focus su quell’area. A noi non piace per cui lo rimuoviamo al volo. Aggiungiamo questa riga di codice alla classe documento, nel costruttore.

stage.stageFocusRect = false;

Ora, date queste nostre nuove conoscenze, non dobbiamo fare altro che applicarle in tutta la classe documento, per garantire un ottimizzazione precisa:
package
{
  //Avoider Game Tutorial, by Michael James Williams
  //http://gamedev.michaeljameswilliams.com

  import flash.display.MovieClip;
  import flash.events.Event;
  import flash.events.ProgressEvent;
  public class DocumentClass extends MovieClip
  {
    public var menuScreen:MenuScreen;
    public var playScreen:AvoiderGame;
    public var gameOverScreen:GameOverScreen;
    public var loadingProgress:LoadingProgress;

    public function DocumentClass()
    {
      loadingProgress = new LoadingProgress();
      loadingProgress.x = 200;
      loadingProgress.y = 150;
      addChild( loadingProgress );
      loaderInfo.addEventListener( Event.COMPLETE, onCompletelyDownloaded );
      loaderInfo.addEventListener( ProgressEvent.PROGRESS, onProgressMade );
      stage.stageFocusRect = false;
    }

    public function onCompletelyDownloaded( event:Event ):void
    {
      removeChild( loadingProgress );
      gotoAndStop(3);
      showMenuScreen();
    }

    public function onProgressMade( progressEvent:ProgressEvent ):void
    {
      loadingProgress.setValue( Math.floor( 100 * loaderInfo.bytesLoaded / loaderInfo.bytesTotal ) );
    }

    public function showMenuScreen():void
    {
      menuScreen = new MenuScreen();
      menuScreen.addEventListener( NavigationEvent.START, onRequestStart );
      menuScreen.x = 0;
      menuScreen.y = 0;
      addChild( menuScreen );

      stage.focus = menuScreen;
    }

    public function onAvatarDeath( avatarEvent:AvatarEvent ):void
    {
      var finalScore:Number = playScreen.getFinalScore();
      var finalClockTime:Number = playScreen.getFinalClockTime();

      gameOverScreen = new GameOverScreen();
      gameOverScreen.addEventListener( NavigationEvent.RESTART, onRequestRestart );
      gameOverScreen.x = 0;
      gameOverScreen.y = 0;
      gameOverScreen.setFinalScore( finalScore );
      gameOverScreen.setFinalClockTime( finalClockTime );
      addChild( gameOverScreen );

      removeChild( playScreen );
      playScreen = null;

      stage.focus = gameOverScreen;
    }

    public function onRequestStart( navigationEvent:NavigationEvent ):void
    {
      playScreen = new AvoiderGame();
      playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
      playScreen.x = 0;
      playScreen.y = 0;
      addChild( playScreen );

      removeChild( menuScreen );
      menuScreen = null;

      stage.focus = playScreen;
    }

    public function onRequestRestart( navigationEvent:NavigationEvent ):void
    {
      restartGame();
    }

    public function restartGame():void
    {
      playScreen = new AvoiderGame();
      playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
      playScreen.x = 0;
      playScreen.y = 0;
      addChild( playScreen );

      removeChild( gameOverScreen );
      gameOverScreen = null;

      stage.focus = playScreen;
    }
  }
}

Come potete notare voi stessi, alla fine non sono molte le modifiche da fare. Ora possiamo continuare con la nostra ottimizzazione. Date un occhiata alla schermata di gioco:

AvoiderGame_Part12_06

^ Click per Ingrandire ^

Guardate questi nemici! Dopo essere caduti nella schermata rimangono oltre l’area normalmente visibile. Questo significa che nessuno di loro verrà coinvolto nella Garbage Collection fin quando il gioco non finisce. È assolutamente un male: se il giocatore riesce a resistere un sacco di tempo avrà dei problemi di rallentamento non indifferenti.

Per prima cosa, nel costruttore del nostro playScreen provvediamo a cancellare queste tre linee di codice:

var newEnemy = new Enemy( 100, -15 );
army.push( newEnemy );
addChild( newEnemy );

non abbiamo più bisogno di questo nemico, in quanto ce ne sono già centinaia che verranno generati casualmente.

E adesso cancelliamo i nemici dopo il loro uso.


Rimuovere oggetti dall’Array

Ecco un problema che, durante le prime volte in cui si tenta di risolverlo, può indurre in errore con facilità. Il nostro obiettivo, attualmente, è eliminare i nemici che vanno oltre il margine inferiore dello schermo: precisamente oltre i 350 pixel.

Per cui, intuitivamente, il codice viene da se:

for each ( var enemy:Enemy in army )
{
  enemy.moveABit();
  if ( PixelPerfectCollisionDetection.isColliding( avatar, enemy, this, true ) )
  {
    gameTimer.stop();
    avatarHasBeenHit = true;
  }
  if ( enemy.y > 350 )
  {
    //il codice per la rimozione va qui
  }
}

(attualmente ci troviamo nella funzione onTick)

Ebbene, dovete sapere che questo codice molto spesso porta ad enormi problemi. Come penso sappiate, gli array sono delle liste, ed ogni elemento di questa lista è un riferimento ad un oggetto. Per esempio, “army[0]” indica il primo nemico nell’array, “army[1]” il secondo e così via.

Quando usiamo il ciclo for each (var enemy:Enemy in army) è come dire “poni enemy = army[0], fai le operazioni descritte, dopodiché poni enemy = army[1], fai le operazioni descritte e.c.c.”. Fino a qui il nostro ragionamento non fa una piega.

Ma ecco che spunta il problema. Supponiamo di voler cancellare il nemico army[4], perché ha raggiunto la fine della schermata. Attualmente, quindi, l’indice sul quale lavoriamo è 4. Cancellando dalla lista il nemico di cui stiamo parlando, gli altri davanti, ovviamente, “scalano di posizione”: il 5 passa al 4, il 6 passa al 5 e così via fino alla fine.

Dopo le operazioni, il ciclo riparte (ci trovavamo al numero 4) e passa al numero 5. Rifletteteci, abbiamo saltato un elemento! È si giusto passare dal 4 al 5, ma dovremmo riprendere in considerazione ancora il 4, dato che l’altro è stato cancellato!

Può essere astruso come concetto, lo so. Per rendere meglio l’idea ecco un altro esempio più pratico. Supponiamo di dover preparare qualcosa per stasera: l’idea è una macedonia. Ci siamo fatti la nostra lista della spesa:

  1. Mele
  2. Arance
  3. Banane
  4. Pere
  5. Fragole

Iniziamo il nostro ciclo for each sulla lista della spesa. L’elemento selezionato attualmente sarà evidenziato in grassetto.

Prima tappa:

L’indice della lista è 1.

  1. Mele
  2. Arance
  3. Banane
  4. Pere
  5. Fragole

le Mele ci sono. Perfetto. Andiamo avanti.

L’indice della lista è 2.

  1. Mele
  2. Arance
  3. Banane
  4. Pere
  5. Fragole

anche le arance sono state trovate. Avanti.

L’indice della lista è 3.

  1. Mele
  2. Arance
  3. Banane
  4. Pere
  5. Fragole

cavolo, le banane non ci sono! Rimuoviamole dalla lista e rimettiamo i numeri al posto giusto (dato che così funziona un array ;) )

L’indice della lista è ora 4.

  1. Mele
  2. Arance
  3. Pere
  4. Fragole

le Fragole ci sono. Abbiamo finito la nostra spesa. Torniamo a casa e ci accorgiamo che ci siamo completamente scordati le pere!!! La macedonia non si potrà fare e voi stasera morirete di fame.

Ok, torniamo alla programmazione. Come possiamo risolvere questo increscioso problema? Semplice, basta lavorare al contrario: facendo un ciclo che prende in considerazione (uno alla volta) tutti gli elementi dell’array a partire dall’ultimo per arrivare al primo, questo problema non ci tocca minimamente.

Proviamo con la nostra lista della spesa.

L’indice della lista è 5.

  1. Mele
  2. Arance
  3. Banane
  4. Pere
  5. Fragole

le fragole ci sono, andiamo avanti.

L’indice della lista è 4.

  1. Mele
  2. Arance
  3. Banane
  4. Pere
  5. Fragole

anche le pere ci sono, avanti!

L’indice della lista è 3.

  1. Mele
  2. Arance
  3. Banane
  4. Pere
  5. Fragole

anche stavolta le Banane non ci sono. Cancelliamo questo frutto maledetto e aggiorniamo la numerazione della lista.

  1. Mele
  2. Arance
  3. Pere
  4. Fragole

L’indice ora è 2.

  1. Mele
  2. Arance
  3. Pere
  4. Fragole

le arance ci sono. Non farò l’ultimo passaggio, il succo del ragionamento l’ho affrontato. Dovrebbe essere chiaro anche per voi ora! Ora, non possiamo forzare il funzionamento di un ciclo for each. Per definizione questi partono dal primo elemento per arrivare all’ultimo.

Cambiamo quindi istruzione. Usiamo un bel while!

var i:int = army.length - 1;    //int significa "Numero Intero"
var enemy:Enemy;
while ( i > -1 )
{
  enemy = army[i];
  //altro codice qui.
  i = i - 1;
}

Il codice che vedete è facile da capire. Ma vi voglio bene e spiegherò comunque tutto.

  • var i:int indica una variabile numerica, che consente l’uso di valori esclusivamente interi. (1 e 5 ok, 2.38 non è intero);
  • army.lenght – 1 indica la grandezza del nostro array. Il “-1” che andiamo a sottrarre serve perché con il nostro ciclo arriviamo ad army[0], e non army[1]!
  • var enemy:Enemy è la variabile che useremo sulla quale fare le operazioni. Non ancora le diamo una referenza;
  • while ( i > –1) è il ciclo che scandaglierà il nostro array elemento per elemento, a partire dall’ultimo elemento fino ad arrivare allo 0 (l’ultimo numero maggiore di –1 :) )
  • i = i – 1 è invece l’operazione di decremento che ci serve per tornare indietro nel nostro array. Se vi scordate questa istruzione flash inizierà a gestire un loop infinito che durerà 30 secondi, mandando poi il gioco in crash;

Vediamo come appare la nostra funzione onTick, adesso.

var i:int = army.length - 1;
var enemy:Enemy;
while ( i > -1 )
{
  enemy = army[i];
  enemy.moveABit();
  if ( PixelPerfectCollisionDetection.isColliding( avatar, enemy, this, true ) )
  {
    gameTimer.stop();
    avatarHasBeenHit = true;
  }
  if ( enemy.y > 350 )
  {
    //codice per la rimozione del nemico
  }
  i = i - 1;
}

Bene, l’ultima cosa che ci rimane da fare è trovare il codice necessario alla rimozione del nostro nemico. L’istruzione, che metteremo nel blocco commentato poco sopra, sarà:
if ( enemy.y > 350 )
{
  army.splice( i, 1 );
}

Splice è la funzione adatta. Il primo parametro indica il punto di inizio per “l’eliminazione” e la seconda cifra (nel nostro caso 1) indica il numero di elementi da cancellare a partire dal punto i.

Salvate e testate il vostro gioco, allungando verticalmente la finestra, ed iniziate a giocare.

AvoiderGame_Part12_07

I nostri nemici vengono rimossi dall’array, ma non dallo schermo! (la rimozione è testimoniata dal fatto che, essendo rimossi dall’array, non vengono più mossi sullo schermo, fermandosi in basso) Questo è ovvio, dato che li abbiamo precedentemente addChild-ati.

Per cui, rimuoviamoli!

if ( enemy.y > 350 )
{
  removeChild( enemy );
  army.splice( i, 1 );
}

Et voilà, il vostro gioco ora funzionerà perfettamente e non soffrirà dei rallentamenti per questa svista. Continuiamo con la nostra ottimizzazione.


Continuiamo la nostra Pulizia

Quello che abbiamo fatto fino ad adesso, per quanto possa sembrare poco, è un bel lavoro di ottimizzazione del nostro codice. Lo rende decisamente più corretto, ma c’è ancora un po’ di roba da fare. Stavolta parliamo di eventi.

Quando aggiungiamo un EventListener, infatti, le funzioni vengono (in un certo senso) viste come degli oggetti. Fin quando l’EventListener esiste anche l’oggetto associato a quella funzione, quindi, esisterà. Fortunatamente, c’è un modo semplice e veloce per risolvere questo problema: si chiama la “weak reference”, in Italiano “riferimento debole”.

In poche parole, invece di rimanere in memoria, una funzione che ha un sacco di weak reference viene rimossa. A livello di codice bisogna solo modificare l’istruzione addEventListener:

Da questo formato

addEventListener( TipoEvento.EVENTO, unaFunzione );

a questo
addEventListener( TipoEvento.EVENTO, unaFunzione, false, 0, true );  //weak ref

Ignorate il terzo ed il quarto parametro, per ora non è necessario capirli. Il quinto parametro, se impostato su “true”, provvede all’uso della weak reference.

Nota: se volete più informazioni sui due parametri, date un occhiata Qui.

Come Michael e Grant Skinner, raccomando l’uso di queste weak reference, in quanto più spazio c’è in memoria e meglio è. Non c’è bisogno di sovraccaricare un sistema se non ci sono determinate necessità, vero?

Ecco la nostra document class dopo il “restyling”:

package
{
  //Avoider Game Tutorial, by Michael James Williams
  //http://gamedev.michaeljameswilliams.com

  import flash.display.MovieClip;
  import flash.events.Event;
  import flash.events.ProgressEvent;
  public class DocumentClass extends MovieClip
  {
    public var menuScreen:MenuScreen;
    public var playScreen:AvoiderGame;
    public var gameOverScreen:GameOverScreen;
    public var loadingProgress:LoadingProgress;

    public function DocumentClass()
    {
      loadingProgress = new LoadingProgress();
      loadingProgress.x = 200;
      loadingProgress.y = 150;
      addChild( loadingProgress );
      loaderInfo.addEventListener( Event.COMPLETE, onCompletelyDownloaded, false, 0, true );
      loaderInfo.addEventListener( ProgressEvent.PROGRESS, onProgressMade, false, 0, true );
      stage.stageFocusRect = false;
    }

    public function onCompletelyDownloaded( event:Event ):void
    {
      removeChild( loadingProgress );
      gotoAndStop(3);
      showMenuScreen();
    }

    public function onProgressMade( progressEvent:ProgressEvent ):void
    {
      loadingProgress.setValue( Math.floor( 100 * loaderInfo.bytesLoaded / loaderInfo.bytesTotal ) );
    }

    public function showMenuScreen():void
    {
      menuScreen = new MenuScreen();
      menuScreen.addEventListener( NavigationEvent.START, onRequestStart, false, 0, true );
      menuScreen.x = 0;
      menuScreen.y = 0;
      addChild( menuScreen );

      stage.focus = menuScreen;
    }

    public function onAvatarDeath( avatarEvent:AvatarEvent ):void
    {
      var finalScore:Number = playScreen.getFinalScore();
      var finalClockTime:Number = playScreen.getFinalClockTime();

      gameOverScreen = new GameOverScreen();
      gameOverScreen.addEventListener( NavigationEvent.RESTART, onRequestRestart, false, 0, true );
      gameOverScreen.x = 0;
      gameOverScreen.y = 0;
      gameOverScreen.setFinalScore( finalScore );
      gameOverScreen.setFinalClockTime( finalClockTime );
      addChild( gameOverScreen );

      removeChild( playScreen );
      playScreen = null;

      stage.focus = gameOverScreen;
    }

    public function onRequestStart( navigationEvent:NavigationEvent ):void
    {
      playScreen = new AvoiderGame();
      playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath, false, 0, true );
      playScreen.x = 0;
      playScreen.y = 0;
      addChild( playScreen );

      removeChild( menuScreen );
      menuScreen = null;

      stage.focus = playScreen;
    }

    public function onRequestRestart( navigationEvent:NavigationEvent ):void
    {
      restartGame();
    }

    public function restartGame():void
    {
      playScreen = new AvoiderGame();
      playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath, false, 0, true );
      playScreen.x = 0;
      playScreen.y = 0;
      addChild( playScreen );

      removeChild( gameOverScreen );
      gameOverScreen = null;

      stage.focus = playScreen;
    }
  }
}

e qui invece potete osservare la classe AvoiderGame rimessa a nuovo:
package
{
  import flash.display.MovieClip;
  import flash.utils.Timer;
  import flash.events.TimerEvent;
  import flash.ui.Mouse;
  import flash.events.KeyboardEvent;
  import flash.ui.Keyboard;
  import flash.events.Event;
  import flash.media.SoundChannel;

  public class AvoiderGame extends MovieClip
  {
    public var army:Array;
    public var enemy:Enemy;
    public var avatar:Avatar;
    public var gameTimer:Timer;
    public var useMouseControl:Boolean;
    public var downKeyIsBeingPressed:Boolean;
    public var upKeyIsBeingPressed:Boolean;
    public var leftKeyIsBeingPressed:Boolean;
    public var rightKeyIsBeingPressed:Boolean;
    public var backgroundMusic:BackgroundMusic;
    public var bgmSoundChannel:SoundChannel;  //bgm for BackGround Music
    public var enemyAppearSound:EnemyAppearSound;
    public var sfxSoundChannel:SoundChannel;  //sfx for Sound FX
    public var currentLevelData:LevelData;

    public function AvoiderGame()
    {
      currentLevelData = new LevelData( 1 );
      setBackgroundImage();

      backgroundMusic = new BackgroundMusic();
      bgmSoundChannel = backgroundMusic.play();
      bgmSoundChannel.addEventListener( Event.SOUND_COMPLETE, onBackgroundMusicFinished, false, 0, true );
      enemyAppearSound = new EnemyAppearSound();

      downKeyIsBeingPressed = false;
      upKeyIsBeingPressed = false;
      leftKeyIsBeingPressed = false;
      rightKeyIsBeingPressed = false;

      useMouseControl = false;
      Mouse.hide();
      army = new Array();

      avatar = new Avatar();
      addChild( avatar );
      if ( useMouseControl )
      {
        avatar.x = mouseX;
        avatar.y = mouseY;
      }
      else
      {
        avatar.x = 200;
        avatar.y = 250;
      }

      gameTimer = new Timer( 25 );
      gameTimer.addEventListener( TimerEvent.TIMER, onTick, false, 0, true );
      gameTimer.start();

      addEventListener( Event.ADDED_TO_STAGE, onAddToStage, false, 0, true );
    }

    public function onBackgroundMusicFinished( event:Event ):void
    {
      bgmSoundChannel = backgroundMusic.play();
      bgmSoundChannel.addEventListener( Event.SOUND_COMPLETE, onBackgroundMusicFinished, false, 0, true );
    }

    public function onAddToStage( event:Event ):void
    {
      addEventListener( KeyboardEvent.KEY_DOWN, onKeyPress, false, 0, true );
      addEventListener( KeyboardEvent.KEY_UP, onKeyRelease, false, 0, true );
    }

    public function onKeyPress( keyboardEvent:KeyboardEvent ):void
    {
      if ( keyboardEvent.keyCode == Keyboard.DOWN )
      {
        downKeyIsBeingPressed = true;
      }
      else if ( keyboardEvent.keyCode == Keyboard.UP )
      {
        upKeyIsBeingPressed = true;
      }
      else if ( keyboardEvent.keyCode == Keyboard.LEFT )
      {
        leftKeyIsBeingPressed = true;
      }
      else if ( keyboardEvent.keyCode == Keyboard.RIGHT )
      {
        rightKeyIsBeingPressed = true;
      }
    }

    public function onKeyRelease( keyboardEvent:KeyboardEvent ):void
    {
      if ( keyboardEvent.keyCode == Keyboard.DOWN )
      {
        downKeyIsBeingPressed = false;
      }
      else if ( keyboardEvent.keyCode == Keyboard.UP )
      {
        upKeyIsBeingPressed = false;
      }
      else if ( keyboardEvent.keyCode == Keyboard.LEFT )
      {
        leftKeyIsBeingPressed = false;
      }
      else if ( keyboardEvent.keyCode == Keyboard.RIGHT )
      {
        rightKeyIsBeingPressed = false;
      }
    }

    public function onTick( timerEvent:TimerEvent ):void
    {
      gameClock.addToValue( 25 );
      if ( Math.random() < currentLevelData.enemySpawnRate )
      {
        var randomX:Number = Math.random() * 400;
        var newEnemy:Enemy = new Enemy( randomX, -15 );
        army.push( newEnemy );
        addChild( newEnemy );
        gameScore.addToValue( 10 );
        sfxSoundChannel = enemyAppearSound.play();
      }
      if ( useMouseControl )
      {
        avatar.x = mouseX;
        avatar.y = mouseY;
      }
      else
      {
        if ( downKeyIsBeingPressed )
        {
          avatar.moveABit( 0, 1 );
        }
        else if ( upKeyIsBeingPressed )
        {
          avatar.moveABit( 0, -1 );
        }
        else if ( leftKeyIsBeingPressed )
        {
          avatar.moveABit( -1, 0 );
        }
        else if ( rightKeyIsBeingPressed )
        {
          avatar.moveABit( 1, 0 );
        }
      }

      var avatarHasBeenHit:Boolean = false;
      var i:int = army.length - 1;
      var enemy:Enemy;
      while ( i > -1 )
      {
        enemy = army[i];
        enemy.moveABit();
        if ( PixelPerfectCollisionDetection.isColliding( avatar, enemy, this, true ) )
        {
          gameTimer.stop();
          avatarHasBeenHit = true;
        }
        if ( enemy.y > 350 )
        {
          removeChild( enemy );
          army.splice( i, 1 );
        }
        i = i - 1;
      }
      if ( avatarHasBeenHit )
      {
        bgmSoundChannel.stop();
        dispatchEvent( new AvatarEvent( AvatarEvent.DEAD ) );
      }

      if ( gameScore.currentValue >= currentLevelData.pointsToReachNextLevel )
      {
        currentLevelData = new LevelData( currentLevelData.levelNum + 1 );
        setBackgroundImage();
      }
    }

    public function setBackgroundImage():void
    {
      if ( currentLevelData.backgroundImage == "blue" )
      {
        backgroundContainer.addChild( new BlueBackground() );
      }
      else if ( currentLevelData.backgroundImage == "red" )
      {
        backgroundContainer.addChild( new RedBackground() );
      }
    }

    public function getFinalScore():Number
    {
      return gameScore.currentValue;
    }

    public function getFinalClockTime():Number
    {
      return gameClock.currentValue;
    }
  }
}

Le altre classi da rimettere a nuovo sono GameOverScreen e MenuScreen. Penso tu sia capace di farlo da solo, anche perchè c’è da gestire solo un EventListener per classe :)

E così abbiamo gestito la nostra GarbageCollection! Ora l’ultimo ritocco ed è finita.


Dichiariamo Manualmente l’istanza sullo Stage

Vi ricordate taaaaaanto, ma tanto tempo fa, durante il nostro primo incontro, quando vi dissi che dovevamo controllare di aver selezionato “Dichiara automaticamente le istanze sullo stage” tramite il menù “File > Impostazioni Pubblicazione > Flash > Impostazioni”?  Bene, dopo ben 12 lezioni stiamo andando a deselezionarlo.

Ma a che cosa serve?  Date un occhiata al simbolo “PlayScreen”. Contiene un oggetto Score, un oggetto Clock. Nessun Avatar. Se diamo un occhiata alla classe corrispondente, AvoiderGame, noterete che ha il codice “public var avatar:Avatar”, mentre non troviamo nulla di corrispondente per Score e Clock.

Dunque, selezionando “Dichiara automaticamente le istanze sullo stage” facciamo in modo che flash inserisca le linee di codice”public var clock:Clock;” e “public var score:Score;” automaticamente, quando faremo partire il gioco. Deselezionandolo, invece, dovremo dichiarare tutto da noi.

Perché, quindi, annullare questo aiuto e assegnarci del lavoro extra? Semplicemente perché l’editor che ci mette a disposizione Flash per lavorare è decisamente scarso, rispetto ad altri in circolazione. Ci sono programmi come FlashDevelop, per esempio, che fanno dei lavori allucinanti: import automaticamente inseriti, eventi creati in base alle necessità degli utenti e tante altre feature interessanti.

Per trarre il massimo dall’uso di questi programmi però, dobbiamo evitare di far dichiarare a flash le istanze. Per questo deselezionate la voce e provate a salvare tutto per poi testare. Come potete immaginare riceverete un sacco di errori.

AvoiderGame_Part12_11

Quello che dovete fare è semplice. Fare il doppio click su ognuno degli errori e per l’oggetto interessato modificare il codice della classe nel quale veniamo portati, aggiungendo un “public var e.c.c.”. Per esempio il primo errore che otteniamo noi è:

1120: Access of undefined property gameClock.

Doppio-clicchiamo e ci ritroviamo nella classe AvoiderGame, linea 122.
gameClock.addToValue( 25 );

Andiamo quindi all’inizio del file ed aggiungiamo al posto giusto la dichiarazione di quest’altra variabile.
public class AvoiderGame extends MovieClip
{
  public var gameClock:Clock;

Ripetete il processo per tutti gli errori e non avrete più problemi! Non ci metterete molto, non vi preoccupate.

E così, eccoci giunti alla fine. Le idee per ampliare il nostro gioco sono tante, e sul sito originale di Michael James Williams ne sono arrivate a palate. Quello che vi consiglio, quindi, è di iniziare a leggere qualcosa anche in inglese, dato che nel bene e nel male è sempre la lingua dei programmatori.

È stato un piacere ed un onore curare questa traduzione e spero che non sia rivelata troppo difficile. In certi casi ho esposto dei concetti che, affrontati la prima volta, sono tosti da mandare giù. Nonostante tutto ho cercato di essere il più chiaro possibile e di fare esempi altrettanto cristallini.

Potete trovare i file di questa lezione Qui, come al solito.

Vi saluto, promettendovi altre guide (spero altrettanto interessanti) sull’argomento!

Grazie dell’ascolto!

  • Share/Bookmark

DigitalNoise.it parla di me!

Ciao a tutti! Faccio un breve aggiornamento per riportare l’articolo che i ragazzi di DigitalNoise hanno fatto su di noi. Un grazie infinito, mi fa sempre piacere vedere che faccio un lavoro non del tutto inutile :D

Potete trovare l’articolo qua:

http://www.digitalnoise.it/2010/03/10/guida-alla-realizzazione-di-giochi-in-flash-di-mjwilliams-tradotta-in-italiano/

Ciao!

  • Share/Bookmark

Creare un Gioco in Flash – Parte 11: Salvare e Caricare Informazioni

Ci siamo quasi… siamo all’undicesima lezione e a breve avremo finito al 100% il nostro primo gioco in Flash. La volta scorsa abbiamo aggiunto un elemento davvero importante: la progressione. Inserendo nuovi livelli abbiamo fatto in modo di creare delle situazioni in cui il giocatore non riesce a terminare il gioco in una sola volta.

Oggi introdurremo un concetto molto utile: il salvataggio di informazioni per poterle riusare successivamente in futuro. Ovviamente applicheremo questa nozione al nostro gioco, memorizzando (per ogni utente che gioca) il suo punteggio migliore. Per raggiungere il nostro obiettivo useremo un oggetto specifico, chiamato SharedObject.

Come realizzare il tutto? Continuate a leggere il nostro articolo.

Prendete i file di backup della lezione precedente (nell’articolo precedente) oppure prendete i vostri file di backup e partiamo!


Una cosa Davvero Semplice

Fortunatamente per voi, posso dire da subito che la realizzabilità di questa caratteristica è davvero semplice rispetto a tante altre: prevedo un articolo più corto delle volte precedenti. Partiamo subito con il lavoro e apriamo il nostro file .fla.

Nella libreria, aprite la GameOverScreen ed entrate quindi in modalità modifica. Aggiungete due box per il testo: una sarà statica, e riporterà qualcosa come “Punteggio Migliore: “, l’altra invece dinamica ospiterà, appunto, il punteggio migliore (numerico). Dategli come nome d’istanza “bestScore”.

AvoiderGame_Part11_01

Qualcosa del genere, per intenderci ;)

I dati che noi salveremo grazie all’oggetto SharedObject verranno salvati in una cartella ben precisa del sistema operativo. Per salvare o accedere a questi dati, come già detto, useremo l’oggetto SharedObject in svariati modi. Per fare le cose in maniera più semplice, aggiungeremo tutto il codice necessario nel file GameOverScreen.as.

Per prima cosa quindi importiamo l’oggetto SharedObject e dichiariamone un istanza a livello di classe:

package
{
  import flash.display.MovieClip;
  import flash.display.SimpleButton;
  import flash.events.MouseEvent;
  import flash.text.TextField;
  import flash.ui.Mouse;
  import flash.net.SharedObject;
  public class GameOverScreen extends MovieClip
  {
    public var sharedObject:SharedObject;

Il passo successivo è “collegare” quest’oggetto al contenuto presente sul nostro hard disk. Ovviamente considerate che, come il nome può suggerire, questo è un oggetto “condiviso”. Vuol dire che può essere utilizzato anche da altre applicazioni, per cui dovete scegliere un nome ben preciso che difficilmente verrà usato da altri. Noi per esempio abbiamo deciso “mjwScores”. Traduciamo il ragionamento in codice ed ecco cosa abbiamo:
public function GameOverScreen()
{
  Mouse.show();
  restartButton.addEventListener( MouseEvent.CLICK, onClickRestart );
  sharedObject = SharedObject.getLocal( "mjwScores" );
}

Notate che, a sinistra dell’uguale abbiamo “sharedObject” con la s piccola, mentre a destra “SharedObject” con la S grande. So che probabilmente l’avete già capito, ma ricordate che a sinistra abbiamo l’oggetto, mentre a destra abbiamo la classe ;)

getLocal è invece una funzione: proprio come abbiamo potuto realizzare delle variabili statiche (utilizzabili senza creare un istanza della classe) possiamo trovare delle funzioni statiche. Il parametro che passiamo tra le parentesi è una stringa indicante il nome dell’identificativo del nostro punteggio. “mjwScores”, appunto.

Piccola Nota: ritornando un secondo al discorso di prima, non scervellatevi troppo se il nome scelto per memorizzare i dati non sembra così “originale”: considerate che flash esegue un controllo sia sul nome della variabile che sull’URL della pagina dove si trova il vostro gioco ;) Siti come Newgrounds e Kongregate hanno inoltre dei sistemi di protezione ulteriori.

Ora che abbiamo l’accesso a questa risorsa sul nostro hard disk, mettiamoci qualcosa dentro! L’istanza del nostro SharedObject possiede una variabile chiamata “data” nella quale possiamo mettere tutto ciò che vogliamo. Nel codice di esempio potete capire al volo di cosa parlo:

public function setFinalScore( scoreValue:Number ):void
{
  finalScore.text = scoreValue.toString();
  sharedObject.data.bestScore = scoreValue;
}

Potete aggiungere al vostro oggetto quello che volete, semplicemente con la formula

sharedObject.data.nome_che_volete = valore_che_volete

Semplicissimo, vero? ;) Dovete tenere in mente una cosa però: fin quando non chiuderete il file swf (uscendo dal gioco o dalla pagina dove si trova) le modifiche non verranno salvate. Possiamo tranquillamente aggirare quest’ostacolo aggiungendo una semplice istruzione: sharedObject.flush().

public function setFinalScore( scoreValue:Number ):void
{
  finalScore.text = scoreValue.toString();
  sharedObject.data.bestScore = scoreValue;
  sharedObject.flush();
}

Questo permetterà istantaneamente il salvataggio dell’informazione. Ora che l’abbiamo salvata però dobbiamo mostrarla, altrimenti che lo usiamo a fare questo oggetto?
public function setFinalScore( scoreValue:Number ):void
{
  finalScore.text = scoreValue.toString();
  sharedObject.data.bestScore = scoreValue;
  bestScore.text = sharedObject.data.bestScore.toString();
  sharedObject.flush();
}

Salvando e testando il gioco, il migliore punteggio equivarrà sempre a quello della partita appena svolta.

AvoiderGame_Part11_02

La cosa non può rimanere così semplice, per cui dobbiamo pensare ad un approccio diverso! Il punteggio migliore deve essere aggiornato solo se realmente migliore di quello precedente! Per questo motivo dobbiamo aggiungere qualche controllo specifico nel metodo setFinalScore():

public function setFinalScore( scoreValue:Number ):void
{
  finalScore.text = scoreValue.toString();
  if ( scoreValue > sharedObject.data.bestScore )
  {
    sharedObject.data.bestScore = scoreValue;
  }
  bestScore.text = sharedObject.data.bestScore.toString();
  sharedObject.flush();
}

Come potete vedere “scoreValue” è il nostro punteggio attuale, che viene paragonato al punteggio migliore attualmente in memoria. Se è realmente maggiore, quindi il migliore, viene assegnato alla variabile data dell’oggetto sharedObject.

Altrimenti tutto rimane com’è e viene visualizzato il punteggio migliore precedente. Insomma, una cosa facile facile. Ecco il nostro risultato su schermo:

AvoiderGame_Part11_03

In linea di massima quindi funziona tutto, però c’è una cosa che dovete contare. Ragionate con me: eseguite per la prima volta il gioco, quindi non abbiamo nessun valore per il punteggio migliore! Cosa succede se, a fine partita, andiamo a controllare il punteggio precedente (che non esiste) ? ERRORE!

Nello specifico l’errore sarà il seguente:

 TypeError: Error #1010: A term is undefined and has no properties.

Che sbadati. Correggiamo immediatamente questo orrore!
public function setFinalScore( scoreValue:Number ):void
{
  finalScore.text = scoreValue.toString();
  if ( sharedObject.data.bestScore == null )
  {
    sharedObject.data.bestScore = scoreValue;
  }
  else if ( scoreValue > sharedObject.data.bestScore )
  {
    sharedObject.data.bestScore = scoreValue;
  }
  bestScore.text = sharedObject.data.bestScore.toString();
  sharedObject.flush();
}

Come potete vedere ho aggiunto un controllo, verificando innanzitutto se sharedObject.data.bestScore è uguale a null (ovvero è la prima volta che giochiamo). Quindi provvediamo a fare gli altri controlli precedentemente scritti. È cambiato molto poco in realtà :)

Testate tutto e provate, ora funzionerà alla perfezione. Chiudete tutto e riaprite il file, ri-testando il vostro gioco. Il punteggio migliore sarà ancora lì, memorizzato ;)

AvoiderGame_Part11_04

Et voilà ;)


Dettagli sui Salvataggi

Penso che, da buoni sviluppatori che siete (o volete diventare), avete qualche curiosità riguardante questi salvataggi tramite lo SharedObject. Beh, in questa parte dell’articolo riporterò un po’ di informazioni sicuramente utili.

Per prima cosa, ecco dove i dati dell’oggetto vengono memorizzati:

  • WindowsXP: C:\Documents and Settings\nomeUtente\Application data\Macromedia\Flash Player\#SharedObjects
  • Windows Vista: C:\Utenti\nomeUtente\AppData\Roaming\Macromedia\Flash Player\#SharedObjects
  • Mac OS/X: ~/Library/Preferences/Macromedia/Flash Player/#SharedObjects/
  • Unix/Linux: ~/.macromedia/Flash_Player/#SharedObjects/

Queste sono le cartelle utilizzate di default.

Considerate, inoltre, che se contate di memorizzare una grande mole di dati potreste avere dei problemi: flash, infatti, permette al massimo una dimensione dei file memorizzati di 100KB per il vostro file .swf. Fortunatamente, abbiamo la possibilità di individuare per tempo questi errori.

Guardate di nuovo l’errore uscito precedentemente: è stato “lanciato” un TypeError:

 TypeError: Error #1010: A term is undefined and has no properties.

Prima di concludere questo articolo, quindi, impareremo ad utilizzare uno strumento decisamente utile per “rilevare” gli errori che vengono lanciati in determinati contesti: il blocco “try – catch”. È proprio dentro questo blocco che gestiamo la possibilità di ottenere degli errori. Prima il codice e poi la spiegazione:
public function setFinalScore( scoreValue:Number ):void
{
  finalScore.text = scoreValue.toString();
  try
  {
    if ( sharedObject.data.bestScore == null )
    {
      sharedObject.data.bestScore = scoreValue;
    }
    else if ( scoreValue > sharedObject.data.bestScore )
    {
      sharedObject.data.bestScore = scoreValue;
    }
    bestScore.text = sharedObject.data.bestScore.toString();
    sharedObject.flush();
  }
  catch ( sharedObjectError:Error )
  {
    trace( "Caught this error:", sharedObjectError.name, sharedObjectError.message );
  }
}

Ecco cosa succede: flash tenta di eseguire tutto il codice che si trova nel blocco “try”. Nel caso vengono fuori degli errori di molteplice natura, allora viene eseguito il codice che si trova nel blocco “catch”, che in questo caso serve solo (tramite trace) a riportare nella console di output il testo dell’errore.

Una soluzione abbastanza sicura, quindi. In più, nel caso ci dovessero essere dei problemi, aggiungiamo quest’altra riga di codice al blocco catch:

catch ( sharedObjectError:Error )
{
  trace( "Caught this error:", sharedObjectError.name, sharedObjectError.message );
  bestScore.text = "???";
}

In questo modo, nel caso ci siano dei problemi (raramente se ne verificheranno in questo caso) al posto del punteggio migliore ci ritroveremo dei punti interrogativi, per segnalare un problema in fase di lettura. Ripeto, sarà molto, molto raro che accada.

AvoiderGame_Part11_06

Una schermata che difficilmente vedremo.


Sfide

Oltre al punteggio migliore c’è anche altro che è possibile mostrare su schermo: il miglior tempo o magari anche il numero del livello più alto al quale siamo riusciti ad arrivare. Non dovrebbe essere difficile! O ancora, si potrebbe memorizzare il numero del livello attuale per riproporlo la prossima volta che il giocatore giocherà al nostro capolavoro!

Anche oggi ce l’abbiamo fatta. Ormai manca solo l’ultima lezione e dopodiché questa piccola ma piacevole avventura sarà terminata. Scaricate da Qui i file della lezione di oggi e aspettateci nel prossimo articolo, nel quale parleremo di uno strumento davvero potente: il Garbage Collector.

Ciao!

  • Share/Bookmark

PHP – Autocompleter in stile Google (PHP + MySQL) funzionante

Qualche giorno fa, per esigenze di lavoro, mi sono ritrovato a dover implementare un “autocompleter” per uno script in php. Ho cercato un po’ in giro e ne ho trovati sicuramente tanti, che però mi hanno dato un sacco di problemi in fase di implementazione.

Alcuni di voi si chiederanno: cosa diavolo è un Autocompleter?

È uno strumento, generalmente realizzato con l’ausilio di javascript ed altri linguaggi, che permette, data la stringa di ricerca inserita, di visualizzare alcuni risultati “possibili” al di sotto della barra di ricerca. Giusto per farvi un esempio ecco l’autocompleter di Google in azione:

Senza titolo-2

Ho notato che molto spesso è una feature decisamente ben accolta e richiesta: per questo motivo vi riporto il metodo da me utilizzato per implementare qualcosa del genere.


File Necessari

Prima di partire dobbiamo prendere tutti i file che ci servono. Innanzitutto abbiamo bisogno di jQuery, libreria ormai famosissima nell’ambito web, e nello specifico alcune sue parti:

Una volta scaricati tutti i file potete posizionarli nella cartella nella quale accederete successivamente dal file dove volete posizionare il form interessato. Per esempio, il form con la textbox che mi interessa li ho in un file chiamato index.php e tutti questi file da includere li ho messi nella cartella “js”.

Ecco quindi come appaiono le cartelle:

Cartella Principale

Senza titolo-22

Cartella “Lib”

Senza titolo-3

^ Cliccare per Ingrandire ^


Cosa Scrivere nei File

Apriamo il nostro index.php e per prima cosa copiamo queste righe di codice per includere tutti i file necessari:

<link href="js/jquery.autocomplete.css" rel="stylesheet" type="text/css" media="screen">
<script src="js/jquery-1.3.2.min.js" type="text/javascript"></script>
<script src="js/jquery.metadata.js" type="text/javascript"></script>
<script src="js/jquery.autocomplete.js" type="text/javascript"></script>
<script src="js/autocomplete.custom.js" type="text/javascript"></script>

dopodichè dobbiamo quindi provvedere ad assegnare al campo che ci interessa la classe “suggest” e quindi creare un altro file php da usare per il “prelievo” delle informazioni, qualsiasi sia la sorgente.

Nel file principale dove abbiamo la nostra textbox, ecco l’istruzione che dobbiamo trascrivere:

<input name="suggest1" id="suggest1" class="suggest" alt="dati.php" type="text">

Infine, nella stessa cartella del nostro file index.php, creiamo un nuovo file chiamato “dati.php”. Il suo contenuto sarà il seguente:

$q = strtolower($_GET['q']);
if (!$q) return;

$items = array(
  'Francesco',
  'Lorenzo',
  'Paolo',
  'Francescopaolo',
  'Dottor X',
  'Action Man',
  'PongiBonzi',
  'Popof'
);

foreach ($items as $value)
{
    if (strpos(strtolower($value), $q) !== false)
  {
          echo "$value\n";
      }
}

Ho messo qualche valore simile (Francesco e Francescopaolo) in modo tale da evidenziare il fatto che l’autocompleter provvederà a proporre entrambi se si scrive “Francesc”, per esempio. Il collegamento tra i due file avviene in fase di definizione dell’attributo “alt” del tag input precedentemente usato.

Mi sembra inutile dirvi che è tranquillamente possibile, al posto dell’uso dell’array, effettuare una query su un database e prelevare i risultati.

Byez.

  • Share/Bookmark

Creare un Gioco in Flash – Parte 10: Livelli Multipli

Fino ad ora abbiamo aggiunto un sacco di elementi degni di un gioco nel vero senso della parola: dagli elementi di gameplay basilari fino ad arrivare ai suoni e ad un sofisticato preloader. Tuttavia, manca un elemento a mio parere (e non solo) fondamentale per un buon gioco: la progressione.

La riuscita del nostro gioco infatti dipende essenzialmente da una dose di fortuna, prontezza dei riflessi e subito dopo qualche secondo abbiamo mostrato al mondo tutto quello che c’era da mostrare. E non solo dal punto di vista del gameplay, ma anche graficamente e.c.c.

Per questo motivo, in questa decima lezione della nostra guida impareremo ad aggiungere dei nuovi livelli di gioco per far divertire il nostro utente ancora di più. Se non avete seguito gli altri tutorial provvedete immediatamente a prendere i file necessari alla fine della lezione precedente. Altrimenti preleviamo i backup e via!


Ehi ma… di che diamine stai parlando? Livelli?

Prima di decidere l’aggiunta dei livelli al nostro gioco, diamo una bella occhiata sulle varie modalità tramite le quali altri sviluppatori di giochi hanno risolto questo problema. Partiamo da Dodge, nel quale inizialmente avremo a che fare con pochi nemici.

Dodge1

Una volta distrutti questi, il livello cambierà e avremo la possibilità di scambiare i punti di gioco per punti salute prima di iniziare il successivo. Ad ogni livello, inoltre, viene incrementato il numero di nemici presenti. Come se non bastasse, vengono introdotti anche nuovi tipi di nemici (immagine di seguito)!

Dodge

Amorphous+ invece funziona in modo differente. Si può scegliere quale nemico affrontare e quindi il livello viene deciso dall’utente prima ancora di iniziare il gioco.

Amorphous2 Amorphous1

Click sulle immagini per ingrandirle

Volendo fare altri esempi, invece, possiamo citare Four Second Frenzy, che presenta uno stile molto simile a Wario Ware: tanti mini giochi uno dopo l’altro con obiettivi completamente differenti.

4sf1

Un esempio di mini gioco di “Four Second Frenzy”

Una volta creati tutti questi piccoli micro games, vengono incorporati in una struttura generale che li controlla e che gestisce le vite del giocatore.

Ricapitolando, come potete vedere ci sono svariati modi per identificare un “livello”. Un cambio di livello può significare esclusivamente un cambio di grafica, oppure solo di gameplay o addirittura entrambi, per non parlare del caso in cui cambi davvero tutto.

Bisogna decidere cosa fare.


E allora, cosa faremo?

Basandoci su quello che abbiamo detto nelle parti precedenti del tutorial, la soluzione ideale per la nostra estensione è creare una classe per ogni livello. In questo modo, potremmo avere più classi (che estendono AvoiderGame ovviamente) dal nome AvoiderGameLevelOne ed AvoiderGameLevelTwo.

Successivamente quindi potremmo creare un evento del tipo NavigationEvent.NEXT_LEVEL ed eseguire quindi il cambiamento di livello. Ovviamente la classe predisposta al cambio e al controllo di tutto sarà la classe documento: il livello corretto verrà “sparato” tramite un istruzione come playScreen = AvoiderGameLevelTwo() (o qualsiasi livello serva), passando le informazioni come il punteggio ed il tempo.

Dovrebbe funzionare. Potrebbe.

Ma abbiamo un limite. Abbiamo costruito 10 livelli, arriviamo al decimo e poi? Il gioco finisce? Se avesse una storia, magari come un gioco d’avventura, le cose sarebbero fattibili. Ma il nostro gioco è più simile ad un arcade game, e noi vorremmo che continuasse.

Consideriamo quindi il design di un pilastro del mondo videoludico quale Tetris. Alla fine di ogni livello, i blocchi vanno più veloci e, in alcune versioni, l’immagine di sfondo (o il colore) cambiano. Creando un nuovo livello con lo stratagemma di “una classe per livello”, perderemmo le informazioni precedenti.

Per questo motivo l’approccio che utilizzeremo sarà il seguente:

  • finiremo il livello attuale
  • passeremo i dati importanti ad un altra struttura appositamente creata
  • riavvieremo il gioco con le informazioni prese dalla struttura

ed in questo modo non dovremmo perdere niente.

Potremmo basare la nostra struttura proprio su questo concept, però troviamo davvero limitante l’approccio “classe per livello”. Come diamine possiamo fare? Quando pensiamo alla classe AvoiderGame possiamo pensare ad essa come un insieme di regole ben definite, per ogni componente in essa presente.

Potremmo creare dei nuovi livelli semplicemente cambiando questi valori e queste regole in base al livello attuale:

if ( currentLevel == 1 )
{
  gameScore.addToValue( 10 );
}
else if ( currentLevel == 2 )
{
  gameScore.addToValue( 15 );
}
else if ( currentLevel == 3 )
{
  gameScore.addToValue( 22 );
}

Ma finiremmo per scrivere del codice “specifico” per il livello in giro per la classe, creando una gran confusione in più punti e rendendo un eventuale manutenzione praticamente impossibile. La migliore soluzione è quindi memorizzare tutte le informazioni specifiche in un “posto” separato dalle regole generiche, facendo in modo di richiedere questi valori da parte della classe AvoiderGame per il livello necessario ogni volta.

Creiamo nuove classi.


La Classe LevelData

Iniziamo la nostra lunga modifica effettuando un cambiamento innanzitutto visuale: l’immagine di sfondo del livello.

Apriamo il simbolo PlayScreen nella nostra libreria (ricordatevi che è collegata con la classe AvoiderGame). Attualmente, il background è disegnato direttamente sul PlayScreen. Volendo essere capaci di modificarlo dal codice, dovremmo creare un simbolo apposito. Selezionate tutto il background (facendo attenzione a non selezionare l’orologio ed il punteggio):

AvoiderGame_Part10_01

Per rendere la selezione un unico simbolo, quindi, cliccate sul menù “Modifica > Converti in Simbolo”, chiamatelo BackgroundContainer ed esportatelo per Actionscript con lo stesso nome. Ricordate una cosa importantissima: stiamo usando un preloader per il nostro gioco, quindi non esportate niente nel primo fotogramma. Quest’opzione deve rimanere sempre disattivata.

Entrate in modalità di modifica del PlayScreen e date all’istanza del BackgroundContainer il nome di “backgroundContainer”. Molto probabilmente si sarà automaticamente posizionato davanti a tutti gli altri contenuti del simbolo: cliccate su di esso con il pulsante destro del mouse, quindi selezionate “Disponi > Sposta Dietro”.

AvoiderGame_Part10_02

Quindi, riassumendo, il nostro PlayScreen contiene un clip di classe BackgroundContainer, che a sua volta contiene un altro movie clip che è il nostro “BlueBackground”. Ora, duplichiamo il nostro BlueBackground nella libreria ed entriamo in modalità di modifica nel nuovo simbolo duplicato.

Modificatelo come volete, io l’ho chiamato RedBackground e questo è il suo aspetto:

AvoiderGame_Part10_03

Salve tutto e testate, verificando che con il background blu funzioni tutto correttamente e senza intoppi. Ora dobbiamo passare al codice: creeremo la classe che contiene le informazioni specifiche dei livelli. Creiamo una nuova classe, che chiameremo LevelData.as. Serve davvero che vi dica dove dovete salvarla?

package
{
  public class LevelData
  {
    public function LevelData()
    {

    }
  }
}

Notate una cosa importante: non stiamo estendendo nulla, e non ce n’è bisogno. In questa classe infatti caricheremo solo dei dati relativi al livello che non richiedono import specifici. Creiamo una variabile pubblica chiamata “backgroundImage”:
package
{
  public class LevelData
  {
    public var backgroundImage:String;

    public function LevelData()
    {

    }
  }
}

Come facciamo a “collegare” a livello logico il numero del livello con il background? Ecco il codice che ci serve:
package
{
  public class LevelData
  {
    public var backgroundImage:String;

    public function LevelData( levelNumber:Number )
    {
      if ( levelNumber == 1 )
      {
        backgroundImage = "blue";
      }
      else if ( levelNumber == 2 )
      {
        backgroundImage = "red";
      }
    }
  }
}

Ogni volta che creeremo una nuova istanza della classe LevelData, quindi, passeremo come parametro del costruttore un numero indicante il livello in cui stiamo giocando, impostando la variabile “backgroundImage” con il nome del colore rispettivo (parlando dello sfondo del livello ;) ). Otterremo questa variabile passando per la classe AvoiderGame. Salvate quindi la classe e creiamo una nuova variabile a livello di classe nel file AvoiderGame.as:
public var currentLevelData:LevelData;

ed ecco l’istanza dei dati del livello.

Nella funzione del costruttore istanziamo l’oggetto in questo modo:

public function AvoiderGame()
{
  currentLevelData = new LevelData( 1 );

Volendo verificare anche con un semplice trace, attualmente la variabile currentLevelData.backgroundImage avrà il valore “blue”. Quello che ora faremo sarà aggiungere, in base a questa stringa, il giusto simbolo di background.
public function AvoiderGame()
{
  currentLevelData = new LevelData( 1 );
  if ( currentLevelData.backgroundImage == "blue" )
  {
    backgroundContainer.addChild( new BlueBackground() );
  }
  else if ( currentLevelData.backgroundImage == "red" )
  {
    backgroundContainer.addChild( new RedBackground() );
  }

Seguite bene i passi effettuati e non vi perdete. Ovviamente con la pratica sarà tutto più chiaro, per cui non vi spaventate se un concetto un po’ più avanzato come questo vi da problemi al primo colpo.

Quello che abbiamo fatto nella sezione di codice poco sopra è semplice: in base alla stringa pervenuta (“blue” o “red”) abbiamo addChild-ato di conseguenza il giusto background: con “blue” avremo un nuovo BlueBackground, e con “red” un nuovo RedBackground.

In questo passaggio l’uso del BackgroundContainter è spiegato: usando un oggetto contenitore, infatti, non dobbiamo portarlo dietro tutte le volte che creiamo (re-inizializziamo è più corretto :D ) un nuovo background. Non usando BackgroundContainer avremmo visto il background davanti a tutto e quindi riportarlo indietro ogni volta. Non è molto pratico.

Ad ogni modo, se salvate tutto e provate, il vostro gioco avrà lo sfondo di colore blu. Infatti il codice attuale è

currentLevelData = new LevelData( 1 );

Cambiamolo ora, in:
currentLevelData = new LevelData( 2 );

Salvate tutto e provate!

AvoiderGame_Part10_04

Wooooooooooooooooooooow.


Level Up!

Abbiamo appena visto come mostrare uno specifico livello di gioco. Ma come collegare il tutto logicamente e quindi andare al livello successivo durante la partita? Per prima cosa, dobbiamo decidere cosa il nostro giocatore dovrà fare per arrivare al livello successivo.

La scelta più ovvia è stata quella più semplice: una volta raggiunti un tot di punti, provvedere a mandare il giocatore al livello successivo. Cambiate il valore nell’istruzione “new LevelData”, passando 1 invece di 2.  In questo modo faremo iniziare il giocatore dal primo livello.

Ora, ritrovate la funzione onTick() e aggiungeteci questo codice alla fine:

  if ( avatarHasBeenHit )
  {
    bgmSoundChannel.stop();
    dispatchEvent( new AvatarEvent( AvatarEvent.DEAD ) );
  }

  if ( gameScore.currentValue &gt;= 150 )
  {
    currentLevelData = new LevelData( 2 );
    if ( currentLevelData.backgroundImage == "blue" )
    {
      backgroundContainer.addChild( new BlueBackground() );
    }
    else if ( currentLevelData.backgroundImage == "red" )
    {
      backgroundContainer.addChild( new RedBackground() );
    }
  }
}

Anzi, facciamo le cose ancora meglio: creiamo una nuova funzione con il codice come segue:
  if ( avatarHasBeenHit )
  {
    bgmSoundChannel.stop();
    dispatchEvent( new AvatarEvent( AvatarEvent.DEAD ) );
  }

  if ( gameScore.currentValue &gt;= 150 )
  {
    currentLevelData = new LevelData( 2 );
    setBackgroundImage();
  }
}

public function setBackgroundImage():void
{
  if ( currentLevelData.backgroundImage == "blue" )
  {
    backgroundContainer.addChild( new BlueBackground() );
  }
  else if ( currentLevelData.backgroundImage == "red" )
  {
    backgroundContainer.addChild( new RedBackground() );
  }
}

In questo modo non ripeteremo lo stesso codice del costruttore. Fate, ovviamente, la stessa cosa in quest’ultimo: cambiate il codice nel costruttore sostituendo le linee con l’unica necessaria a richiamare la funzione.

Salvate tutto e testate. Dopo aver raggiunto il vostro obiettivo, tuttavia, il gioco rallenterà in una maniera mostruosa. La spiegazione è semplice: una volta superati i 150 punti il controllo continuerà a ridare true sempre. Per questo motivo dall’istante in cui si raggiungeranno i 150 punti verrà ricreato il background e riposizionato (calcolando 25ms del timer) circa 40 volte al secondo.

Come risolvere questo problema? La mia soluzione consiste nel creare una variabile chiamata pointsToReachNextLevel, nella classe LevelData, ed impostarla in questo modo:

package
{
  public class LevelData
  {
    public var backgroundImage:String;
    public var pointsToReachNextLevel:Number;

    public function LevelData( levelNumber:Number )
    {
      if ( levelNumber == 1 )
      {
        backgroundImage = "blue";
        pointsToReachNextLevel = 150;
      }
      else if ( levelNumber == 2 )
      {
        backgroundImage = "red";
        pointsToReachNextLevel = 9999999;
      }
    }
  }
}

Useremo un numero decisamente alto ed esagerato temporaneamente, dato che non abbiamo un livello 3 ;)   Ora, tornando in AvoiderGame, impostate un controllo con un if per permettere al giocatore di passare di livello.
if ( gameScore.currentValue &gt;= currentLevelData.pointsToReachNextLevel )
{
  currentLevelData = new LevelData( 2 );
  setBackgroundImage();
}

Testate tutto e non dovreste avere problemi. Il nostro sistema funziona! Dopo la modifica alla grafica è giunto il momento, quindi, di effettuare qualche cambiamento a livello di gameplay. Cosa ne dite di modificare la frequenza dei nemici quando arriviamo al secondo livello?

Tutto quello che dobbiamo fare è aggiungere una nuova variabile alla classe LevelData ed impostarla:

package
{
  public class LevelData
  {
    public var backgroundImage:String;
    public var pointsToReachNextLevel:Number;
    public var enemySpawnRate:Number;

    public function LevelData( levelNumber:Number )
    {
      if ( levelNumber == 1 )
      {
        backgroundImage = "blue";
        pointsToReachNextLevel = 150;
        enemySpawnRate = 0.05;
      }
      else if ( levelNumber == 2 )
      {
        backgroundImage = "red";
        pointsToReachNextLevel = 9999999;
        enemySpawnRate = 0.1;
      }
    }
  }
}

… quindi usare questa variabile nella classe AvoiderGame:
if ( Math.random() &lt; currentLevelData.enemySpawnRate )
{
  var randomX:Number = Math.random() * 400;
  var newEnemy:Enemy = new Enemy( randomX, -15 );
  army.push( newEnemy );
  addChild( newEnemy );
  gameScore.addToValue( 10 );
  sfxSoundChannel = enemyAppearSound.play();
}

Et voilà!


Livelli Infiniti

Qualche centinaio di parole fa avevo sottolineato il fatto che usare una classe per livello può rivelarsi uno svantaggio: la mia affermazione non era stata buttata così e per ora possiamo contare due livelli. Proviamo adesso ad aggiungerne un altro paio.

Nella classe LevelData modifichiamo il codice in questo modo:

public function LevelData( levelNumber:Number )
{
  if ( levelNumber == 1 )
  {
    backgroundImage = "blue";
    pointsToReachNextLevel = 150;
    enemySpawnRate = 0.05;
  }
  else if ( levelNumber == 2 )
  {
    backgroundImage = "red";
    pointsToReachNextLevel = 350;
    enemySpawnRate = 0.1;
  }
  else if ( levelNumber == 3 )
  {
    backgroundImage = "blue";
    pointsToReachNextLevel = 600;
    enemySpawnRate = 0.13;
  }
  else if ( levelNumber == 4 )
  {
    backgroundImage = "red";
    pointsToReachNextLevel = 9999999;
    enemySpawnRate = 0.15;
  }
}

Dato che disponiamo di sue soli sfondi, ci limiteremo ad alternarli. Ovviamente potete farne più di due ed usarli come volete, fin quando modificate in maniera appropriata la funzione setBackgroundImage().

Tornando a noi, abbiamo un problema. Date un occhiata a questo codice:

if ( gameScore.currentValue &gt;= currentLevelData.pointsToReachNextLevel )
{
  currentLevelData = new LevelData( 2 );
  setBackgroundImage();
}

Man mano che andiamo avanti, nonostante si debba passare dal livello 2 al 3 oppure dal 3 al 4, il gioco continua a caricare il secondo livello ( l’istruzione new LevelData( 2 ); ) !!! Dobbiamo modificare un po’ la nostra classe, ed aggiungere una variabile che tenga traccia del numero del livello attuale.

In questo modo, potremmo richiamare un nuovo livello con un istruzione del tipo

new LevelData( currentLevelNumber + 1 );

Ovviamente metteremo anche questa variabile nella classe LevelData. Modificate il codice in questo modo:

public var backgroundImage:String;
public var pointsToReachNextLevel:Number;
public var enemySpawnRate:Number;
public var levelNum:Number;

public function LevelData( levelNumber:Number )
{
  levelNum = levelNumber;

Ho chiamato la variabile “levelNumber”, di tipo numerico. Nel file AvoiderGame.as, invece, usiamo il riferimento per questa variabile come segue:
if ( gameScore.currentValue &gt;= currentLevelData.pointsToReachNextLevel )
{
  currentLevelData = new LevelData( currentLevelData.levelNum + 1 );
  setBackgroundImage();
}

Testate il vostro gioco e tutto funzionerà ;) Certo, potete provare a raggiungere la bellezza di dieci milioni di punti, se non avete altro da fare :D

Pensavate davvero di finirla qua? Sapete quanto siamo pignoli da queste parti :) Per cui, perché non implementare a tutti gli effetti un numero infinito di livelli? Le cose si fanno poco più complicate: dobbiamo definire una regola generica per impostare questi valori. Se avete fatto le successioni algebriche (in realtà pure capire semplicemente la moltiplicazione aiuta molto) capirete di cosa sto parlando, altrimenti continuate a leggere e la pratica vi chiarirà le idee.

Date un occhiata al codice qui di seguito:

else if ( levelNumber == 4 )
{
  backgroundImage = "red";
  pointsToReachNextLevel = 770;
  enemySpawnRate = 0.15;
}
else
{
  backgroundImage = "blue";
  pointsToReachNextLevel = levelNumber * 200;
  enemySpawnRate = 0.6 - ( 2 / levelNumber );
}

Mi raccomando, massima attenzione al cambiare il valore del punteggio necessario perché lasciarlo a nove milioni e passa non è una buona idea ;) Ragionando un po’ con la matematica, noterete che al livello 5 il giocatore avrà bisogno di 1000 punti per arrivare al successivo; 1200 per il passare dal sesto al settimo e così via.

La regola generale sarà, quindi:

n * 200 = (punti da raggiungere per passare al livello n+1)

dove n = livello attuale

esempio pratico: per passare dal livello 7 al livello 8 avremo bisogno di 7*200 punti = 1400

Spero sia tutto chiaro ;)

Ora rimane solo una cosa da risolvere: giocando al nostro capolavoro infatti noteremo presto che, dopo il livello 4, il background torna blu e tale rimane all’infinito. Non è esattamente quello che vogliamo, dato che dovremmo alternare il rosso e il blu!

Per risolvere quest’ultimo dilemma introduco la funzione modulo. Questo metodo restituisce il resto della divisione che gli viene sottoposta. Per esempio, cercando il modulo della divisione 2 / 2 otterremo come risultato 0. Per 1 / 2 otterremo 1 e via discorrendo.

Ora mi chiederete: tutto questo è molto bello ma cosa serve, in realtà?

La funzione di modulo, in flash, è rappresentata dall’operatore %. Quello che faremo noi sarà semplice: partendo dal numero del livello attuale, calcoleremo il modulo della divisione tra il livello attuale e 2. Due perché i nostri background sono due.

Il risultato sarà quindi 0 oppure 1, questi sono i possibili resti. Se otterremo 0 useremo il background rosso, nel caso di 1 invece quello blu. Ecco alcuni esempi:

  • Livello 6 = 6 % 2 = 0 –> Background Rosso
  • Livello 7 = 7 % 2 = 1 –> Background Blu
  • Livello 8 = 8 % 2 = 0 –> Background Rosso

Ora capite perché la regola è idealmente applicabile all’infinito? ;)

Traduciamo questo ragionamento in codice:

else
{
  if ( levelNumber % 2 == 1 )
  {
    backgroundImage = "blue";
  }
  else
  {
    backgroundImage = "red";
  }
  pointsToReachNextLevel = levelNumber * 200;
  enemySpawnRate = 0.5 - ( 2 / levelNumber );
}

e l’ultimo tocco è dato.


Sfide

Sulla scia della versione originale di questi articoli riporto qualche piccola sfida proposta per aumentare la familiarità con il codice. Per esempio:

  • che ne dite di mostrare su schermo il livello attuale? ne dovreste essere capaci, lavorando con le variabili che avete (ne basterebbe una ;) ) e una bella textbox dinamica!
  • magari potreste implementare delle schermate tra un livello e l’altro: con gameTimer.Stop() potreste fermare temporaneamente il gioco e quindi usare un simbolo da cliccare (un pulsante per esempio) per passare al livello successivo e far ripartire il caro gameTimer.

Qualcosa di più difficile?

  • Create dei livelli bonus: magari al posto dei nemici appaiono delle monete e prendendole aumenta il punteggio!

Come potete ben capire, le idee possibili (anche per un gioco semplice come questo) sono migliaia. E tutte realizzabili. Scatenate la vostra fantasia e proponete! Inoltre, notate una cosa: stavolta non abbiamo usato neanche un import, un extend e addirittura neanche un Event Listener!

Prima di concludere vi lascio i file della lezione, che trovate Qui, annunciandovi che nella prossima lezione faremo qualcosa di molto avanzato: impareremo a gestire i salvataggi, a crearli e ricaricarli ;)

Fino ad allora, buona serata a tutti!

  • Share/Bookmark

Creare un Gioco in Flash – Parte 9: Aggiungere Musica ed Effetti Sonori

In questa nona lezione dedicata allo sviluppo di giochi in flash inizieremo a deliziare gli altri sensi: è il turno infatti degli effetti sonori e delle musiche di sottofondo. Se non avete seguito la nostra serie di tutorial, prelevate il file zip alla fine della lezione precedente. Altrimenti, come d’uso ormai comune, prendete i vostri file di backup e via!


Cerchiamo la Musica

Ok, per prima cosa dobbiamo cercare tutti i file audio che ci servono: beh, internet è letteralmente pieno, stracolmo di siti che permettono di prelevare dei loop gratuitamente e di buona qualità. Per esempio, vi ricordate, prima, quando avevo parlato del sito 8bitrocket? Come ulteriore prova della sua qualità vi lascio il link della sezione musicale, dalla quale potrete scaricare svariati loop gratuiti disegnati proprio per flash. La versione completa di questi loop, invece, viene intorno ai 10$.

Se invece non vi piace, possiamo guardare anche altrove: uno dei siti più quotati è sicuramente Newgrounds, che da anni propone il meglio dei contenuti flash in circolazione. Oppure, ancora, troviamo Flash Kit e per quelli che proprio vogliono spendere dei soldi abbiamo SomaTone, famosa per “giochetti” quali Peggle, Medal Of Honor Frontline e World Of Warcraft.

Senza considerare, inoltre, che ci sono un sacco di strumenti che ci permettono di creare i nostri loop!

Flash, attualmente, supporta svariati tipi di audio: mp3, wav, aiff ed altri che sono riportati tutti nella lista ufficiale. C’è proprio l’imbarazzo della scelta! Dopo aver preso i file che vi servono, create una nuova cartella “Suoni” dentro la cartella principale (dove prima avevamo messo la cartella “Classi”) e metteteci i suoni dentro.


Portando la Musica nel Gioco

Per importare correttamente la musica nel nostro file .swf, dovremo necessariamente importarla nella nostra libreria, nel file .fla. Ovviamente tutto ciò è molto semplice: cliccate su “File > Importa > Importa nella Libreria”, selezionate la musica contenuta nella cartella dei suoni e cliccate su “Apri”. Dopo poco meno di un secondo i file appariranno nella libreria:

AvoiderGame_Part9_01

Ecco come appare un suono nella libreria.

Tramite il pulsante triangolare potrete riprodurre il suono per verificare di aver importato quello giusto. Se cliccate con il pulsante destro del mouse sul suono e selezionate la voce “Proprietà”, invece, vi si aprirà una finestra simile a quella del MovieClip:

AvoiderGame_Part9_02

^ Click per Ingrandire ^

Come vedete c’è un sacco di roba in questa finestra, vediamo un po’ di analizzarla:

  • Il primo “riquadro” contiene il nome del nostro simbolo (anche il file audio è identificato come tale). Di default il nome è quello del file originale importato;
  • Successivamente troviamo le informazioni riguardanti il file: dimensioni, posizione sull’hard disk e la data di creazione del file, oltre ovviamente alle informazioni sulla qualità e lunghezza;
  • Sulla sinistra potete invece vedere una rappresentazione grafica della sound wave;
  • Il pulsante “Aggiorna” chiede al Flash di ricaricare il suono dall’hard disk, nel caso siano state fatte modifiche esterne al file;
  • “Importa” permette di scambiare il file attuale con un altro importato, mantenendo le impostazioni attuali immutate;
  • “Prova” ed “Interrompi” possono essere usati per riprodurre ed interrompere il suono;
  • “Audio Dispositivo”, nella sezione di “Impostazioni Esportazione” è da utilizzare nel caso si debba usare un suono differente quando questo simbolo audio deve essere riprodotto su dispositivi come PDA o Smartphone che usano Flash Lite;
  • La sezione “Concatenamento” è praticamente uguale a quella per i pulsanti e Movie Clip.

Lasciate le impostazioni di esportazione nei valori di default; cambiate però il nome del suono in “BackgroundMusic” ed esportatelo per ActionScript. Il nome della classe sarà sempre BackgroundMusic e provvedete anche ad esportarlo per il primo frame, per il momento.


Play!

Bene, ora la musica è stata inclusa nel file .swf finale (se salvate tutto e testate, infatti, noterete che ora la dimensione del filmato è aumentata rispetto a prima), ma non abbiamo scritto da nessuna parte che deve essere riprodotta! Proprio come abbiamo fatto con altri simboli, anche stavolta lavoreremo via codice per aggiungere la musica alla nostra schermata di gioco.

Aprite il file AvoiderGame.as. Useremo due oggetti distinti per gestire la musica:

  • Il suono stesso (il simbolo BackgroundMusic, per intenderci);
  • Un “sound channel” che si occuperà di controllare questo suono.

Questi oggetti dovranno essere disponibili a livello di classe, per cui ci occuperemo di definirli con gli altri, globalmente.

public var backgroundMusic:BackgroundMusic;
public var bgmSoundChannel:SoundChannel;  //bgm for BackGround Music

BackgroundMusic è già nella libreria e verrà caricato normalmente. SoundChannel però dovrà essere importato! Per cui aggiungiamo al codice
import flash.media.SoundChannel;

e non avremo problemi. Il prossimo passo sarà quindi far partire la musica non appena verrà creata la schermata di gioco. Stiamo parlando del metodo costruttore ;)
public function AvoiderGame()
{
  backgroundMusic = new BackgroundMusic();
  bgmSoundChannel = backgroundMusic.play();

La prima istruzione si occupa di inizializzare il suono dalla libreria, proprio come quando creiamo un nuovo oggetto di tipo Avatar con l’istruzione “avatar = new Avatar();”. La successiva istruzione invece si occupa di assegnare quel suono al SoundChannel, mandandolo in riproduzione (successivamente vi mostrerò come usare più canali per gestire più tipologie di suoni).

Se salvate e provate il gioco, il nostro suono partirà: l’unica fregatura è che verrà riprodotto una volta e basta, evitando altri loop. A breve risolveremo questo problema, ma ora una cosa più importante: dobbiamo fare in modo che il suono venga caricato dal nostro benedetto/maledetto preloader.

Per prima cosa, tra le proprietà del nostro suono leviamo il segno di spunta che indica che verrà esportato nel primo fotogramma. Tuttavia, proprio come gli altri MovieClip, in questo modo il suono non verrà importato e non saremo capaci di usarlo durante il gioco.

Per questo motivo dobbiamo provvedere subito ad aggiungerlo al nostro AssetHolder, tanto menzionato durante la lezione precedente. Quindi, doppio click sull’AssetHolder nella nostra libreria ed entreremo in fase di modifica del simbolo.

Cliccando con il pulsante destro del mouse su “Livello 1” selezioniamo “Inserisci Livello”.

AvoiderGame_Part9_03

Chiamiamolo in modo da ricordarci a cosa servirà, qualcosa come “BackgroundMusic” (si, lo so, sono dannatamente originale) (in realtà potete chiamarlo anche PongiBonzi o Poffarbaccoli, tanto al fine del codice non servirà a niente ;) ) Una volta selezionato questo layer, trasciniamo sullo stage il nostro BackgroundMusic. Il fotogramma sulla linea temporale cambierà:

AvoiderGame_Part9_04

Da così…

AvoiderGame_Part9_05

… a così.

Cliccando con il pulsante destro del mouse su quel layer e selezionate “Inserisci Frame” per capire il perchè…

AvoiderGame_Part9_06

Stiamo visualizzando una rappresentazione grafica del suono :SS

Nota: prima di continuare annulla le ultime modifiche (intendo l’inserimento di questo nuovo frame). Devi essere sicuro che tutti i livelli occupino un solo frame, altrimenti avrai degli errori in fase di test. Non ancora riesco a capire per bene il perché, se qualcuno lo sa sarà mia premura aggiornare il post con il dovuto riferimento ;)

Salvate il tutto, testate il gioco e simulate di nuovo il download: stavolta noterete che ci metterà un bel po’ di più rispetto a prima. Questo vuol dire che le cose vanno esattamente come devono andare!!! Infatti il nostro suono viene caricato nel file .swf finale regolarmente, ma fin quando non viene caricato completamente non viene riprodotto.

Ma c’è ancora un piccolo problema. La musica infatti verrà riprodotta appena il menu di gioco apparirà. E non è esattamente quello che vogliamo. Questo succede perché, avendolo trascinato nell’AssetHolder (e quindi trascinato sullo stage) un file audio viene riprodotto automaticamente… un po’ come un file grafico che viene mostrato subito!

In fase di modifca del nostro AssetHolder clicchiamo con il pulsante destro del mouse sul frame che contiene il suono. La seguente opzione sarà visibile:

AvoiderGame_Part9_07

Tutto quello che dobbiamo fare è cambiare il valore riportato nella listbox affianco a “Sincr.” (o “Sync” in inglese). Da “Evento” cambiamolo in “Interrompi”. Se testate ora il gioco, noterete che tutto funziona perfettamente.

Ora dobbiamo fare in modo che il suono venga riprodotto ciclicamente.


Ripetizioni

Ok, prima di partire riflettiamo un po’: come possiamo fare in modo di riprodurre ciclicamente e all’infinito il nostro file audio? Potremmo cambiare in questo modo il nostro codice:

public function AvoiderGame()
{
  backgroundMusic = new BackgroundMusic();
  bgmSoundChannel = backgroundMusic.play( 0, 10 );

L’istruzione backgroundMusic.play è cambiata. Abbiamo aggiunto due numeri come parametri, e non sono stati messi lì così a caso. Il primo indica che ogni volta che riprodurremo il nostro suono verrà riprodotto dall’inizio. Il secondo invece indica il numero di ripetizioni da effettuare.

Seguendo questo ragionamento potremmo impostare, ad esempio, 99.999 ripetizioni e starcene tranquilli! D’altronde, di questo passo una persona dovrebbe giocare al nostro gioco per nove giorni consecutivi per rimanere senza musica e per quanto possa essere divertente non credo esista qualcuno così deviato mentalmente.

Ma siamo pignoli, e vogliamo a tutti gli effetti che il nostro brano si ripeta all’infinito. Senza compromessi. E cosa faremo per raggiungere il nostro obiettivo?

Semplice, aggiungiamo un nuovo EventListener. L’evento che dobbiamo individuare, stavolta, è di tipo Event.SOUND_COMPLETE, e verrà gestito tramite il seguente codice:

public function AvoiderGame()
{
  backgroundMusic = new BackgroundMusic();
  bgmSoundChannel = backgroundMusic.play();
  bgmSoundChannel.addEventListener( Event.SOUND_COMPLETE, onBackgroundMusicFinished );

Notate che non abbiamo bisogno di importare nulla stavolta, dato che SOUND_COMPLETE fa parte di Event, che abbiamo già importato prima.

Ora creiamo l’EventHandler adatto:

public function onBackgroundMusicFinished( event:Event ):void
{
  bgmSoundChannel = backgroundMusic.play();
  bgmSoundChannel.addEventListener( Event.SOUND_COMPLETE, onBackgroundMusicFinished );
}

Notate che ho aggiunto l’eventListener un altra volta, nel metodo. Questo perché ogni volta che richiamiamo il metodo play() le informazioni sull’eventListener vengono perse. Considerate inoltre che questo metodo aggiunge una grande flessibilità. Senza problemi infatti potremmo gestire più tracce audio contemporaneamente.

I problemi, però, non finiscono qua. Se perdiamo la partita e andiamo in Game Over, infatti, la musica non si ferma e continua. Riavviando la partita tramite il pulsante apposito, infatti, viene creato un altro canale e il suono viene ripetuto due volte, in un modo davvero fastidioso.

La soluzione però è semplice. Basta dare la giusta istruzione quando, appunto, il nostro avatar viene a contatto con un nemico:

if ( avatarHasBeenHit )
{
  bgmSoundChannel.stop();
  dispatchEvent( new AvatarEvent( AvatarEvent.DEAD ) );
}

Una semplice istruzione dal nome altrettanto semplice: stop. Serve davvero che vi spieghi a cosa serve? ;)


Aggiungiamo degli Effetti Sonori

Anche aggiungere gli effetti sonori, come per i clip musicali, è abbastanza semplice. Quello che farò in quest’ultima parte del tutorial sarà riprodurre un effetto ogni volta che verrà generato un nuovo nemico. Userò un suono chiamato Synth Bleep 4, trovato su FlashKit.

Proprio come prima, riassumiamo quello che dobbiamo fare per ogni file:

  • Salvare il file nella cartella dei suoni;
  • Importarlo nella libreria;
  • Dargli un nome utile al contesto (per esempio EnemyAppearSound);
  • Esportarlo per ActionScript (non al primo frame però);
  • Aggiungerlo su un nuovo livello dell’AssetHolder, impostando “Sincr.” su “Interrompi”.

Una cosa veloce. Ed ora un po’ di codice:

public var backgroundMusic:BackgroundMusic;
public var bgmSoundChannel:SoundChannel;  //bgm for BackGround Music
public var enemyAppearSound:EnemyAppearSound;
public var sfxSoundChannel:SoundChannel;  //sfx for Sound FX

Anche per il codice la procedura è quella di prima. Inizializzate il suono nel costruttore:
public function AvoiderGame()
{
  backgroundMusic = new BackgroundMusic();
  bgmSoundChannel = backgroundMusic.play();
  bgmSoundChannel.addEventListener( Event.SOUND_COMPLETE, onBackgroundMusicFinished );
  enemyAppearSound = new EnemyAppearSound();

Aggiungiamo quindi il codice per riprodurre il suono quando, appunto, un nuovo nemico viene creato:
if ( Math.random() &lt; 0.1 )
{
  var randomX:Number = Math.random() * 400;
  var newEnemy:Enemy = new Enemy( randomX, -15 );
  army.push( newEnemy );
  addChild( newEnemy );
  gameScore.addToValue( 10 );
  sfxSoundChannel = enemyAppearSound.play();
}

Salvate e testate. Sarà esattamente quello che ci aspettavamo! In realtà, potremmo gestire tutti i suoni con un solo SoundChannel, ma la scelta di differenziarli è dovuta a motivi vari: per esempio con due SoundChannel diversi possiamo sistemare i volumi come meglio crediamo!

E anche per questa lezione ce l’abbiamo fatta. Usate saggiamente i suoni perché aggiungono veramente tanto al vostro lavoro. Un “ding” quando il caricamento è completato oppure un suono triste per annunciare il Game Over. Le idee possono essere un milione.

I file della lezione, come al solito, possono essere scaricati da Qui.

Nella prossima lezione aggiungeremo un elemento in più al nostro gioco: più livelli per tutti!

  • Share/Bookmark