cerca
Giochino del Sottomarino
modifica cronologia stampa login logout

Wiki

Tools

Categorie

Help

edit SideBar

Utenti.GiochinoSottomarino-OttobreRosso History

Show minor edits - Show changes to output

Changed lines 273-274 from:
''...continua...''
to:
!!Lampeggiamento dell'Ottobre Rosso
Come dicevamo sopra, quando viene colpito l
'Ottobre Rosso deve "lampeggiare", cioè diventare trasparente ad intermittenza, come succedeva nei giochi di una volta. Costruiamo quindi un '''Timer''' chiamato '''lampeggiaTimer''' in '''OttobreRosso''', e creiamo anche una variabile '''boolean hit'''. Il '''Timer''' lo inizializziamo così:

lampeggiaTimer = new Timer(new Runnable() {
public void run() {
hit = false;
}
}, 1 * 1000);

e ricordiamoci di aggiornarlo in '''update'''. Ci serve anche un '''LFO''', che chiamiamo '''hitLFO''', e lo inizalizziamo così:

hitLFO = new LFO(10, 0, 1);

Vogliamo che l'Ottobre Rosso lampeggi 10 volte al secondo.

La variabile '''hit''' viene messa a '''true''' nel metodo '''hit''' di '''OttobreRosso''', che a sua volta viene chiamato da '''StatoBoss''' quando si verifica una collisione tra una '''Bomba''' e il sottomarinone rosso. Il metodo '''hit''' è fatto così:

public void hit() {
life--;
hit = true;

hitLFO.restart();
lampeggiaTimer.start();

}

In '''update''' mettiamo anche:

if (hit) {
hitLFO.update(delta);
}

ma la cosa interessante viene in '''render''':

Image i = direction ? ottobreDx : ottobreSx;

if (!hit || hitLFO.getValue() >= 0.0f) {
i.draw(box.getX(), box.getY());
}

Con questo codice facciamo in modo che, quando l'Ottobre Rosso viene colpito, allora usiamo l'LFO per sapere se disegnare o meno l'immagine. Facciamo così "lampeggiare" l'Ottobre Rosso.

!!Sequenza di affondamento
L'ultimo comportamento dell'Ottobre Rosso riguarda il suo affondamento. Quando viene colpito 3 volte, allora deve affondare. La sequenza di affondamento consiste nel sottomarino che ruota la propria prua verso il fondo del mare, e scompare. Ci serve una variabile di stato booleana che chiamiamo '''affondamento'''. Per non sbagliare la mettiamo a '''true''' in '''start''':

public void start() {
partenzaTimer.start();
life = 3;
affondamento = false;
angolo = 0;
ottobreDx.setRotation(0.0f);
ottobreSx.setRotation(0.0f);
}

Ci servono poi anche le variabili per gestire la rotazione e l'affondamento del sottomarino:

private float angolo = 0;
private float velAngolare = 360.0f / 10000.0f;
private float velAffondamento = 50.0f / 1000.0f;

La '''velAngolare''' è la velocità di rotazione, mentre la '''velAffondamento''' è la velocità lineare con cui il sottomarino si inabissa.

Aggiungiamo il metodo '''kill''':

public void kill() {
affondamento = true;
missileTimer.stop();
}

che verrà chiamato dallo '''StatoBoss''' quando, controllando che le vite dell'Ottobre Rosso sono diventate 0, deciderà di ucciderlo.

Sia '''update''' che '''render''' andranno modificati, perché il sottomarino deve avere comportamenti diversi a seconda della "fase" in cui si trova. Se sta funzionando normalmente, deve andare di qua e di là e lanciare missili etc. Se invece sta affondando, deve fare tutt'altro, ovvero ruotare puntando la prua verso il basso e poi scomparire, e quando è finito sotto lo schermo deve chiamare il metodo '''affondamentoCompleto''' del suo '''ManagerOttobreRosso'''. Ecco quindi l'intero metodo '''update''' riscritto in luce di queste nuove scoperte:

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

partenzaTimer.update(delta);
lampeggiaTimer.update(delta);
missileTimer.update(delta);

if (active) {
if (affondamento) {
angolo += velAngolare * delta;
if (angolo > 90.0f) {
angolo = 90.0f;
}

float y = box.getY() + velAffondamento * delta;
box.setLocation(box.getX(), y);

if (y >= container.getHeight() + box.getWidth() / 2) {
myGestore.affondamentoCompleto();
affondamento = false;
}
} else {
int multi = direction ? 1 : -1;
float x = box.getX() + this.speed * delta * multi;
box.setLocation(x, box.getY());

if (hit) {
hitLFO.update(delta);
}

if ((direction && box.getX() > container.getWidth())
|| (!direction && box.getX() < -box.getWidth())) {
active = false;
missileTimer.stop();

// Faccio ripartire il timer
partenzaTimer.start();
}
}
}
}

ed ecco anche il metodo '''render''':

public void render(GameContainer container, StateBasedGame game, Graphics g) {
if (active) {
if (affondamento) {
if (direction) {
ottobreDx.setRotation(angolo);
ottobreDx.draw(box.getX(), box.getY());
} else {
ottobreSx.setRotation(-angolo);
ottobreSx.draw(box.getX(), box.getY());
}
} else {
Image i = direction ? ottobreDx : ottobreSx;

if (!hit || hitLFO.getValue() >= 0.0f) {
i.draw(box.getX(), box.getY());
}
}
}
}

Con questo, l'Ottobre Rosso è concluso e questa lunga e faticosa pagina è terminata!:)
Changed line 180 from:
%lframe%Attach:missile.png|'''Il missile'''
to:
%lframe%Attach:sottomarinomissile.png|'''Il missile'''
Changed lines 208-275 from:
''...continua...''
to:
!!!Missile
Veniamo dunque al
'''Missile'''. Quando lo costruiamo, gli diciamo quale deve essere il suo limite superiore. Quando arriva al limite chiamerà il '''check''' che abbiamo appena visto.

private boolean active = false;
private Image missile;
private Rectangle box;
private float upperLimit;
private float speed = 150.0f / 1000.0f;
private CheckMissileNave checkMissileNave;

public Missile(float upperLimit, CheckMissileNave cmn) throws SlickException {
this.upperLimit = upperLimit;
this.checkMissileNave = cmn;

missile = new Image("./data/missile.png");

box = new Rectangle(0, 0, missile.getWidth(), missile.getHeight());
}

Ci mettiamo anche il metodo '''start''', che fa le seguenti cose:

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

box.setLocation(x, y);
}

Questo vuol dire che in '''StatoBoss''' possiamo finalmente implementare il metodo '''lanciaMissile''' di '''ManagerOttobreRosso''':

public void lanciaMissile(float x, float y) {
for (Missile m : missili) {
if (!m.isActive()) {
m.start(x, y);
break;
}
}
}

Tornando al '''Missile''', il metodo '''update''' è il solito, con l'aggiunta della chiamata a '''checkMissileNave''' quando termina la vita utile:

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

if (active) {

float y = box.getY() - speed * delta;

if (y < upperLimit) {
active = false;
checkMissileNave.check(box.getX(), box.getX() + box.getWidth());
}

box.setLocation(box.getX(), y);
}
}

E anche '''render''' scade nella banalità:)

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

E con questo anche il '''Missile''' è funzionante! Quando l''''OttobreRosso''' decide di sparare un '''Missile''', si rivolge al suo manager, che nel nostro caso è ancora '''StatoBoss'''. A sua volta il '''Missile''', quando arriva alla superficie, si rivolge al suo manager per dirgli di eseguire il controllo per l'eventuale collisione con la '''Nave'''. Così facendo si eseguono controlli solo quando strettamente necessario. Non che siano costosi, sti calcoli, ma può essere una tecnica interessante:)

''...continua...''
Changed lines 180-181 from:
''...continua...''
to:
%lframe%Attach:missile.png|'''Il missile'''

Il '''Missile''' viene gestito dallo '''StatoBoss''', allo stesso modo in cui vengono gestite anche le altre entità, cioè infilandolo in un'ArrayList e così via.

Stabiliamo però che il '''Missile''' arriverà al massimo alla superficie dell'acqua, e quando arriva qui verificherà se è entrato in collisione con la '''Nave'''. Anche in questo caso, il '''Missile''' deve poter comunicare con lo '''StatoBoss''', e utilizziamo il solito sistema di interfacce, creandone una appositamente che verrà implementata da '''StatoBoss'''.

public interface CheckMissileNave {
public void check(float sx, float ex);
}

Questo metodo viene invocato dal '''Missile''' quando si accorge di essere finito fuori "range". '''sx''' è il valore sulle ascisse del lato sinistro della bounding box del '''Missile''', mentre '''ex''' è il valore del lato destro sempre sullo stesso asse. Se uno dei due estremi è incluso tra gli estremi della bounding box della '''Nave''' allora vuol dire che questa è stata colpita.

Andiamo quindi subito ad implementare il metodo '''check''' in '''StatoBoss''':

public void check(float sx, float ex) {
Rectangle playerBox = player.getBoundingBox();
if ((sx >= playerBox.getX() && sx <= playerBox.getX() + playerBox.getWidth())
|| (ex >= playerBox.getX() && ex <= playerBox.getX() + playerBox.getWidth()))
{
Log.info("Il missile ha colpito la nave!");
attivaEsplosione(playerBox.getCenterX(), playerBox.getCenterY());

Globali.punti -= 200;
}
}

Non so se l'avevo detto precedentemente, comunque si suppone che lo '''StatoBoss''' sia in pratica una replica di '''StatoGioco''', e che quindi abbia anche lui un '''player''' e così via.

''...continua...''
Changed line 81 from:
float depth = (float) (300 + Math.random() * (container.getHeight() - 300 -
to:
float depth = (float) (300 + Math.random() * (screenHeight - 300 -
Changed line 91 from:
box.setLocation(container.getWidth(), depth);
to:
box.setLocation(screenWidth, depth);
Added lines 99-100:
Le variabili '''screenWidth''' e '''screenHeight''' sono le dimensioni dello schermo. Per averle, occorre che venga passato il '''GameContainer''' al costruttore, e da lì le trarremo mediante '''container.getWidth()''' e '''container.getHeight()'''.
Added lines 135-180:

!!Missili
Ci vuole un '''Timer''':

private Timer missileTimer;

e nel costruttore lo tiriamo in piedi:

missileTimer = new Timer(new Runnable() {

public void run() {

double caso = Math.random() * 100;

if (caso > 40) {
myGestore.lanciaMissile(box.getX() + box.getWidth() / 2, box.getY());
}

missileTimer.stop();
missileTimer.start();

}
}, 1300);

La variabile '''caso''' mi dà la probabilità che il missile venga effettivamente lanciato, confrontandola con il 40. Viene chiamato il metodo '''lanciaMissile''' del '''ManagerOttobreRosso''', ovvero lo '''StatoBoss'''.

Dobbiamo però anche ricordare che i missili possono essere lanciati solo se il sottomarino è attivo, cioè sta girando per lo schermo. Subito dopo, quindi, nel costruttore, chiamiamo

missileTimer.stop();

per far sì che questo '''Timer''' non prosegua.

In '''update''' lo aggiorniamo:

missileTimer.update(delta);

Quando il sottomarino parte, cioè in '''partenza''', facciamo partire anche il timer:

missileTimer.start();

Quando il sottomarino esce dallo schermo, lo fermiamo:

missileTimer.stop();

!!!La classe Missile
''...continua...''
Changed lines 30-31 from:
Qui ci sono le variabili standard che fanno parte dell''''Ottobre Rosso'''
to:
Anche l''''Ottobre Rosso''' è una '''Entity'''. Qui ci sono le variabili standard che fanno parte dell''''Ottobre Rosso'''. I metodi soliti li lascio a voi.
Added lines 112-119:
Dobbiamo anche controllare che il sottomarino non sia uscito dai bordi. Se è così, allora dobbiamo disattivarlo e far ripartire il '''partenzaTimer''':

