Academic Club Website

Categorie Articoli

Primi passi con XNA - Parte 3

Primi passi con XNA - Parte 3

by Petrucco Marco

 

Spostamento dell’avatar

Conclusa la panoramica relativa alla visualizzazione della scena tridimensionale (e delle principali tecniche utilizzate per ottenerla), osserviamo come è stato implementato lo spostamento dell’avatar nel mondo 3D.

Nella seguente immagine è possibile vedere i principali punti di tale processo, tutti presenti all’interno della procedura Update():

Immagine 23 – Spostamento avatar

Immagine 23 – Spostamento avatar

 

Poiché dobbiamo far interagire l’utente con il nostro ambiente tridimensionale, per prima cosa è indispensabile leggere l’input che l’operatore ci fornisce.

Grazie a XNA possiamo indifferentemente utilizzare la tastiera, il mouse o il joypad della console Xbox 360. Al momento è supportato solamente l’input tramite tastiera, perché era il metodo più veloce da implementare tra le tre possibilità; in questo modo l’interazione con l’ambiente è simile a quella offerta da titoli pioneristici nel settore dei videogiochi con visuale in prima persona, come il famoso “Doom”.

Ovviamente in una ipotetica versione definitiva rivolta al grande pubblico, si aggiungerebbe anche il supporto al Freelook tramite mouse e, volendo, anche il supporto alle levette analogiche del joypad. Dato che la periferica di input non è di cruciale importanza in questa fase del progetto, per ora ci limitiamo ad analizzare come viene gestito l’input da tastiera.

Quest’ultimo viene processato nella procedura ProcessKeyboard : essenzialmente ad ogni frame si leggono i tasti premuti (grazie a “KeyboardState keys = Keyboard.GetState()”) e si modificano alcune variabili relative ai tre assi (x, y,z).

Per il movimento sull’asse orizzontale si usano i tasti W, A, S, D (classici comandi degli attuali First Person Shooter), mentre per il movimento dello sguardo utilizziamo i tasti freccia, gestendo l’UP, il DOWN ed il ROLL della camera.

Spostamento orizzontale:

            if (keys.IsKeyDown(Keys.W))

            {

                // avanti

                position += addvector * speed;

            }

            if (keys.IsKeyDown(Keys.S))

            {

                // indietro

                position -= addvector * speed;

            }   

if (keys.IsKeyDown(Keys.A))

            {

    // gira a sinistra

                zRot = 1.5f * speed;

            }

            if (keys.IsKeyDown(Keys.D))

            {

    // gira a destra

                zRot = -1.5f * speed;

            }

Rotazione sugli assi Y (up/down) e X (roll):

                if (keys.IsKeyDown(Keys.Right))

                {

                    yRot = 1.5f * speed;

                }

                if (keys.IsKeyDown(Keys.Left))

                {

                    yRot = -1.5f * speed;

                }

                if (keys.IsKeyDown(Keys.Down))

                {

                    xRot = 1.5f * speed;

                }

                if (keys.IsKeyDown(Keys.Up))

                {

                    xRot = -1.5f * speed;

                }

A questo punto si crea un quaternione contenente le informazioni relative ai tre assi:

Quaternion additionalRot = Quaternion.CreateFromAxisAngle(new Vector3(0, 1, 0), yRot) * Quaternion.CreateFromAxisAngle(new Vector3(1, 0, 0), xRot) * Quaternion.CreateFromAxisAngle(new Vector3(0, 0, 1), zRot);

 

Aggiornando infine anche la variabile globale che tiene traccia delle rotazioni dell’avatar:

MyAvatarRotation *= additionalRot;

Limitandoci solo a questo, il nostro avatar non farebbe altro che ruotare su se stesso: dobbiamo perciò anche aggiornare la variabile MyAvatarPosition oltre che la MyAvatarRotation. Il tutto viene fatto grazie a UpdatePosition(), in cui prima si calcola la direzione del movimento (prendendo il vettore (0,1,0) e trasformandolo secondo la rotazione dell’avatar), poi la si normalizza (in modo che la lunghezza diventi pari a 1), poi si moltiplica il tutto per la velocità ed infine si aggiorna la posizione.

Vector3 addvector = new Vector3(0, 1, 0);

addvector = Vector3.Transform(addvector, Matrix.CreateFromQuaternion(rotationquat));

            addvector.Normalize();

            position += addvector * speed;

 

