1. Introduzione
  2. Iniziamo
  3. Nemici Multipli
  4. Game Over
  5. Punteggi e Orologio
  6. Tanti Piccoli Miglioramenti
  7. Usiamo la Tastiera
  8. Aggiungere un Preloader (Parte 1 - Parte 2)
  9. Aggiungere Musica ed Effetti Sonori
  10. Livelli Multipli
  11. Salvare e Caricare Informazioni
  12. Garbage Collection

In questa nuova parte della guida andiamo ad aggiungere e gestire quella che per molti giochi è una componente fondamentale, se non importantissima: l’input da tastiera. Tra l’altro, per fare in modo di non confondervi eccessivamente, manterremo il codice destinato alla gestione della tastiera separato da quello, già esistente, per il mouse.

Per tutti coloro che ci seguono a partire da questa lezione raccomando il download dei file della lezione 6, che potete trovare alla fine della detta lezione. Dopo aver preso i file apriamo il nostro file .fla e possiamo procedere.


Passando da un Sistema di Controllo all’Altro

Al contrario di quanto potrebbe essere fatto in altre guide, noi faremo qualcosa in più del normale: anziché rimuovere del tutto il codice della gestione del mouse, faremo in modo di poter “passare” da un sistema di controllo all’altro.

Per prima cosa apriamo il nostro file “AvoiderGame.as”, dove aggiungeremo una nuova variabile: useMouseControl. Sarà di tipo booleano.

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;

Penso ormai sappiate bene che una variabile booleana può assumere solo due valori: true oppure false. Se non ve lo ricordavate tenetelo bene a mente da ora in poi, dato che non mi ripeterò su una cosa così base. Dato il tipo, useremo questa variabile come una sorta di interruttore: se impostata su true allora utilizzeremo il mouse per i controlli. Altrimenti useremo l’input da tastiera.

Innanzitutto, impostiamo la variabile su true, dato che non ancora abbiamo un sistema di controllo della tastiera. In questo modo potremo comunque testare il gioco. Quindi:

public function AvoiderGame()
{
  useMouseControl = true;

Dopo aver aggiunto questa linea di codice, dobbiamo cercare tutte le altre linee dove viene utilizzato il mouse. I posti dove cercare sono attualmente 2: il primo è nel costruttore della classe, nella quale prendiamo la posizione iniziale del nostro Avatar:
avatar = new Avatar();
addChild( avatar );
avatar.x = mouseX;
avatar.y = mouseY;

Le cose ovviamente vanno cambiate. Dovremo quindi:

  • controllare se la variabile è uguale a true;
  • in tal caso usare il codice di controllo del mouse, altrimenti usare dell’altro codice (ovvero per la tastiera)

Il precedente codice diventerà:

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

La sezione di codice nell’else, ovviamente, verrà eseguita solo nel caso si voglia usare la tastiera.

Ora procediamo nella prossima zona: il metodo onTick() dove troviamo il controllo sulla posizione del mouse per aggiornare quella del giocatore.

avatar.x = mouseX;
avatar.y = mouseY;

Stavolta le cose cambieranno così:
if ( useMouseControl )
{
  avatar.x = mouseX;
  avatar.y = mouseY;
}
else
{
  //il codice per la gestione della tastiera andrà qui!
}

Ricordate che l’istruzione nell’else è solamente un commento e verrà ignorato da flash. L’abbiamo usato come segnaposto per ricordarci dove vanno le giuste istruzioni (spero così inizi ad esservi chiaro anche lo scopo dei commenti come strumento d’aiuto).

Possiamo testare quindi il nostro gioco e vedere che le cose funzionano. Cambiamo ora il valore della variabile useMouseControl: anzichè true, impostiamola su false.

AvoiderGame_Part7_01

Ed anche adesso funziona. Il nostro giocatore rimane fermo dov’è dato che abbiamo impostato la variabile useMouseControl su false.

Spero che quello che ho detto sia chiaro fino ad ora, non credo di aver detto cose eccessivamente astruse :)


Andando in Giù