if ((direction && box.getX() > container.getWidth())
|| (!direction && box.getX() < -box.getWidth())) {
active = false;

// Faccio ripartire il timer
partenzaTimer.start();
Added lines 122-133:
In '''render''' quindi dobbiamo disegnare il sottomarino:

if (active) {
if (direction) {
ottobreDx.draw(box.getX(), box.getY());
} else {
ottobreSx.draw(box.getX(), box.getY());
}
}

E quindi abbiamo già l''''Ottobre Rosso''' che scorrazza per lo schermo di gran carriera:)
Added lines 1-117:
(:title Giochino del Sottomarino:)

<<|[[Giochino Sottomarino]]|>>

%titolo%''':: Giochino del Sottomarino - Ottobre Rosso ::'''

!!Affondiamo l'Ottobre Rosso!
%lframe%Attach:SottomarinoOttobreRosso.png|'''L'Ottobre Rosso'''

Dal momento che si tratta di un gioco di sottomarini, non possiamo astenerci dall'infilarci l'Ottobre Rosso:) Questo sarà il boss di fine gioco (uhm dopo ben un livello il gioco già finisce). Sarà più complesso rispetto ai sottomarini normali, oltre ad essere più grosso.

!!!Comportamento
L''''Ottobre Rosso''' esce improvvisamente da un lato dello schermo e si dirige a gran velocità verso l'altro lato. Ogni tanto decide di sparare missili verso la superficie, cercando di colpire la '''Nave'''. Se la colpisce, il giocatore perde tot punti (no, non mettiamo le vite alla '''Nave'''). Il giocatore deve colpire tre volte l''''Ottobre Rosso''', prima di affondarlo. Quando viene colpito, l''''Ottobre Rosso''' lampeggia come da tradizione videoludica. Infine, quando è stato affondato, si inabisserà lentamente nell'Abisso Laurenziano.

