cerca
Giochino del Sottomarino
modifica cronologia stampa login logout

Wiki

Tools

Categorie

Help

edit SideBar

Giochino del Sottomarino

<< Esplosioni e oscillazioni | Giochino Sottomarino | Ottobre Rosso >>

 :: Giochino del Sottomarino - Fumo e Stati di Gioco ::

Aggiungere Fumo alla Nave

La Nave sarebbe più carina se uscisse del fumo dal comignolo. Per fare il fumo, però, non usiamo un'animazione "semplice", perché voglio introdurre un coso nuovo. In Slick c'è la possibilità di utilizzare un particle system, ovvero una specie di sistema autonomo in cui ci sono delle particelle più o meno libere, che rispondono a variabili come la gravità etc., e che si muovono in modo quasi casuale. Serve per creare cose come esplosioni, fumo, scintille, in generale effetti grafici un po' più casuali e dall'aspetto naturale rispetto ad animazioni predefinite.

Nella pagina di Slick c'è il link al webstart di Pedigree, che è un editor che permette di creare questi particle system. Poi questi sistemi vengono esportati, e vengono tirati su via codice. Ovviamente sono un po' più onerosi per la CPU rispetto ad un'animazione "normale", ma tanto qui non stiamo esagerando.

Qui dentro c'è l'occorrente per il fumo: un semplice effetto che ho preparato. Lo zip contiene due file:

  • fumoemitter.xml
  • particle.tga

L'XML contiene la definizione dell'emettitore. L'immagine è invece l'immagine che sarà usata per ogni particella. Mettiamo questi due files nella solita cartella, ./data.

Andiamo quindi alla classe Nave. Ci servono queste variabili nuove:

 ParticleSystem fumo;
 ConfigurableEmitter fumoEmitter;

fumo è il particle system, che può contenere a sua volta diversi emettitori. Pedigree permette di esportare interi sistemi con diversi emettitori etc., ma qui per motivi di flessibilità creeremo il particle system via codice e ci attaccheremo un emitter, fumoEmitter.

Nel costruttore di Nave mettiamo queste righe:

 fumo = new ParticleSystem(new Image("./data/particle.tga"));
 fumoEmitter = ParticleIO.loadEmitter("./data/fumoemitter.xml");
 fumo.addEmitter(fumoEmitter);

Questo codice potrebbe generare un'eccezione IOException, regolatevi voi se volete racchiuderlo in un try .. catch oppure far rimbalzare il tutto fuori da Nave.

Le righe sono abbastanza autoesplicative: creo il particle system dandogli in pasto il nome dell'immagine che farà da particella. Creo successivamente l'emitter a partire dalla configurazione XML, e poi aggiungo questo emitter al system.

Nel metodo render devo anche disegnare il fumo. Decidiamo di disegnarlo "sopra" la Nave, quindi facciamo così:

 if (direction) {        
   naveDx.draw(box.getX(), box.getY());
   fumo.render(box.getX() + box.getWidth() / 2 - 15, box.getY());
 } else {
   naveSx.draw(box.getX(), box.getY());
   fumo.render(box.getX() + box.getWidth() / 2 + 15, box.getY());
 }

Le coordinate passate alla render del fumo sono quelle dell'emitter. Sono definite così perché il camino della Nave non è esattamente centrato rispetto all'immagine della Nave stessa.

Andiamo ora al metodo update. Chiaramente dovremo anche aggiornare l'emitter del fumo. Però, siccome la Nave si muove, vogliamo che il fumo faccia come il fumo vero, cioè resti in qualche modo "dietro" alla Nave.

A questo proposito, l'emitter ci viene incontro, perché ha un parametro che rappresenta il vento. Lo impostiamo così:

 fumoEmitter.windFactor.setValue((speed / maxSpeed) * -3.0f * dirMult);
 fumo.update(delta);

Così facendo il fumo andrà in direzione opposta alla Nave. Possiamo ora provare il gioco e vedere l'effetto che fa:)

Aggiungiamo uno Stato