Vi ricordate quando abbiamo programmato il nostro nemico, e scritto la funzione moveDownABit? Per raggiungere il nostro obiettivo, ad ogni tick, incrementavamo il valore sull’asse y, simulando in questo modo il movimento dell’oggetto.

Ecco, per quanto riguarda il nostro avatar quello che faremo sarà decisamente simile. Apriamo il file Avatar.as dalla cartella delle classi.

package
{
  import flash.display.MovieClip;
  public class Avatar extends MovieClip
  {
    public function Avatar()
    {

    }
  }
}

ed aggiungiamo una nuova funzione:
public function Avatar()
{

}

public function moveDownABit():void
{
  y = y + 2;
}

Certo, questo va contro le cose dette riguardo all’hard-coding nell’articolo precedente, ma non vi allarmate, dato che cambieremo presto il tutto. Tornando a noi, con il nemico eseguivamo il movimento ad ogni tick, mentre per il giocatore le cose sono diverse.

Idealmente, infatti, dovremmo fare qualcosa del genere:

if ( useMouseControl )
{
  avatar.x = mouseX;
  avatar.y = mouseY;
}
else
{
  if ( downKeyIsBeingPressed )
  {
    avatar.moveDownABit();
  }
}

dove downKeyIsBeingPressed è una variabile da noi usata per vedere se il tasto freccia giù è premuto sulla nostra tastiera. Bene, ora che avete compreso l’approccio…

scordatevi del tutto di utilizzarlo!

Una cosa del genere poteva essere fatta con ActionScript2, forse, ma ora le cose sono diverse. Gli anni sono passati e da AS2 ora siamo ad AS3 e di conseguenza ora abbiamo a che fare con il futuristico concetto degli eventi. Ragioneremo di conseguenza.

Con gli EventListener, infatti, possiamo controllare quando un determinato tasto è stato premuto e possiamo inoltre rilevare quando un certo tasto è stato rilasciato. Se questo concetto non è chiaro, immaginate la seguente scenetta:

Immaginate che ogni volta che premiamo il tasto B sulla tastiera il pc ci dica: “il pulsante B è stato premuto!”. Ogni volta che invece togliamo il dito dal pulsante, rilasciandolo, il computer ci dice “il pulsante B è stato rilasciato!”. Chiunque, ascoltando il pc, può farsi un’idea chiara della situazione senza guardare le nostre mani ;)

Dopo questo parlare passiamo al codice:

Per prima cosa aggiungeremo una nuova variabile booleana, che chiameremo downKeyIsBeingPressed, nel file AvoiderGame.as:

public var useMouseControl:Boolean;
public var downKeyIsBeingPressed:Boolean;