!!Comunicazione con lo stato di gioco
L''''Ottobre Rosso''' lancerà dei missili, ma spetta allo stato di gioco visualizzarli e gestirne la collisione con la '''Nave'''. Dobbiamo quindi inventare un metodo per permettere all'Ottobre Rosso di inviare messaggi allo stato di gioco

!!!ManagerOttobreRosso
Per ottenere questo, inventiamo una nuova interfaccia che chiamiamo '''ManagerOttobreRosso'''. Lo '''StatoBoss''' implementerà quest'interfaccia.

public interface ManagerOttobreRosso {
public void lanciaMissile(float x, float y);
public void affondamentoCompleto();
}

* '''lanciaMissile''' dice allo '''StatoBoss''' di lanciare un missile in prossimità di quelle coordinate;
* '''affondamentoCompleto''' comunica che ha finito di affondare, così che lo '''StatoGioco''' può passare al prossimo stato.

!!Robe standard
Qui ci sono le variabili standard che fanno parte dell''''Ottobre Rosso'''

private Rectangle box;
private Image ottobreDx;
private Image ottobreSx;
private boolean direction;
private boolean active = false;
private float speed = 200.0f / 1000.0f;

A queste dobbiamo aggiungere un po' di robe

private ManagerOttobreRosso myGestore;