Questo ci permette di ottenere il pieno controllo sul movimento dell’avatar e quindi di navigare liberamente all’interno dell’ambiente tridimensionale, come riportato in figura 24.

Navigazione all’interno della libreria

Immagine 24 – Navigazione all’interno della libreria

Rilevamento collisioni

Prima di spiegare come è stato risolto il problema del rilevamento delle collisioni, è opportuno spiegare al lettore gli strumenti che XNA offre allo sviluppatore per la gestione di simili problematiche.

Come precedentemente annunciato, il framework dispone di due classi per il rilevamento delle collisioni; queste sono: BoundingBox e  BoundingSphere. Entrambe non presentano elementi visibili e dispongono di un metodo Intersects() che controlla se due oggetti collidono tra di loro.

La struttura base di un controllo sulle collisioni, è riassumibile nella seguente scrittura:

if (Oggetto1BoundingBox.Intersects(Oggetto2BoundingBox))

{

// COLLISIONE

}

 

Vi sono ancora delle limitazioni nell’uso di entrambe le classi:

-         è compito del programmatore aggiornare la posizione di questi oggetti quando i modelli corrispondenti si muovono all’interno della scena;

-         se scaliamo il modello, anche la corrispondente box deve essere scalata manualmente;

-         soltanto le BoundingSphere sono generate in automatico quando importiamo un modello dall’esterno... se vogliamo utilizzare le BoundingBox, dobbiamo specificarle noi;

-         si deve gestire anche il caso di rotazione dei modelli, modificando opportunamente le caratteristiche della box.

 

Axis Aligned Bounding Box

Ricordiamo che XNA utilizza delle Axis Aligned Bounding Box, ossia dei “contenitori” allineati agli assi.

Forma e posizione sono semplicemente definite da un valore minimo ed un massimo per ogni dimensione: avremo quindi sia i minimi per X, Y e Z che i loro valori massimi. Il minimo ed il massimo rappresentano i due angoli “diagonalmente opposti” della box.

Si può comprendere meglio tale caratteristica grazie al seguente esempio:


Immagine 25 – Bounding Box

Per comprendere come creare una bounding box, osserviamo questo esempio, nel quale se ne costruiscono due per due oggetti A e B:

Vector3 MinV1 = new Vector3(OggettoA_X, OggettoA_Y,0);

Vector3 MinV2 = new Vector3(OggettoB_X, OggettoB_Y,0);

Vector3 MaxV1 = new Vector3(OggettoA_X+32, OggettoA_Y+48,0);

Vector3 MaxV2 = new Vector3(OggettoB_X+8, OggettoB_Y+16,0);

Ora che conosciamo i vertici, possiamo definire le BoundingBox:

bb1.Min = MinV1; bb2.Min = MinV2;

bb1.Max = MaxV1; bb2.Max = MaxV2;

ed eseguire il controllo di collisione tra le due:

