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

Giochi in Actionscript 3: Arkanoid

Bricks, Arkanoid, Breakout, tanti nomi per un solo gioco. Vedremo come realizzare una versione molto basilare di questo gioco così da capirne i principali meccanismi e poterla poi migliorare secondo i propri gusti
Bricks, Arkanoid, Breakout, tanti nomi per un solo gioco. Vedremo come realizzare una versione molto basilare di questo gioco così da capirne i principali meccanismi e poterla poi migliorare secondo i propri gusti
Link copiato negli appunti

Tra i giochi che vantano innumerevoli cloni possiamo sicuramente annoverare Arkanoid, conosciuto anche come Bricks o Breakout, ovvero il classico gioco dove il giocatore ha come scopo quello di distruggere un muro di mattoncini facendo rimbalzare la palla su di una stanghetta.

La prima versione di questo gioco, che si chiamava Breakout, fu opera di Steve Jobs e Steve Wozniak, fondatori di Apple, tanto che in diverse versioni di Mac OS si trovava questo gioco come Easter Egg.

Lo scopo del gioco è quindi molto semplice, e anche la programmazione non presenta eccessive problematiche se non quella di calcolare con adeguata precisione i rimbalzi sui bordi dell'area di gioco, sui mattoncini e sulla barretta del giocatore, il "pad". In questo articolo vedremo come realizzare una versione molto basilare di questo gioco così da capirne i principali meccanismi e poterla poi eventualmente migliorare secondo i propri gusti.

Preparazione degli elementi di gioco

La prima cosa da fare è la preparazione degli oggetti di gioco, che realizzeremo graficamente all'interno di un file FLA. Creiamo quindi un nuovo file Actionscript 3 (File -> New -> Actionscript 3 File).

Dobbiamo disegnare la pallina, i bordi che useremo per delimitare l'area di gioco, un mattoncino e il pad; ognuno di questi elementi sarà all'interno di un movieclip che esporteremo per poterne creare dinamicamente delle istanze tramite Actionscript. Creiamo ad esempio la nostra pallina, disegnandola con colori a piacere e dimensioni di 18x18, quindi convertiamola in movieclip ed impostiamone l'esportazione per Actionscript con classe Pallina.

Figura 1. Conversione in movieclip ed esportazione per Actionscript
Screenshot del pannello

Eseguiamo l'operazione analoga anche per gli altri elementi; il mattoncino avrà classe Mattoncino e dimensioni di 50x18 pixel, mentre il pad avrà classe Pad e misurerà 90x15 pixel. Queste misure sono modificabili a piacere, tuttavia per il nostro esempio (che prevede uno stage di dimensioni 550x400) consentono di inserire un buon numero di mattoncini e mantenere una certa proporzione tra i vari elementi.

Figura 2. I tre elementi principali del gioco
Screenshot dello stage

L'ultimo elemento da disegnare à la clip che costituirà i bordi su cui la pallina potrà rimbalzare: ovviamente tali bordi saranno solo sui lati destro, sinistro e superiore poiché se la pallina cadrà dal lato inferiore dovremo considerare persa una vita del giocatore.

La clip sarà a solo scopo decorativo, infatti, come vedremo nel codice, il rimbalzo sul muro lo calcoleremo in base alla posizione della pallina senza utilizzare altre clip (come invece faremo per calcolare la collisione e il rimbalzo tra pallina e pad), possiamo quindi creare il muro a nostro piacimento e non è necessario assegnarli un identificativo, poiché posizioneremo il movieclip direttamente sullo stage.

Figura 3. La clip posizionata sullo stage
Screenshot dello stage

Come ultima operazione creiamo un campo di testo dinamico (nome istanza txtvite) e posizioniamolo sullo stage in un punto a piacimento, ad esempio in basso a destra. Rimuoviamo infine eventuali istanze delle altre clip dallo stage (non sono necessarie poiché gli oggetti verranno creati tramite Actionscript) e salviamo il nostro file con il nome Arkanoid.fla.

Impostazione della classe