Viene passato al costruttore dell'Ottobre Rosso:

public OttobreRosso(ManagerOttobreRosso lm) throws SlickException {
this.myGestore = lm;

ottobreDx = new Image("./data/ottobrerosso.png");
ottobreSx = ottobreDx.getFlippedCopy(true, false);

box = new Rectangle(0, 0, ottobreDx.getWidth(), ottobreSx.getHeight());
}

In '''StatoBoss''' costruiremo nel metodo '''init''' l''''Ottobre Rosso''':

ottobreRosso = new OttobreRosso(this);

Come dicevo sopra, '''StatoBoss''' deve implementare l'interfaccia '''ManagerOttobreRosso'''. Per ora lasciamo i due metodi vuoti.

Occorrono però anche altre variabili per gestire vari aspetti dell''''Ottobre Rosso'''.

!!!Partenza
L''''Ottobre Rosso''' parte ad intervalli regolari da un bordo dello schermo, e si dirige verso l'altro bordo. Per fare questo ci serve un '''Timer''':

private Timer partenzaTimer;

Lo inizializziamo nel costruttore:
partenzaTimer = new Timer(new Runnable() {

public void run() {
partenza();
}
}, 2 * 1000);

Questo vuol dire che l''''Ottobre Rosso''' rimarrà "fuori" dallo schermo per due secondi, prima di decidere di partire.

Nella '''run()''' viene invocato il metodo '''partenza''', in cui facciamo partire effettivamente il sottomarino:

private void partenza() {

float depth = (float) (300 + Math.random() * (container.getHeight() - 300 -
box.getHeight()));

double caso = Math.random() * 100;

if (caso > 50) {
direction = true;
box.setLocation(0 - box.getWidth(), depth);
} else {
direction = false;
box.setLocation(container.getWidth(), depth);
}

active = true;
}

La variabile '''active''' è un booleano che viene impostato a '''false''' nel costruttore. Sta ad indicare se l''''Ottobre Rosso''' sta facendo qualcosa oppure riposa ai lati dello schermo.

Andiamo quindi in '''update'''. La prima cosa è aggiornare il timer della partenza:

partenzaTimer.update(delta);

e poi controllare il movimento secondo il solito schema:

if (active) {

int multi = direction ? 1 : -1;
float x = box.getX() + this.speed * delta * multi;

box.setLocation(x, box.getY());

}

<<|[[Giochino Sottomarino]]|>>

----
[[!Guide]]