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
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.
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:




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