public function AvoiderGame()
{
  downKeyIsBeingPressed = false;
  useMouseControl = false;

Nota: stiamo assumendo che il giocatore all’avvio del gioco non prema pulsanti.

Ora dobbiamo vedere come riconoscere quando i tasti sono premuti oppure no. Nel metodo costruttore della stessa classe aggiungiamo:

addEventListener( KeyboardEvent.KEY_DOWN, onKeyPress );
addEventListener( KeyboardEvent.KEY_UP, onKeyRelease );

Probabilmente ci avrete già pensato, ma ricordatevi che dovete aggiungere l’import del KeyboardEvent:
import flash.display.MovieClip;
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.ui.Mouse;
import flash.events.KeyboardEvent;

Creiamo quindi la funzione onKeyPress che abbiamo specificato nell’aggiungere il primo Event Listener! Il codice è qui di seguito:
public function onKeyPress( keyboardEvent:KeyboardEvent ):void
{

}

Ora, come facciamo a sapere quale pulsante è stato premuto? Come forse avrete intuito, i nostri EventListener aggiunti parlano di KEY_DOWN o KEY_UP (tasto premuto, tasto rilasciato) ma di certo non si parla di quale di questi tasti!

Ebbene, ogni tasto ha un suo codice che lo identifica univocamente. In questo modo per noi è facile riconoscere quale dobbiamo usare. Flash inoltre ci aiuta, usando una classe Keyboard contenente tutti questi codici. Ovviamente dovremo per prima cosa importarla:

public function onKeyPress( keyboardEvent:KeyboardEvent ):void
{
  if ( keyboardEvent.keyCode == Keyboard.DOWN )
  {
    downKeyIsBeingPressed = true;
  }
}

Usando l’Adobe Flash, appena scriverete “Keyboard.” (come nell’esempio “Keyboard.DOWN”) vi comparirà una lista con tutti i valori dei pulsanti da usare. In questo modo basterà scorrere l’elenco e prendere quello che ci serve.

Molto simile è il metodo onKeyRelease:

public function onKeyRelease( keyboardEvent:KeyboardEvent ):void
{
  if ( keyboardEvent.keyCode == Keyboard.DOWN )
  {
    downKeyIsBeingPressed = false;
  }
}

L’unica differenza si trova infatti nel valore logico assegnato alla variabile. Il motivo dell’uso di true, prima, e false dopo mi sembra non ovvio, ma di più. Con questi metodi, ora, possiamo definire in qualsiasi istante lo stato del tasto freccia giù.

Possiamo quindi aggiungere il controllo necessario nel metodo onTick:

if ( useMouseControl )
{
  avatar.x = mouseX;
  avatar.y = mouseY;
}
else
{
  if ( downKeyIsBeingPressed )
  {
    avatar.moveDownABit();
  }
}

Salvate e testate il gioco. Ci sarà qualcosa che non va. Mi dispiace, ma dobbiamo fare ancora qualche modifica. Leggendo tra la documentazione di Flash, infatti, sono venuto a conoscenza del fatto che gli Event Listeners della tastiera devono essere aggiunti solo ed esclusivamente dall’oggetto “stage”.

Allora è semplice! Basterà aggiungere l’oggetto desiderato in questo modo:

stage.addEventListener( KeyboardEvent.KEY_DOWN, onKeyPress );
stage.addEventListener( KeyboardEvent.KEY_UP, onKeyRelease );

Ma qualcosa ancora non va. Stavolta abbiamo un errore:
TypeError: Error #1009: Cannot access a property or method of a null object reference.

Il problema principale è che, in questo momento dell’esecuzione, lo stage è impostato praticamente su null. Un oggetto ha accesso allo stage solo se quest’oggetto è stato addChild-ato alla document class, oppure ad un altro oggetto addChild-ato alla document class e così via…

Guardate il codice nella classe documento:

playScreen = new AvoiderGame();
playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
playScreen.x = 0;
playScreen.y = 0;
addChild( playScreen );

il nostro playScreen non è subito addChild-ato, e non possiamo fare altrimenti! La soluzione quindi prevede un giro leggermente più lungo: dobbiamo creare un altro Event Listener. Questo evento rileverà il momento in cui useremo l’addChild( playScreen ), permettendoci quindi di aggiungere gli EventListener della tastiera al momento giusto, senza errori.

Nel caso sia confuso come discorso, seguite il codice e sono sicuro che le cose andranno meglio.

Per prima cosa, in AvoiderGame.as, sostituiamo

stage.addEventListener( KeyboardEvent.KEY_DOWN, onKeyPress );
stage.addEventListener( KeyboardEvent.KEY_UP, onKeyRelease );

con
addEventListener( Event.ADDED_TO_STAGE, onAddToStage );

sarà questo l’evento che ci permetterà di capire quando sarà il momento di aggiungere gli EventListener della tastiera. Ovviamente dovremo importare anche questo evento…
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;

Dopodiché creiamo il metodo onAddToStage:
public function onAddToStage( event:Event ):void
{
  stage.addEventListener( KeyboardEvent.KEY_DOWN, onKeyPress );
  stage.addEventListener( KeyboardEvent.KEY_UP, onKeyRelease );
}

Salviamo tutto e proviamo… finalmente funzionerà!!!

Nota Aggiuntiva: provando a premere gli altri pulsanti sulla tastiera durante l’esecuzione di un gioco noterete che il cursore potrà cambiare e verranno selezionati i simboli nell’IDE flash a seconda del tasto premuto. Questo è uno degli altri scogli del flash per la programmazioni di giochi secondo Stephen Calender.

E’ possibile risolvere questo problema, mentre stiamo giocando, selezionando “Controlli > Disattiva tasti di scelta rapida da tastiera”. C’è pure da dire che, una volta pubblicato il nostro gioco la cosa non influenzerà minimamente il gameplay, dato che questo problema si verifica in fase di test.

AvoiderGame_Part7_02

Voilà, niente più problemi.


Movimento nelle Quattro Direzioni

Da questo punto in poi è veramente facile gestire il codice per gli altri tre tasti: essenzialmente dobbiamo ripetere la stessa procedura che abbiamo seguito per gli altri tasti che ci interessano.

Ecco le variabili da usare nella classe:

public var useMouseControl:Boolean;
public var downKeyIsBeingPressed:Boolean;
public var upKeyIsBeingPressed:Boolean;
public var leftKeyIsBeingPressed:Boolean;
public var rightKeyIsBeingPressed:Boolean;

La funzione Costruttore:
public function AvoiderGame()
{
  downKeyIsBeingPressed = false;
  upKeyIsBeingPressed = false;
  leftKeyIsBeingPressed = false;
  rightKeyIsBeingPressed = false;

l’event handler onKeyPress:
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;
  }
}

