24) Collisioni con le BoundingSphere

Rilevare le collisioni è stato sempre una delle questioni più ostiche per chi progetta un videogioco in 3D. Di sistemi ne esistono diversi ed ognuno ha le sue difficoltà con i sui pro e i suoi contro.
Quello che vedremo ora si avvale dell'uso delle BoundingSphere, ovvero, delle sfere che contengono i nostri modelli e rilevano la collisione tra di loro.
Questo metodo non è il più accurato ma sicuramente il più semplice da implementare.
Le BoundingSphere sono delle classi di XNA create appositamente per rilevare le collisioni tra i modelli.
Esistono anche i BoundigBox che sono l'equivalente delle BoundingSphere ma di forma parallelepipeda che vedremo nella lezione 26.
Tali classi possiedono un metodo chiamato Intersects() che restituisce un valore boleano (vero o falso) nel caso di collisione. La sua sintassi è la seguente:

BS_1.Intersects(BS_2)

Dove BS_1 è una BoundingSphere e BS_2 è la seconda BoundingSphere.
Questa riga controllerà la collisione tra le due BoundingSphere e restituirà true se esse si toccheranno. Ponendo il centro delle BoundingSphere uguale a quello dei modelli di cui vorremo controllare la collisione, avremo controllato quando i modelli si toccano.
Come detto e come è intuibile, rilevare le collisioni in questo modo non sarà preciso al millimetro perché di norma i modelli non hanno una forma sferica.

bs1   bs2

Come si nota, la collisione viene rilevata anche se in realtà gli oggetti ancora non si toccano.
Per ovviare a questo problema potremmo usare più BoundingSphere per ogni modello così da rendere più precisa la rilevazione.

bs3

In questo modo le collisioni sarebbero rilevate in manienra più precisa. Come si può notare dalle immagini potremo impostare anche il raggio delle sfere.
Esistono anche metodi migliori per rilevare le collisioni anche triangolo per triangolo.
Noi, per semplicità, useremo un'unica BoundingSphere.

La sintassi per impostare una BoundingSphere è la seguente:

BoundingSphere_1 = new BoundingSphere(posizione_sfera, raggio_sfera);

Dunque, il primo parametro, che io ho chiamato posizione_sfera sarà un Vector3 che identifica la posizione della BoundingSphere mentre il secondo parametro è il raggio della sfera.
Per fare in modo che il personaggio si fermi al contatto con un altro potremmo usare tanti stratagemmi.
Noi faremo in modo che, se esiste una collisione, la posizione del modello dovrà tornare uguale a quella che aveva nel fotogramma precendete.
Per fare questo dovremo salvare la posizione che aveva il nostro Pg nel fotogramma precendete a quello che che stiamo vivendo.

Questo sistema ci tornerà molto utile anche in seguito quando, per evitare che la pressione di un tasto venga rilevata più di una volta, dovremo controllare che al fotogramma precedente non fosse premuto. Infatti, appena faremo le prime prove per "sparare" qualche cosa (tipo un missile, un proiettile ecc..) incontreremo il problema che, anche nel caso che noi premessimo un pulsante per un istante, vedremo partire decine di colpi. Questo perché, per quanto saremo veloci a premere e rilasciare un tasto, il computer sarà sempre più veloce di noi e verificherà che il controllo "tasto premuto" sia vero, almeno una decina di volte nell'istante che noi teniamo premuto il pulsante ed eseguirà decine di volte l'istruzione dentro l'if.
Quando si presenterà questo problema vedremo che il modo per superarlo sarà il medesimo che useremo ora per rilevare cosa è successo nel fotogramma precedente.



Per sapere il valore di una variabile nel fotogramma precedente a quello che stiamo viventdo, dovremo usare queste due semplice righe.

vecchia_posizione_pg = tmp_posizione_pg;
tmp_posizione_pg = posizione_pg;

Abbiamo preso per esempio la posizione del PG.
vecchia_posizione_PG è la variabile che vogliamo trovare, cioè quella nel fotogramma precedente.
tmp_posizione_PG è una variabile temporanea che servirà di passaggio.
posizione_PG è la variabile odierna, la posizione del modello in questo momento.
In questo modo, la variabile vecchia_posizione_PG sarà uguale a posizione_PG solo nel fotogramma successivo a quello in cui è in quel momento, dunque ritarderà ad aggiornarsi di un fotogramma.
Con queste due semplici righe avremo impostato la variabile vecchia_posizione_PG con la posizione del PG che aveva nel fotogramma precendente.

Mie_variabili.vecchia_posizione_pg = tmp_posizione_pg;
tmp_posizione_pg = Mie_variabili.posizione_pg;

Nel nostro caso aggiungeremo la variabile vecchia_posizione_pg nella struttura.
Insieme ad essa aggiungeremo anche una variabile di tipo BoundingSphere che chiameremo BS_pg così che la nostra struttura diventi così:

    Mie_variabili.cs apri

Avremo cinque variabili nella struttura tra cui una di tipo BoundingSphere, che abbiamo chiamato BS_pg, che identificherà la sfera del personaggio giocante.

Andiamo adesso ad inizializzare la nostra sfera che contiene il personaggio che muoviamo.
Per far si che la sfera segua sempre il modello, dovremo inizializzare la BoundingSphere all'interno di un metodo che viene eseguito costantemente, ad ogni fotogramma. Potremmo usare il metodo Draw() che abbiamo già nel file pg.cs oppure riscrivere il metodo Update().
Aggiungeremo questa riga:

Mie_variabili.BS_pg = new BoundingSphere(Mie_variabili.posizione_pg, 20f);

Cioè inizializzeremo la variabile BS_pg di tipo BoundingSphere impostando il centro della sfera sul centro del modello (Mie_variabili.posizione_pg) ed impostando il raggio della sfera a 20f.
Comprese le due rughe di cui parlavamo prima, per controllare la posizione del pg al frame precedente, il nostro file pg.cs diventrà così:

    pg.cs apri

Andiamo adesso a modificare anche la classe Stampa_modelli creando una BoundingSphere che contenga il modello del lupo o qualunque altro modello che stamperemo con la classe Stampa_modelli.

    Stampa_modelli.cs apri

Ciò che ho evidenziato sono le righe che dichiarano e inzializzano la nostra BoundingSphere e controllano la collisione.
Vediamo meglio cosa succede se la condizione  " Mie_variabili.BS_pg.Intersects(BS_modello)"  ritorna true, cioè se c'è una collisione tra la sfera " Mie_variabili.BS_pg" e la sfera "BS_modello".

if (Mie_variabili.BS_pg.Intersects(BS_modello))
{
Mie_variabili.posizione_pg = Mie_variabili.vecchia_posizione_pg;
}

In pratica imposteremo la posizione del modello uguale a quella che aveva un istante prima, nel fotogramma precendente.
Funziona? :) Provate pure a stampare altri lupi in giro per lo schermo aggiungendo nuovi oggetti della classe Stampa_Modelli nel file Game1.cs e vedrete che le collisioni saranno rilevate con tutti i lupi in giro per la scena.

NOTA. Avrete notato che i modelli orco.x e lupo.x che avete scaricato, sono privi di texture. Non dovete pensare che il motivo di questa scelta è da imputare a qualche difficoltà aggiuntiva per stampare anche le texture sul modello. Non sarebbe cambiato nulla. Solo, vi avrei dovuto far scaricare anche le immagini e farvi creare un'ulteriore cartella che le contenga. XNA legge e stampa senza problemi tutti i modelli .x con le texture già applicate.