Creare un Gioco in Flash – Parte 2: Nemici Multipli
- Introduzione
- Iniziamo
- Nemici Multipli
- Game Over
- Punteggi e Orologio
- Tanti Piccoli Miglioramenti
- Usiamo la Tastiera
- Aggiungere un Preloader (Parte 1 - Parte 2)
- Aggiungere Musica ed Effetti Sonori
- Livelli Multipli
- Salvare e Caricare Informazioni
- Garbage Collection
In questa seconda parte della nostra guida capiremo come aggiungere una feature fondamentale al nostro gioco: il prossimo obiettivo, infatti, è quello di consentire alla nostro videogame di creare nemici multipli, in modo tale da aggiungere un fattore di sfida in più al gioco.
Cosa otterremo? Lo vedete nell’immagine di seguito.
Sistemiamo un paio di cose
Se non avete seguito la sezione precedente della guida, vi consiglio di farlo o perlomeno prelevare il file zip con i file già pronti che useremo. Un altro consiglio che vi do, nel caso invece abbiate seguito la prima parte della guida, è di effettuare una copia di backup dei file per sicurezza. In questo modo potrete guardare indietro e capire al meglio quali sono stati i vostri progressi.
Comunque sia, aprite il vostro file *.FLA. Aprite la classe del documento (per coloro che si sono connessi adesso parlo della classe “AvoiderGame.as”) e la classe Enemy, contenuta nel file Enemy.as. In questa sezione della guida andremo a modificare entrambe queste classi in maniera significativa.
Prendiamo il controllo del Nemico
Diamo uno sguardo al metodo costruttore della classe Enemy:
public function Enemy()
{
x = 100;
y = -15;
}Questa piccola porzione di codice ci dice una cosa semplice e chiara: ogni singola istanza della classe Enemy inizierà la propria “esistenza” precisamente al punto 100,-15. Come potete immaginare, un costruttore del genere non renderebbe il nostro gioco molto interessante.
Cambiamo quindi un po’ il metodo come indicato di seguito:
public function Enemy( startX:Number, startY:Number )
{
x = startX;
y = startY;
}Abbiamo aggiunto dei “parametri” alla funzione costruttore. Ora possiamo creare delle istanze della classe enemy direttamente dalla classe del documento (si, avveniva anche prima) ma stavolta potremo passare da lì i valori della posizione di partenza dell’oggetto.
Torniamo alla classe AvoiderGame.as, cambiando il codice seguente:
enemy = new Enemy();in:
enemy = new Enemy( 100, -15 );Se avviamo il gioco (ricordatevi, si fa tramite il menù: “File > Anteprima Pubblicazione > Flash”) non noteremo dei cambiamenti. Il nostro nemico inizia dal punto (100,-15), esattamente come ci aspettavamo. Il cambiamento è stato essenzialmente nel “modo di dire” al nemico che deve assumere quella posizione. Invece di scriverlo nella classe, l’abbiamo passato come parametro al costruttore.
Se volete, provate qualche altro numero per capire bene il tutto e poi procediamo
Clonate Responsabilmente.
Ora vogliamo far apparire più di un nemico alla volta. Come possiamo fare? Prima di vedere cosa dobbiamo fare, lasciate che vi mostri qualcosa che NON dobbiamo fare assolutamente. Non sarà una perdita di tempo, fidatevi di me.
Per prima cosa, definiamo qualche nemico in più nella document class principale:
public class AvoiderGame extends MovieClip
{
public var enemy:Enemy;
public var eric:Enemy;
public var ernie:Enemy;
public var emily:Enemy;
public var avatar:Avatar;
public var gameTimer:Timer;Successivamente, impostiamoli con posizioni di partenza differenti e, utilizzando la funzione addChild, aggiungiamoli al gioco vero e proprio:
public function AvoiderGame()
{
enemy = new Enemy( 100, -15 );
addChild( enemy );
eric = new Enemy( 160, -120 );
addChild( eric );
ernie = new Enemy( 205, -60 );
addChild( ernie );
emily = new Enemy( 317, -85 );
addChild( emily );Ora, nella nostra funzione onTick, gestiamo il loro movimento proprio come avevamo fatto per “enemy” nella parte precedente della guida.
public function onTick( timerEvent:TimerEvent ):void
{
enemy.moveDownABit();
eric.moveDownABit();
ernie.moveDownABit();
emily.moveDownABit();
ed infine impostiamo per ognuno di loro un check per le collisioni:
if ( avatar.hitTestObject( enemy ) )
{
gameTimer.stop();
}
if ( avatar.hitTestObject( eric ) )
{
gameTimer.stop();
}
if ( avatar.hitTestObject( ernie ) )
{
gameTimer.stop();
}
if ( avatar.hitTestObject( emily ) )
{
gameTimer.stop();
}Avviamo il gioco. Si, funziona, anche se vi avevo detto che non era una soluzione adatta. E no, non vi ho preso in giro. Come potete vedere questa è una soluzione disordinatissima e soprattutto, alla lunga, potrebbe appesantire il gioco di molto.
Il perchè è nella quantità di istruzioni che diamo nel codice: per ogni nemico dobbiamo creare l’oggetto, farlo muovere, controllare le collisioni e poi avanti con il successivo. Ci sono soluzioni più veloci!
Cancelliamo tutta questa roba inutile e torniamo alla partenza, stavolta formiamo un piccolo esercito!
Creiamo un Esercito
Copiare ed incollare i nemici in questo modo richiede, come avete notato, il riferirci ad essi nome per nome. Per esempio, abbiamo dichiarato il nemico “eric” e l’abbiamo aggiunto con addChild, poi gestito in tutti i suoi aspetti.
La stessa cosa abbiamo successivamente fatto per gli altri. Davvero conviene? Insomma, quello che noi gestiamo dipende essenzialmente dalla loro situazione, non dal loro nome. (La situazione è per esempio quando il nemico va in collisione col giocatore: in qualunque caso però si deve fermare il gioco, a prescindere che si chiami eric, armando o giancarlo
)
Per cui cambiamo approccio. Utilizzeremo uno strumento molto più adatto per creare questa armata. Utilizzeremo un array, una lista ordinata e strutturata di elementi di un determinato genere. Dato che tutti i nemici che utilizzeremo saranno un istanza della classe Enemy allora possiamo procedere senza nessun problema.
Per prima cosa, definiamo il nostro esercito: esattamente come abbiamo fatto per il nemico singolo. Mi pare scontato il bisogno di utilizzare questo array in tutto il gioco e non solo durante la preparazione di questo (nel metodo costruttore). Procediamo con il codice:
public class AvoiderGame extends MovieClip
{
public var army:Array;
public var enemy:Enemy;
public var avatar:Avatar;
public var gameTimer:Timer;In più, rimuoviamo anche la linea “public var enemy:Enemy;”. Dato che tutti i nostri nemici faranno parte dello stesso gruppo, non abbiamo più bisogno neanche di lui.
Ora il nostro costruttore sarà il seguente:
public function AvoiderGame()
{
army = new Array();
var newEnemy = new Enemy( 100, -15 );
army.push( newEnemy );
addChild( newEnemy );Ok, qualcosa è cambiato. Niente, panico, vi spiego tutto linea per linea:
- army = new Array(); – Abbiamo dichiarato il nostro array di nemici, proprio come abbiamo fatto per il nemico singolo precedentemente;
- var newEnemy = new Enemy(100,-15); – Stavolta abbiamo creato un nuovo nemico con il nome “newEnemy”, perchè il suo scopo sarà appunto quello di rappresentare i nuovi nemici creati.
- army.push( newEnemy ); – In questo preciso istante il nostro nemico entra a far parte dell’esercito che abbiamo creato. E’ stato “pushato” (spinto) nella lista dei nemici.
- addChild( newEnemy ); – Con questo metodo aggiungiamo il nemico alla scena. Come fatto prima per “enemy”.
Ora abbiamo bisogno di muovere il nostro nuovo esercito. Quello che faremo sarà avvalerci di un nuovo comando per ActionScript, introdotto infatti a partire dalla versione 3. Il “for each … in”. Modifichiamo la funzione onTick. Prima era:
public function onTick( timerEvent:TimerEvent ):void
{
enemy.moveDownABit();
avatar.x = mouseX;
avatar.y = mouseY;Ora sarà:
public function onTick( timerEvent:TimerEvent ):void
{
for each ( var enemy:Enemy in army )
{
enemy.moveDownABit();
}
avatar.x = mouseX;
avatar.y = mouseY;Penso sia abbastanza chiaro quello che succede qui. Tuttavia darò una spiegazione un po’ più approfondita. Il ciclo “for each … in” parte dal primo elemento di un array fino ad arrivare all’ultimo, prendendo in considerazione un elemento di questo alla volta.
Quello che noi dobbiamo gestire sono i nemici, per cui tra le parentesi tonde notiamo che usiamo la parola Enemy per indicare il tipo di oggetto che il ciclo deve “aspettarsi” dentro l’array usato. “enemy”, invece, è il nome che avrà l’oggetto preso in considerazione.
In questo caso la sua comodità non potrebbe essere immediata. Ma ragionate per un insieme di, non so, 50 elementi. Dovete eseguire una funzione su ogni elemento e cosa succederebbe? Potreste fare un ciclo come questo con l’istruzione che vi serve, oppure 50 istruzioni uguali… a voi la scelta
Ed è così che torniamo al nostro esempio: per ogni elemento della nostra armata (che sia uno, due o centomila) abbiamo bisogno di controllare la collisione con l’avatar. In più dobbiamo anche farlo scendere tramite il metodo “moveDownABit”.
Ecco il nostro codice:
public function onTick( timerEvent:TimerEvent ):void
{
for each ( var enemy:Enemy in army )
{
enemy.moveDownABit();
if ( avatar.hitTestObject( enemy ) ) { gameTimer.stop(); }
}
avatar.x = mouseX;
avatar.y = mouseY;
}Oltretutto, dobbiamo tenere conto del fatto che stiamo facendo il check delle collisioni prima del movimento dell’avatar. Rimettiamo le cose in ordine allora:
public function onTick( timerEvent:TimerEvent ):void
{
avatar.x = mouseX;
avatar.y = mouseY;
for each ( var enemy:Enemy in army )
{
enemy.moveDownABit();
if ( avatar.hitTestObject( enemy ) )
{
gameTimer.stop();
}
}
}Salviamo tutto ed avviamo, noteremo che tutto funziona esattamente come prima. Però abbiamo ancora un solo elemento nel nostro squadrone. La prossima mossa adesso ci porterà dritti al raggiungere il nostro obiettivo, creando il vero esercito.
Aggiungiamo nuovi elementi all’armata.
Partiamo subito con del codice:
public function onTick( timerEvent:TimerEvent ):void
{
var newEnemy:Enemy = new Enemy( 100, -15 );
army.push( newEnemy );
addChild( newEnemy );Fate caso alla funzione: stiamo parlando di onTick. Quello che stiamo facendo è semplice. Stiamo dicendo al gioco di aggiungere alla nostra armata un nuovo elemento ogni volta che raggiungiamo il periodo. Beh, non è un granchè come tecnica: guardate infatti cosa succede.
Hmm. No, non va bene. Stiamo creando i nuovi nemici sempre allo stesso posto, e poi ne sono così tanti che finiscono uno sopra all’altro creando un effetto davvero sgradevole! Dobbiamo ottimizzare un po le cose.
Innanzitutto, la posizione: non possono iniziare tutti quanti nella stessa posizione, sarebbe un gioco troppo noioso! In nostro aiuto per questo problema arriva la magica funzione del Random, richiamabile tramite il comando “Math.Random()”. Il suo scopo è ritornare un numero a caso tra 0 ed 1 (si considerano anche i decimali ovviamente
)
La soluzione per generare a random i nemici in uno spazio di 400 pixel, quindi, viene quasi spontanea: basterà moltiplicare per 400 il numero ottenuto. In questo modo potremo ottenere un valore casuale tra 0 e 400. Ecco a noi il codice:
public function onTick( timerEvent:TimerEvent ):void
{
var randomX:Number = Math.random() * 400;
var newEnemy:Enemy = new Enemy( randomX, -15 );
army.push( newEnemy );
addChild( newEnemy );Salviamo tutto. Avviamo. In questo modo dovremmo avere i nostri nemici finalmente disposti in maniera casuale sull’asse x. Vediamo il risultato.
MALEDIZIONE!
Ne sono troppi, e questo vuol dire che ne arrivano ad una velocità troppo elevata!
Ma a tutto c’è soluzione.
Meno Nemici al Secondo, Grazie.
Attualmente, i nemici appaiono ad una velocità molto alta. Considerate che creiamo un nemico ad ogni esecuzione di onTick, e questo metodo viene eseguito ogni 25 millisecondi per un totale di 40 esecuzioni al secondo.
Esattamente, quaranta nemici generati ogni secondo. Non è molto comodo, vero? Per questo motivo ecco il nostro prossimo traguardo: ridurre la quantità di nemici al secondo ad un decimo circa di quella attuale. Il metodo che useremo lo spiegherò subito.
Quello che potremmo fare è far comparire un nuovo nemico in maniera del tutto casuale, con una probabilità del 10%. In questo modo, con una probabilità così bassa, la statistica di nemici per secondo si abbassa drasticamente.
Sicuramente non è la soluzione più veloce, ma approfittiamone per ragionare al meglio sul Random, perchè sarà proprio questo metodo che useremo.
Math.Random(), come abbiamo detto precedentemente, genera un numero tra 0 ed 1 ad ogni sua esecuzione. Ragionando in questi termini, la possibilità ad ogni esecuzione che il numero restituito sia di un decimo è identificabile come un numero compreso tra 0 e, appunto, 0.1.
Basterà quindi un semplice controllo sul numero generato e via! Se il random appena generato sarà minore di 0.1, allora creeremo un nuovo nemico. Altrimenti non succederà niente e daremo un po di respiro al giocatore.
Ecco il codice che di sicuro sarà comprensibile:
public function onTick( timerEvent:TimerEvent ):void
{
if ( Math.random() < 0.1 )
{
var randomX:Number = Math.random() * 400;
var newEnemy:Enemy = new Enemy( randomX, -15 );
army.push( newEnemy );
addChild( newEnemy );
}Un semplice controllo ed è tutto risolto. Penso sia semplice da capire, spero lo sia anche per voi
Salviamo tutto e guardiamo quello che è il risultato finale:
Ora si che abbiamo a che fare con qualcosa di più giocabile!
Suggerimento: giocate un po’ con i valori random per comprendere al meglio il loro funzionamento. Nel mondo dei videogiochi questa funzione ha un impiego non indifferente, non sottovalutatela. Inoltre, con le nozioni imparate oggi potete provare a fare due “esercizi”:
- Provate a dare una posizione random (o a caso, come volete voi) al nemico creato all’inizio del gioco.
- A quanto pare i nemici a volte appaiono solo parzialmente, perché troppo a destra rispetto alla schermata di gioco. Riuscite a risolvere questo piccolo inconveniente? (Suggerimento: ragionate sul random moltiplicato per 400 e sulla larghezza del nemico.)
Come è avvenuto anche per la precedente parte della guida, potete scaricare i file degli esempi da Qui.
Nella prossima parte della guida impareremo ad aggiungere al nostro gioco una schermata di Game Over nel caso possa servire.
Grazie per l’ascolto!
Francesco




2 luglio 2010 - 18:42
scusa ma mi sorge una domanda
Non c’è il rischio che il nostro PLAYER sia così bravo da restare per molto tempo in gioco con conseguente sovracaricamento dell’array army tale da rallentare l’esecuzione del gioco????
grazie ciao
6 agosto 2010 - 16:04
Segnalo quello che presumo essere un errore:
nell’ultimo blocco di righe di codice (qui:)
if ( Math.random() < 0.1 )
< va sostituito con <, sennò non funziona e da errore. Almeno a me così risulta…
Comunque grandissimo chi ha messo 'sto tutorial, è troppo utile.
6 agosto 2010 - 16:06
(°_°) non appare quello che indicavo!
comunque era: & l t (senza spazi)
comunque complimenti ancora.