Vogliamo aggiungere uno stato di gioco in cui prossimamente metteremo il boss di fine livello:) Prepariamo dunque le cose per questo "switch off" (termine di moda ora che c'è il digitale terrestre).

Per prima cosa, ci serve una nuova classe che chiameremo StatoBoss. Anch'essa implementerà GameState, e per ora la lasciamo vuota. Comunque, per renderla funzionale, invito a guardare la prima lezione? per un setup di base. Prossimamente la riempiremo anche.

L'ID degli stati e altre cosucce globali

Dal momento che stiamo introducendo un nuovo stato di gioco, nasce la necessità di tenere via delle informazioni che "travalichino" i confini della singola istanza di ogni GameState. Un modo per ottenere ciò è quello di utilizzare variabili pubbliche statiche infilate in una classe apposita.

Creiamo quindi una classe chiamata fantasiosamente Globali e ci mettiamo alcune variabili.

 public static int punti;

in cui salveremo il punteggio ottenuto dal giocatore.

 public static int vittime;

in cui salveremo il numero di sottomarini affondati, per avere un'idea della nostra efficienza sterminatrice.

Poi ci mettiamo anche queste variabili:

 public static final int LIVELLO_GIOCO = 0;
 public static final int LIVELLO_BOSS = 1;

In questo modo non rischiamo di confonderci con gli ID dei vari stati di Gioco. Lo StatoGioco dovrà quindi ritornare Globali.LIVELLO_GIOCO nel suo metodo getID(), mentre StatoBoss dovrà ritornare Globali.LIVELLO_BOSS.

Aggiunta dello StatoBoss

In Applicazione dobbiamo semplicemente istanziare il nuovo stato, ed aggiungerlo alla lista:

 GameState boss = new StatoBoss();
 boss.setInput(container.getInput());
 this.addState(boss);

Passaggio da uno stato all'altro

Decidiamo che il primo livello dovrà durare 60 secondi, trascorsi i quali si passerà automaticamente allo StatoBoss. Per fare questo tireremo in piedi un nuovo Timer in StatoGioco, il quale allo scadere chiamerà una callback che farà cambiare lo stato corrente all'applicazione. Colui che può cambiare livello è il Game, che è visibile nella update. Questo vuol dire che la callback dovrà tirare su una flag, che verrà poi letta nella update.

 private boolean cambiaLivello;

e la mettiamo a false in enter.

Creiamo poi il timer:

 private Timer timerLivello;

e lo possiamo inizializzare direttamente nel costruttore di StatoGioco in questo modo:

 timerLivello = new Timer(new Runnable() {
   public void run() {
     cambiaLivello = true;
   }
 }, 60 * 1000);

e sapete già che cosa succederà:) Nella update lo aggiorniamo, e nella enter ne chiamiamo lo start.

Il giocatore deve sapere quanto tempo manca!

Giusto.

E come facciamo?

Andiamo nella render. Slick offre un paio di sistemi per disegnare testo a schermo. Il più semplice sfrutta il metodo drawString di Graphics, ed è quello che faremo anche noi.

Come prima cosa, dobbiamo sapere quanto tempo effettivamente manca:

 int tempoMancante = (timerLivello.getTempo() - timerLivello.getTempoTrascorso()) / 1000;

Divido per 1000 perché vogliamo il tempo in secondi, mentre il Timer funziona a millisecondi. Appena sotto aggiungiamo anche questo:

 g.drawString("Tempo: " + tempoMancante, screenWidth - 100, 10);

così che sarà visualizzato il tempo mancante.

Altre informazioni

Già che ci siamo, vogliamo visualizzare anche altre informazioni, ovvero i punti e il numero di sottomarini affondati, cioè le vittime. Sempre in render quindi mettiamo queste righe:

 g.drawString("Affondati: " + Globali.vittime, screenWidth - 135, 40);
 g.drawString("Punti: " + Globali.punti, screenWidth - 100, 70);

Nella enter dobbiamo azzerare queste variabili, altrimenti cresceranno sempre.

Andiamo ora nel metodo update, nel punto in cui il controllo delle collisioni tra Bombe e Sottomarini ha avuto successo, e aggiungiamo queste due righe:

 Globali.vittime ++;
 Globali.punti += 100;

Ovviamente se i punti non vanno bene mettetene degli altri:)

Il passaggio di stato

Sempre in update, dobbiamo effettivamente attivare il passaggio allo stato StatoBoss. Lo facciamo così:

 if (cambiaLivello) {
   cambiaLivello = false;
   game.enterState(Globali.LIVELLO_BOSS);
 }

Il passaggio sarà un po' brusco, ma vedremo come avere delle transizioni tra uno stato e l'altro.

<< Esplosioni e oscillazioni | Giochino Sottomarino | Ottobre Rosso >>


Guide