Dopo aver impostato il file FLA possiamo dedicarci alla scrittura del codice Actionscript 3 che regolerà tutto il gioco. Per prima cosa dichiariamo la classe e importiamo i package necessari per il nostro gioco, che saranno:

  • flash.display.MovieClip: per interagire con gli elementi di gioco e per estendere la classe MovieClip;
  • flash.events.*: per ricavare gli input del giocatore;
  • flash.utils.getTimer: per temporizzare alcune azioni;
  • flash.geom.Rectangle: per il calcolo delle collisioni;
  • flash.text.TextField: per il campo di testo delle vite.

Ecco quindi il primo codice per la nostra classe Bricks:

Listato 1. Classe Bricks, importazione package necessari

package {
  import flash.display.*;
  import flash.events.*;
  import flash.utils.getTimer;
  import flash.geom.Rectangle;
  import flash.text.TextField;
  
  public class Bricks extends MovieClip{ }
}

Uno dei maggiori vantaggi nell'uso del codice Actionscript è la possibilità di impostare diversi parametri del gioco in modo tale che qualora volessimo modificare qualche aspetto del nostro lavoro potremmo agire su una o comunque poche righe di codice, per questo si utilizzano le variabili e le costanti.

Nel nostro caso possiamo impostare come costanti le dimensioni dei muri e dei vari oggetti di gioco, oltre alla velocità della pallina, sarà inoltre necessario impostare le variabili per lo svolgimento del gioco. Ecco allora l'elenco delle variabili e delle costanti da inserire nella nostra classe:

Listato 2. Costanti e variabili da aggiungere alla classe

private const raggio:Number = 9; // raggio della pallina
private const muroTop:Number = 18; // spessore del muro superiore
private const muroSx:Number = 18; // spessore del muro sinistro
private const muroDx:Number = 532; // spessore del muro destro
private const padY:Number = 380; // posizione verticale del pad
private const padW:Number = 90; // larghezza del pad
private const velpallina:Number = .2; // velocità della pallina
private const paddleCurve:Number = .005;
private const paddleHt:Number = 18; // altezza del pad

// oggetti
private var pad:Pad; // pad
private var pallina:Pallina; // pallina

private var mattoncini:Array; // array mattoncini

// velocità pallina
private var pallinaDX:Number;
private var pallinaDY:Number;

// timer per animazioni
private var lastTime:uint;

// numero di vite
private var vite:Number;

Possiamo notare che abbiamo dichiarato nel codice valori come larghezza del pad e raggio della pallina: questo principalmente perché dichiarando le costanti all'inizio della classe non potremmo ricavare le dimensioni dell'oggetto poiché questo non è ancora presente sullo stage.

Tutte le costanti verranno utilizzate per il movimento e per i rimbalzi, mentre le variabili pallinaDX e pallinaY stabiliranno la velocità della pallina ogni momento del gioco. Le variabili pad e pallina conterranno i riferimenti a questi due oggetti che creeremo sullo stage, vite memorizzerà il numero di vite disponibili per il giocatore mentre lastTime verrà sfruttato per gestire alcune animazioni.

Funzione di avvio del gioco

Una volta impostate le variabili sarà necessario impostare l'avvio di una nuova partita: sullo stage appariranno sin da subito i bordi, i mattoncini e il pad, mentre la pallina verrà creata solo dopo il click dell'utente, così che il giocatore possa iniziare la partita a suo piacimento. Scriviamo quindi la funzione Brick in questo modo:

Listato 3. Crea la scena di gioco

public function Bricks() {
  // creazione del pad
  pad = new Pad();
  pad.y = padY;
  addChild(pad);
  // creazione dei mattoncini
  creaMattoni();
  // impostiamo il numero di vite
  vite = 3;
  // impostazione dell'animazione
  lastTime = 0;
  addEventListener(Event.ENTER_FRAME,muoviOggetti);
  stage.addEventListener(MouseEvent.CLICK,nuovaPalla);
}

