cerca
Giochino del Sottomarino
modifica cronologia stampa login logout

Wiki

Tools

Categorie

Help

edit SideBar

Giochino del Sottomarino

<< Classi principali: Entity e Nave | Giochino Sottomarino | Collisioni >>

 :: Giochino del Sottomarino - Sottomarini e bombe ::

Il Sottomarino

Il Sottomarino è il nostro nemico. Per ora niente immagini, però possiamo comunque inventarci una classe ed infilarla nel gioco. Anche lui dovrà implementare Entity.

Così come la nave, il Sottomarino avrà una posizione, delle dimensioni ed una velocità:

 public class Sottomarino implements Entity {
  private float x;
  private float y;
  private float speed;
  private boolean active;
  private Rectangle box;

active ci dice se il sottomarino è attivo o meno. A che cosa serve? Ora lo spiego.

La meccanica di gioco prevede che ogni tot secondi un sottomarino parta da un lato dello schermo ad una profondità variabile e con una velocità variabile. Potremmo quindi, ogni tot secondi, istanziare un nuovo sottomarino e farlo partire nel gioco. Non è una cosa sbagliata, non stiamo istanziando l'universo mondo e quindi non si tratterebbe di nulla di pesante. Però vogliamo usare un altro sistema, un po' differente, che ci permette di limitare anche il numero massimo di sottomarini che stanno contemporaneamente sullo schermo. Dei mille modi per ottenere ciò, qui ne presenterò uno.

Nel costruttore del sottomarino mettiamo dei valori di inizializzazione:

 public Sottomarino() {
  x = 0f;
  y = 0f;
  speed = 0f;

  box = new Rectangle(0, 0, 100, 100);

  active = false;
 }

e ci premuriamo di lasciare il sottomarino inattivo. Per farlo partire ci serviamo di un altro metodo, start, in cui gli passeremo posizione, dimensione e velocità.

 public void start(float x, float y, float width, float height, float speed) {
  this.x = x;
  this.y = y;
  this.speed = speed;
  this.box.setBounds(x, y, width, height);
  this.active = true;
 }

Questo vuol dire che lo StatoGioco, quando ci ha voglia, si inventerà delle dimensioni e una posizione e dirà ad un Sottomarino di assumere quella forma e di partire alla ventura.

Il metodo render è il solito, visto che per ora non ci sono immagini:

 public void render(GameContainer container, StateBasedGame game, Graphics g) {
   if (active) {
     g.draw(box);
   }
  }

Nel metodo update invece dobbiamo tenere conto della speed per avere un controllo in più: se il sottomarino esce dallo schermo, deve essere disattivato.

 public void update(GameContainer container, StateBasedGame game, int delta) {

   if (active) {
     x += speed * delta;

     if ((speed > 0 && x + box.getWidth() > container.getWidth())
        || (speed < 0 && x < 0))
     {
       active = false;
       Log.info("Sottomarino fuori schermo");
     }

     box.setLocation(x, y);
   }
 }

A seconda della speed la condizione per sapere se il Sottomarino è uscito dallo schermo è differente. La chiamata Log.info viene completata automaticamente con CTRL-SHIFT-I, e si tratta di una utility interna a Slick.

Facciamo aggiungere a NetBeans, tramite la funzionalità Insert Code, il getter e il setter della variabile booleana active, perché serviranno qui sotto. Per generare automaticamente questo codice si clicca con il destro nella zona di editing, si sceglie Insert code e quindi scegliere i getter, i setter e così via. Comodo.

I Sottomarini nello StatoGioco

Nello StatoGioco voglio avere una lista di Sottomarini, e la dichiaro così:

 private ArrayList<Sottomarino> nemici;

Nel metodo enter popolerò questa lista:

 nemici = new ArrayList<Sottomarino>();

 for (int i = 0; i < 20; i++) {
   nemici.add(new Sottomarino());
 }

In update mi preoccupo di aggiornare tutti i sottomarini:

 for (Sottomarino s : nemici) {
   s.update(container, game, delta);
  }

e in render li disegnerò tutti:

 for (Sottomarino s : nemici) {
   s.render(container, game, g);
 }

Quando li faccio partire? Il Timer

Ma quando li faccio partire? Ogni tot tempo, si era detto. La cosa più semplice è la seguente: tenere via una variabile ed incrementarla con il valore di delta ad ogni giro. Se supera un certo valore, allora scateno un evento.

Questa è una cosa piuttosto comune e di facile implementazione. Ma voglio sfruttarla per introdurre una cosuccia che magari non tutti conoscono, e cioè la classe anonima.

Innanzitutto, decido di creare una nuova classe e di chiamarla Timer. Questa nuova classe contiene un valore di tempo da raggiungere ed il tempo trascorso finora, e viene aggiornata con il classico update. Per renderla un filo più completa deve anche essere possibile farla partire, fermarla, metterla in pausa. Quando il tempo è trascorso, il Timer deve automaticamente scatenare un certo evento.

Come faccio a fargli memorizzare un evento? Siccome siamo in Java, si tratterà di un oggetto. Decido che l'evento sia un'istanza dell'interfaccia Runnable. Viene usata di solito per i thread, e quindi è già esistente, è semplice e fa per noi. L'unico metodo che ha è run. Quello che succederà sarà quindi questo:

  • creo un'istanza di Runnable
  • nel metodo run ci metto il codice del mio evento
  • passo il tutto al Timer e lo faccio partire
  • aggiorno il timer nella update dello StatoGioco
  • quando l'evento viene scatenato, faccio ripartire il Timer

Siccome la classe Timer è lunghetta, non la posto tutta, ma la allego.

L'evento

Torniamo allo StatoGioco. Ci serve una variabile per il Timer:

 private Timer timerSottomarino;

e in enter la istanzio completa di tutto:

 timerSottomarino = new Timer(new Runnable() {
   public void run() {
     attivaSottomarino();
   }
 }, 5000);

 timerSottomarino.start();

Ecco la classe anonima. Il primo parametro del costruttore di Timer vuole un'istanza di Runnable. Invece di dichiararla a parte, con questa sintassi la posso dichiarare ed istanziare al volo, con tanto di metodo run incluso. Il vantaggio è che a questa istanza anonima di Runnable saranno visibili tutti i metodi della classe che la ospita, che nel nostro caso è la classe StatoGioco!

Infatto, in run c'è la chiamata ad attivaSottomarino, che è un metodo di StatoGioco:

 public void attivaSottomarino() {
  for (Sottomarino s : nemici) {
    if (!s.isActive()) {
      // posizione casuale

      float width = (float) (50 + Math.random() * 200f);
      float height = (float) (20 + Math.random() * 50f);
      float depth = (float) (200 + Math.random() * (screenHeight - 200));
      float speed = (float) ((25 + Math.random() * 100) / 1000f);

      double caso = Math.random() * 100;

      float ics = caso < 50 ? 0 : screenWidth - width - 1;

      s.start(ics, depth, width, height, caso < 50 ? speed : speed * -1);

      break;
      }
   }

   timerSottomarino.start();
 }

Le variabili screenWidth e screenHeight le dichiariamo come int in StatoGioco, e nel metodo init le inizializziamo:

 screenWidth = container.getWidth();
 screenHeight = container.getHeight();

In questo metodo ci inventiamo la forma e la posizione del Sottomarino, e lo facciamo partire.

Le Bombe

Usiamo, per le Bombe, la stessa strategia dei Sottomarini, cioè ne teniamo una lista in StatoGioco e le gestiamo da lì.

 public class Bomba implements Entity {

    boolean active;
    private float x;
    private float y;

    private float speed = 100.0f / 1000.0f;

    private Rectangle box;

Come le altre classi, ha bisogno delle solite variabili. La '''speed' è fissa perché assumiamo che sia sempre quella. Magari poi ci viene in mente di modificarla, ma per ora va bene così.

Il costruttore è molto simile a quello del Sottomarino:

 public Bomba (float x, float y) {
  this.x = x;
  this.y = y;

  box = new Rectangle(x, y, 32, 32);

  active = false;
 }

Usiamo anche qui il metodo start per far partire la Bomba. Diversamente dal sottomarino qui le dimensioni della box sono fisse.

 public void start(float x, float y) {
  this.x = x;
  this.y = y;
  active = true;

 }

Nel metodo update dobbiamo vedere se la bomba è scomparsa al di sotto dello schermo. In quel caso, la disattiviamo:

 public void update(GameContainer container, StateBasedGame game, int delta) {

   if (active) {

     y += speed * delta;

     if (y > container.getHeight()) {

       Log.info("Bomba fuori dallo schermo");
       active = false;
     }

     box.setLocation(x, y);

  }

 }

Il metodo render è sempre il solito:

 public void render(GameContainer container, StateBasedGame game, Graphics g) {
  if (active) {
    g.draw(box);
  }
 }

E facciamo mettere a NetBeans anche metodi accessori per la variabile active.

Le Bombe nello StatoGioco

Torniamo allo StatoGioco. Ci serve un array che conterrà le bombe:

 private ArrayList<Bomba> bombe;

che verrà creato e popolato nel metodo enter:

 bombe = new ArrayList<Bomba>();

 for (int i = 0; i < 10; i++) {
   bombe.add(new Bomba(0.0f, 0.0f));
 }

Esattamente come per i Sottomarini, in update aggiornerò tutte le bombe:

 for (Bomba b : bombe) {
   b.update(container, game, delta);
 }

e in render le disegno:

 for (Bomba b : bombe) {
   b.render(container, game, g);
 }

Il lancio delle bombe

Il giocatore lancerà le bombe premendo SPAZIO. Dal momento che l'array delle bombe contiene 10 elementi, non ci saranno più di 10 bombe contemporaneamente sullo schermo. Per essere precisi, vogliamo che la bomba venga sganciata quando il giocatore ha rilasciato il tasto spazio. C'è un metodo apposito nel GameState:

 public void keyReleased(int key, char c) {

  if (key == Input.KEY_SPACE) {

    for (Bomba b : bombe) {
      if (!b.isActive()) {
        Rectangle r = player.getBoundingBox();
        b.start(r.getX() + r.getWidth() / 2, r.getY() + r.getHeight());
        break;
      }
    }
  }
 }

In questo codice c'è una chiamata ad un metodo non ancora esistente: player.getBoundingBox(). Questo metodo dovrà tornare la box del player, cioè della Nave mossa dal giocatore.

Lascio questo come esercizio: aggiungere il metodo getBoundingBox alla Entity e quindi inserirlo in tutte le classi che finora implementano Entity, cioè Nave, Sottomarino e Bomba. Ci servirà per identificare le collisioni:)

Dopo aver fatto queste modifiche, premendo SPAZIO si rilasceranno le bombe sotto la Nave:)

<< Classi principali: Entity e Nave | Giochino Sottomarino | Collisioni >>


Guide