e l’ultimo Event Handler lo lascio a voi per esercizio, non credo sia difficile da fare ;)

Arrivati a questo punto delle cose, se avete letto le sezioni precedenti, potreste pensare che io sia un tantino ipocrita: d’altronde, nella seconda parte della nostra guida parlavo proprio di come sia buona norma evitare il copia e incolla selvaggio. Decisamente giusto!

Potreste infatti usare un array di valori booleani: ve lo lascio come esercizio se volete (tra l’altro ho lasciato degli indizi nei commenti del codice più in alto). Oppure, in alternativa, c’è un codice realizzato bene sul sito di Senocular.

In più, solo perché siete voi, andrò a modificare le cose in modo tale da creare una nuova funzione di movimento generalizzata. Per prima cosa modifichiamo la funzione moveDownABit in:

public function moveABit( xDistance:Number, yDistance:Number ):void
{
  x += xDistance;
  y += yDistance;
}

Credo possiate capire tranquillamente quello che fa: prende come parametri dei numeri (che rappresentano la velocità sull’asse x ed y) e li somma alla posizione totale. Ed ecco quindi come possiamo richiamare la funzione a partire dai controlli che avevamo scritto precedentemente.

Cambiate

if ( downKeyIsBeingPressed )
{
  avatar.moveDownABit();
}

in
if ( downKeyIsBeingPressed )
{
  avatar.moveABit( 0, 3 );
}

e tutto dovrebbe funzionare correttamente. Volendo essere ancora più precisi possiamo usare quest’altro approccio: memorizzare in una variabile la velocità del giocatore ed usarla nelle varie funzioni. Cambiate il codice di prima in:
if ( downKeyIsBeingPressed )
{
  avatar.moveABit( 0, 1 );
}

e nel file Avatar.as invece:
public function moveABit( xDistance:Number, yDistance:Number ):void
{
  var baseSpeed:Number = 3;
  x += ( xDistance * baseSpeed );
  y += ( yDistance * baseSpeed );
}

In questo modo le cose saranno decisamente più veloci! Per modificare la velocità del nostro giocatore infatti basterà modificare il valore in baseSpeed e nient’altro. Di seguito il codice per gestire tutti i vari tasti:
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 );
}

Se volete, potete provare a mappare per esercizio nuovi controlli, magari i tasti WASD per andare alla metà della velocità! Provate, provate e provate.


Tempo di dare dei Limiti

Se avviamo il gioco, noteremo sicuramente che con il nostro personaggio è possibile andare al di fuori della schermata di gioco! Questo vuol dire che il nostro giocatore può tranquillamente barare, posizionarsi al di fuori della schermata e vedere i propri punti salire vertiginosamente senza fare niente.

Risolviamo il problema.