Come possiamo vedere viene creato e posizionato il pad, viene chiamata la funzione di creazione dei mattoncini, vengono impostate le vite e viene anche richiamata la funzione muoviOggetti, che essenzialmente si occuperà di eseguire il movimento di pad e pallina durante tutto il filmato (vediamo infatti che è associata all'evento ENTER_FRAME). Al click dell'utente sullo stage viene invece associata la creazione di una nuova pallina.

Vediamo per prima cosa la funzione creaMattoni, che si occuperà di aggiungere i mattoncini allo stage disponendoli su più file. Ricordiamo che nelle variabili abbiamo dichiarato l'array mattoncini che utilizzeremo per memorizzare i riferimenti ai vari mattoncini creati. Nel nostro esempio useremo una griglia di 5 righe e 8 colonne, per un totale di 40 mattoncini; ecco il codice:

Listato 4. Crea la griglia di mattoncini

private function creaMattoni():void{
  // creiamo l'array
  mattoncini = new Array();
  // creiamo una griglia di 5x8
  for(var y:uint=0;y<5;y++) {
    for(var x:uint=0;x<8;x++) {
      // creiamo il nuovo mattoncino
      var matttone:Mattoncino = new Mattoncino();
      // spaziatura tra i mattoni
      mattone.x = 52*x+65;
      mattone.y = 20*y+50;
      addChild(mattone);
      // inseriamo il riferimento al mattone nell'array
      mattoncini.push(mattone);
    }
  }
}

Impostiamo fin d'ora anche la funzione muoviOggetti, che andremo però a scrivere in seguito; la dichiariamo già ora così da poter testare il filmato (in alternativa avremmo potuto commentare il richiamo a muoviOggetti dalla funzione Bricks).

private function muoviOggetti():void{ }

Prima di eseguire il primo test del nostro filmato andiamo a scrivere la funzione nuovaPallina: molto semplicemente questa funzione andrà a posizionare una nuova pallina sullo stage e verrà chiamata sia ad inizio partita (dopo il click dell'utente) sia ogni volta che il giocatore perderà una vita.

Listato 5. Posiziona la pallina sullo stage

public function nuovaPalla(evt:Event){
  // non facciamo nulla se c'è già una pallina in gioco
  if (pallina != null) return;
  
  // creiamo la pallina e la posizioniamo
  pallina = new Pallina();
  pallina.x = (muroDx-muroSx)/2;
  pallina.y = 200;
  addChild(pallina);
  
  // velocita pallina
  pallinaDX = 0;
  pallinaDY = velpallina;
  
  // rimuoviamo una vita
  vite--;
  txtvite.text = "Vite: "+vite;
  
  // reset animazione
  lastTime = 0;
}

A questo punto facciamo un primo test del nostro filmato: apriamo il file Bricks.fla e impostiamo come Document Class Bricks, quindi avviamo la modalità prova filmato (Ctrl+Invio): ci apparirà lo stage con disegnati i mattoncini e il pad e, quando cliccheremo sullo stage, vedremo apparire anche la pallina e l'indicazione delle vite.

Figura 4. Un primo test del filmato
Screenshot del filmato

Per ora ci fermiamo qui: nella prossima parte vedremo come animare la pallina, calcolare le collisioni ed eliminare i mattoncini colpiti.

Impostare il centro dei movieclip

Sia per calcolare la posizione del pad che per calcolare i rimbalzi, impostiamo il centro dei movieclip in modo che coincida con il centro dell'oggetto: Flash di default imposta il centro del clip in alto a sinistra rispetto all'oggetto creato, noi invece dovremo centrare l'oggetto.

A sinistra abbiamo il mattoncino con l'impostazione di default (notiamo il centro della clip in alto a sinistra), mentre a destra abbiamo il mattoncino allineato centralmente sia in verticale che in orizzontale. Questa impostazione ci sarà utile nel calcolo dei movimenti e soprattutto dei rimbalzi, come vedremo in seguito.

Figura 5. Differenze nell'allineamento al centro del movieclip
Screenshot del movieclip

Movimento del pad

Una volta impostati i centri dei nostri oggetti (mattoncino, pallina e pad), passiamo a programmarne il comportamento.

L'elemento più semplice da controllare è il pad, perché si può muovere solamente in orizzontale e il suo spostamento è controllato dall'utente. Ovviamente il pad dovrà fermarsi una volta entrato in contatto con il muro, quindi la sua area di movimento andrà dal margine del muro sinistro al margine del muro destro.

Per muovere il pad il giocatore utilizzerà il mouse, quindi sfrutteremo le proprietà mouseX e mouseY; ricordiamo che abbiamo precedentemente dichiarato la funzione muoviOggetti, che viene eseguita a ogni ENTER_FRAME del filmato: la sfrutteremo quindi per muovere anche il pad.

Listato 6. Muove il Pad

private function muoviPad(){
  var nuovaPos:Number = Math.min(muroDx-padW/2, Math.max(muroSx+padW/2,mouseX) );
  pad.x = nuovaPos;
}

Il comando Math.min restituisce il minore tra due valori; il primo valore che passiamo a questo comando è muroDx-padW/2, ovvero nel nostro esempio 532-90/2, quindi 532-45 = 487, che è la massima posizione che la clip può raggiungere in quanto coincide con il muro destro meno metà larghezza del pad.

Il secondo valore è il maggiore (ottenuto tramite Math.max) tra muroSx+padW/2 e la posizione X del mouse. muroSx+padW/2 corrisponde a 12+45 = 57, ovvero la posizione minima che il pad può raggiungere, in quanto coincide con il muro sinistro del filmato più metà del pad (ricordiamo che il pad ha l'allineamento al centro ed è per questo motivo che prendiamo sempre in considerazione metà della sua larghezza).

Math.max restituirà sempre un valore compreso tra la posizione minima del pad e la posizione X del mouse, tale valore verrà confrontanto con il limite massimo del pad e tra i due verrà scelto il minore: ne consegue che il movimento potrà andare solo da muroSx+padW/2 a muroDx-padW/2, che sono esattamente i limiti dell'area di movimento del nostro pad.

Andiamo ora a modificare la funzione muoviOggetti in questo modo:

Listato 7. Modifica alla funzione muovi oggetto

private function muoviOggetti(evt:Event):void{
  muoviPad()
}

Possiamo quindi testare il nostro filmato.

Movimento del pad

Movimenti della pallina

Il movimento della pallina presenterà più problematiche, poiché dovrà essere automatizzato e controllato dal player per tutta la durata della partita. Per prima cosa vediamo come avviare il movimento: ecco il codice della funzione muoviPalla.

Listato 8. Codice per muovere la palla

private function muoviPalla(){
  if(pallina == null) return
  if (lastTime == 0) lastTime = getTimer();
  var timePassed:int = getTimer()-lastTime;
  lastTime += timePassed;
  var newX = pallina.x + pallinaDX*timePassed;
  var newY = pallina.y + pallinaDY*timePassed;
  
  pallina.x = newX;
  pallina.y = newY;
}

Se pallina è uguale a null (quindi non c'è ancora una pallina in gioco) la funzione non verrà eseguita; in caso contrario, la variabile lastTime viene associata ai millisecondi trascorsi dall'inizio del filmato, quindi calcoliamo il tempo trascorso dall'ultimo movimento (getTimer()-lastTime). La variabile timePassed viene usata come moltiplicatore dei valori pallinaDX e pallinaDY, che stabiliscono le direzioni orizzontale e verticale; a inizio partità pallinaDX sarà uguale a 0 e quindi la pallina scenderà in verticale verso il pad.

Caduta della pallina

Calcolare le collisioni

Per eseguire i calcoli delle collisioni useremo l'oggetto Rectangle, che ci permette di ottenere informazioni su di un oggetto quali posizione (x e y), dimensione (larghezza e altezza) e controllare singolarmente i bordi superiore, inferiore, sinistro e destro. Per calcolare le collisioni della pallina utilizzeremo due sue posizioni, ovvero quella attuale e quella appena precedente, questo ad esempio per evitare delle eccessive sovrapposizioni tra gli oggetti.

Un oggetto Rectangle richiede 4 parametri: x, y, larghezza e altezza. Ecco come creare i tre oggetti Rectangle relativi rispettivamente alla vecchia posizione della pallina, alla nuova posizione della pallina e alla posizione del pad.

Listato 9. Variabili sulla posizione della pallina

var pallinaRectOld = new Rectangle(pallina.x-raggio,pallina.y-raggio, raggio*2, raggio*2);
var pallinaRectNew = new Rectangle(newX-raggio, newY-raggio, raggio*2, raggio*2);
var padRect = new Rectangle(pad.x-padW/2, pad.y-paddleHt/2, padW, paddleHt);

A questo punto, per stabilire se la pallina ha colpito il pad, possiamo confrontare il bordo inferiore del rettangolo della pallina con il bordo superiore del rettangolo del pad, in questo modo:

if (pallinaRectNew.bottom >= padRect.top) {

Vogliamo anche essere sicuri che la pallina non stesse toccando il pad già nella posizione precedente e soprattutto vogliamo controllare che la pallina sia interna al pad, in caso contrario vorrebbe dire che è caduta e quindi dobbiamo sottrarre una vita all'utente.

Listato 10. Controlla la posizione della pallina

if (pallinaRectNew.bottom >= padRect.top) {
  if (pallinaRectOld.bottom < padRect.top) {
    // se la pallina colpisce il pad, rimbalza
    if (pallinaRectNew.right > padRect.left && pallinaRectNew.left < padRect.right) {
      // rimbalzo verticale
      newY -= pallinaRectNew.bottom - padRect.top; // nuova posizione
      pallinaDY *= -1; // nuova direzione verticale
      // impostazione del nuovo angolo
      pallinaDX = (newX-pad.x)*padCurve;
    }
  }else if (pallinaRectNew.top > 400) {
    // altrimenti viene persa una vita
    removeChild(pallina);
    pallina = null;
    if(vite==0){
      gameOver();
    }
    return;
  }
}

Se il bordo inferiore della pallina (pallinaRectNew.bottom) risulta più in basso del bordo superiore del pad (padRect.top) controlliamo che la pallina non stesse già toccando il pad in precedenza e che si trovi all'interno dei margini del pad stesso (padRect.left e padRect.right), nel qual caso impostiamo la nuova Y uguale al bordo del pad (per evitare che la pallina si sovrapponga troppo a esso), quindi impostiamo la nuova direzione verticale (-1) e la nuova direzione orizzontale, calcolata in base alla distanza tra le coordinate orizzontali di pallina e pad, moltiplicato per la curvatura del pad.

Quando la pallina non è compresa nell'area del pad, non la rimuoviamo immediatamente ma aspettiamo che il suo bordo superiore sia uscito dallo stage (> 400). Nel caso in cui quella persa fosse l'ultima vita, richiameremo la funzione gameOver, altrimenti l'utente potrà giocare una nuova pallina cliccando sullo stage.

Il richiamo alla funzione gameOver è commentato dato che non la abbiamo ancora dichiarata.

Modifichiamo la funzione muoviOggetti aggiungendo il richiamo alla funzione muoviPalla.

Rimbalzo della pallina sul pad

Passiamo ora alle collisioni coi muri; il concetto è identico a quanto visto per il pad, con due eccezioni: nel caso di collisione con i muri laterali, il rimbalzo sarà in orizzontale, inoltre, poiché abbiamo già dichiarato le variabili con le posizioni dei muri superiore, destro e sinistro l'unico oggetto Rectangle che useremo sarà quello della pallina. Ultima differenza sarà che il rimbalzo avverà allo stesso modo su tutto il muro, differentemente dal pad dove colpire più o meno lontano dal centro causa un diverso effetto sulla pallina.

Listato 11. Gestisce i rimbalzi

// muro superiore
if (pallinaRectNew.top < muroTop) {
  newY += muroTop - pallinaRectNew.top;
  pallinaDY *= -1;
}
// muro sinistro
if (pallinaRectNew.left < muroSx) {
  newX += muroSx - pallinaRectNew.left;
  pallinaDX *= -1;
}
// muro destro
if (pallinaRectNew.right > muroDx) {
  newX += muroDx - pallinaRectNew.right;
  pallinaDX *= -1;
}

Nel caso in cui il la posizione della palla sia inferiore (nel caso di muri superiore e sinistro) o superiore (nel caso del muro destro) al valore indicato, viene cambiata direzione alla palla.

Rimbalzo della pallina sul pad e sui muri

Se per pad e muri la pallina può colpire solamente da un lato, ogni mattoncino può essere colpito da qualsiasi direzione. Dobbiamo poi controllare ogni volta tutti i mattoncini presenti nel gioco ed eliminare quelli colpiti.

Per prima cosa creiamo un ciclo che analizzi tutti i mattoncini, sfruttando l'array mattoncini che contiene il riferimento a ogni mattoncino presente nel gioco.

for(var i:int=mattoncini.length-1;i>=0;i--) {

Per ogni mattoncino creiamo il relativo rettangolo.

var mattoncinoRect:Rectangle = mattoncini[i].getRect(this);

Prima di calcolare da quale lato avviene la collisione ci interessa sapere se la pallina e il mattoncino sono entrati in contatto: è meno dispendioso in termini di memoria controllare prima la collisione "generica" e poi eventualmente valutare da quale lato sia avvenuta, piuttosto che analizzare sempre i quattro lati di ogni mattoncino. Per questo scopo è molto utile il comando intersects, un metodo della classe Rectangle che si occupa di valutare l'intersezione tra due oggetti di tipo Rectangle. Nel nostro caso possiamo sfruttarlo per confrontare i rettangoli di pallina e mattoncino:

if (mattoncinoRect.intersects(pallinaRectNew)) {

Possiamo ora passare al controllo del lato da cui è avvenuta effettivamente la collisione: per questo confronteremo ogni bordo del mattoncino con il rispettivo bordo della pallina.

Listato 12. Controlla il lato del mattoncino colpito

if (pallinaRectOld.right < mattoncinoRect.left){
  newX += 2*(mattoncinoRect.left - pallinaRectOld.right);
  pallinaDX *= -1;
}else if (pallinaRectOld.left > mattoncinoRect.right){
  newX += 2*(mattoncinoRect.right - pallinaRectOld.left);
  pallinaDX *= -1;
}else if (pallinaRectOld.top > mattoncinoRect.bottom){
  pallinaDY *= -1;
  newY += 2*(mattoncinoRect.bottom-pallinaRectNew.top);
}else if (pallinaRectOld.bottom < mattoncinoRect.top){
  pallinaDY *= -1;
  newY += 2*(mattoncinoRect.top - pallinaRectNew.bottom);
}

Vediamo che il rimbalzo è calcolato impostando la nuova coordinata e la nuova direzione orizzontale o verticale a seconda del bordo che è stato colpito.

Le ultime operazioni da eseguire sono la rimozione del mattoncino colpito sia dallo schermo che dall'array, il controllo dei mattoncini rimasti e la fine del gioco quando non ci sono più mattoncini.

Listato 13. Rimuove i mattincini e calcola la fine del gioco

removeChild(mattoncini[i]);
mattoncini.splice(i,1);
if (mattoncini.length < 1) {
//gameOver();
return;
}

Fine del gioco

Non ci resta che impostare la funzione gameOver; che avrà il compito di rimuovere gli elementi di gioco e di eliminare gli eventi CLICK e ENTER_FRAME, inoltre mostrerà un messaggio al giocatore.

Listato 14. Termina il gioco e mostra un messaggio

private function gameOver(){
  var testo = new TextField()
  testo.text = "Game over"
  testo.x = stage.width / 2
  testo.y = stage.height / 2
  addChild(testo)
  removeChild(pad)
  for(var i:int=mattoncini.length-1;i>=0;i--) {
    removeChild(mattoncini[i])
  }
  pad = null
  mattoncini = null
  if(pallina != null){
    removeChild(pallina)
    pallina = null
  }
  removeEventListener(Event.ENTER_FRAME,muoviOggetti);
  stage.removeEventListener(MouseEvent.CLICK,nuovaPalla);
}

Ecco il risultato finale:

Il gioco completo

Ti consigliamo anche