if(bb1.Intercept(bb2)

{

// Collisione

}

Poichè dobbiamo specificare direttamente la posizione dei vertici della BoundingBox e poichè non è al momento possibile leggere tali informazioni dalle mesh dei modelli importati dall’esterno, risulta chiaro che tale tecnica è praticabile solo sugli oggetti creati da noi, per i quali siamo già a conoscenza di tali informazioni.

Un ulteriore problema relativo alle Axis Aligned Bounding Box si ha nel momento in cui si ruota il modello a cui fanno riferimento: ovviamente possiamo trasformare i corrispondenti vettori (e quindi i vertici) delle box seguendo le rotazioni, ma così facendo non ruoteremo anche le box, che invece si adatteranno ai nuovi vertici.

Questo problema è facilmente comprensibile osservando le seguenti immagini:

               

        

Immagine 26 – BoundingBox “ruotata”                            Immagine 27 - BoundingBox interpretata da XNA

Nelle due immagini i punti di minimo e di massimo sono gli stessi, ma il risultato desiderato (figura 31) e quello ottenuto (figura 32) sono differenti.

La spiegazione di questo comportamento è che queste Bounding Box sono allineate agli assi (Axis Aligned).

 

BoundingSphere

Per ovviare al problema della rotazione della Bounding Box, possiamo utilizzare le BoundingSphere, dato che per queste la rotazione è ininfluente.

Inoltre, usando tale oggetto, evitiamo anche il lavoro di dover creare manualmente i confini (Bounding) per le singole mesh, poichè tale operazione la esegue XNA in automatico quando carica il modello stesso.

Ovviamente, per il nostro avatar, abbiamo optato per questa tecnica, anche in considerazione del fatto che il modello stesso è una sfera!

Importando in XNA il modello usato per gli esempi precedenti (figure 30-32), si otterebbe la suddivisione presentata in figura 33 (assumendo che il modello sia costituito dalle sole tre mesh di fusoliera, ali e coda).

 


Immagine 28 – BoundingSphere generate in automatico da XNA

 

Nel caso si desideri una suddivisione più accurata del modello, non resta che eseguirne una settando le caratteristiche delle sfere a mano, partendo come nel caso delle BoundingBox, da dati conosciuti.

Non presenteremo il processo (seppure semplice) in tale sede, ma ci limiteremo a fornire due immagini del risultato finale (ovviamente il numer di BoundingSphere può variare a seconda delle scelte del programmatore).

         

           

Immagine 29 – BoundingSphere multiple (sopra)           Immagine 30 - BoundingSphere multiple (lato) 

  

Rilevamento Collisioni: Implementazione

Nel nostro caso ci siamo limitati ad utilizzare una BoundingSphere per contenere l’avatar dell’utente ed alcuni controlli matematici.

La gestione delle collisioni è affidata alla funzione CheckCollision(), che accetta in ingresso la posizione dell’avatar, definita dal centro della sfera, ed il raggio della stessa. Fornirà come risultato uno “0” (nessuna collisione) oppure un “1” (collisione avvenuta), grazie a due semplici controlli:

// Controllo se sono più basso del pavimento

if (position.Z - radius < 0) return 1;

 

// Controllo la mia posizione rispetto agli scaffali della libreria

if ((position.X - radius > 0) && (position.X + radius < WIDTH) && (position.Y - radius > 0) && (position.Y + radius < HEIGHT))

            {

// Se sono “dentro” uno scaffale, la collisione è avvenuta

                if (position.Z - radius < AltezzaScaffali[int_Floorplan[(int)position.X, (int)position.Y]]) return 1;

            }

I commenti dovrebbero fornire una spiegazione già esaustiva di come è strutturato l’algoritmo, vediamone comunque i passi principali:

-      per prima cosa verifico di non  trovarmi sotto il pavimento

-      in seguito controllo di trovarmi entro i confini della biblioteca

-      in caso affermativo, verifico di non trovarmi nell’area occupata da uno scaffale

Questo controllo, richiamato all’interno di Update(), viene eseguito 60 volte al secondo (una per ogni frame disegnato) ed in caso di avvenuta collisione, blocca l’avanzata dell’avatar, sostituendo la sua posizione con quella che aveva nel frame precedente.

if (CheckCollision(MyAvatarPosition, MyAvatarModel.Meshes[0].BoundingSphere.Radius * 0.0005f) > 0)

                {

                  // Nel caso in cui si rilevi una collisione,

// fermo l'avanzata dell'avatar ripristinando

// l'ultima sua posizione corretta.

                  MyAvatarPosition = MyOldAvatarPosition;

                }

In questo modo l’utente ha l’impressione di essere bloccato (ed in effetti lo è) e può cambiare strada.

 

Conclusioni

Si può affermare che al momento attuale la suite XNA permetta all’utente alle prime armi di confrontarsi in modo relativamente semplice con le principali problematiche di un ambiente tridimensionale e, più in generale, con quelle relative allo sviluppo di un videogioco,  facendo concentrare la persona sul prodotto che desidera sviluppare e non su questioni troppo tecniche che vengono invece gestite dal framework.

Si consiglia a tutti coloro che sono interessati a questa piattaforma di visitare il sito “Creators Club” (http://creators.xna.com) ed in particolare le seguenti sezioni:

GettingStarted

Tutorials

Samples

StarterKits

 

Disclaimer

I contenuti presentati in tale documento sono offerti dall’autore  in forma gratuita, al meglio delle sue capacità.

Nel documento sono state inserite delle immagini gratuitamente reperibili su internet.

L’autore ed Academic Club declinano ogni responsabilità, diretta e indiretta, nei confronti degli utenti e in generale di qualsiasi terzo, per eventuali imprecisioni, errori, omissioni, danni (diretti,indiretti, conseguenti, punibili e sanzionabili) derivanti dai suddetti contenuti.

 

Clicca qui per scaricare il codice del progetto

 

Comments

No Comments
Syndication
Archive