Innanzitutto, vediamo dove posizionare il codice per questo tipo di controllo:

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 );
  }
}

// il codice di controllo per l'avatar
// verrà messo qui!

var avatarHasBeenHit:Boolean = false;
for each ( var enemy:Enemy in army )
{
  enemy.moveABit();

L’abbiamo messo dopo l’if perchè i confini devono riguardare entrambi gli schemi di comando, a prescindere se stiamo usando il mouse o la tastiera!

Ora troviamo dove il nostro giocatore non può arrivare. Partiamo dalla linea orizzontale. Sappiamo che la coordinata più a sinistra sarebbe zero (il margine sinistro dello schermo avrà quindi come coordinata x lo zero). Ecco quindi il primo controllo:

if ( avatar.x < 0 )
{
  avatar.x = 0;
}

Detta a parole semplici, tutte le volte che l’avatar va troppo a sinistra, e quindi esce dalla schermata di gioco, bisogna riportarlo alla coordinata x = 0 (il margine sinistro, appunto). Potrebbe sembrare tutto corretto ma c’è ancora qualcosa da specificare: vi ricordate dove si trova il punto di registrazione del nostro avatar? Esattamente al centro.

Ciò vuol dire che dobbiamo modificare il codice per fare in modo che questo sia veramente efficace! Se lasciassimo le cose così, il nostro avatar potrebbe nascondersi comunque per metà oltre la schermata di gioco e questo non va assolutamente bene!

Ottimizziamo il codice allora:

if ( avatar.x < ( avatar.width / 2 ) )
{
  avatar.x = avatar.width / 2;
}

In questo modo, sapendo che il punto di registrazione è al centro, basta calcolare la metà della larghezza del nostro Avatar. E agire di conseguenza. Il codice per gli altri casi di “sforamento” può essere ricavato in maniera abbastanza facile. Per aiutarvi ve lo riporto ugualmente di seguito: provate a capire voi però qual’è il ragionamento fatto alla base, stavolta.
if ( avatar.x < ( avatar.width / 2 ) )
{
  avatar.x = avatar.width / 2;
}
if ( avatar.x > 400 - ( avatar.width / 2 ) )
{
  avatar.x = 400 - ( avatar.width / 2 );
}
if ( avatar.y < ( avatar.height / 2 ) )
{
  avatar.y = avatar.height / 2;
}
if ( avatar.y > 300 - ( avatar.height / 2 ) )
{
  avatar.y = 300 - ( avatar.height / 2 );
}

Ovviamente 400 sarebbe la larghezza dell’area di gioco e 300 l’altezza. ;)


Nuove Sfide

Non vi preoccupate, non vi tratterrò oltre nella lezione di oggi. Volevo solo riportare alcuni “esercizi” che potete eventualmente fare per prendere ancora più dimestichezza con il sistema. Buon lavoro ;)

  • Che ne dite di creare un paio di pulsanti nel menù, per scegliere se si vuole giocare con il mouse oppure con la tastiera?
  • Avrete notato che il gioco è decisamente più difficile se usiamo la tastiera. Che ne dite di modificare la frequenza di nemici SOLO nel caso di uso della tastiera?

E per ultima cosa qualcosa di più impegnativo:

Che ne dite del movimento ad otto direzioni? Ovvero le direzioni normali più le diagonali. Considerate che potreste controllare il movimento in diagonale con un istruzione tipo

if ( ( downKeyIsPressed ) && ( leftKeyIsPressed ) )

però fate attenzione. Il teorema di pitagora sulla distanza tra due punti lascia intendere che potremmo avere problemi: elaborando i due movimenti contemporaneamente andremmo più lontano del previsto. Rasmus Wriedt Larsen ha scritto un articolo decisamente completo sull’argomento. E’ in inglese e per ora così rimane, magari in futuro lo tradurrò.

E così la nostra lezione è giunta al termine. Come al solito allego i file della lezione, che potete trovare Qui. Nella prossima lezione vedete di seguire, dato che parleremo di uno degli elementi più importanti di un gioco in flash: il Preloader.

Bye!

  • Share/Bookmark