Il codice sin qui illustrato si limita a disegnare una scena ripresa da una telecamera fissa. Adesso proveremo a modificare la posizione e la rotazione della telecamera sulla base dell'input dell'utente. La prima cosa da fare è quello di andare a leggere nel metodo Update della nostra classe Game
eventuali input da tastiera:
var kb = Keyboard.GetState();
Impostiamo adesso la velocità di rotazione della telecamera:
var ROTATE_SPEED = 1.5f;
Nel determinare quest'ultima, dobbiamo però tenere presente che maggiore è il frame rate della nostra applicazione e maggiore sarà la velocità con cui la telecamera ruoterà intorno ad uno dei suoi assi. Per mantenere questo valore costante, in modo che non sia influenzato dal numero di fps che la nostra macchina è in grado di elaborare, è sufficiente moltiplicare la velocità di rotazione per il tempo intercorso tra due successivi cicli di Update
(espresso dalla proprietà GameTime.ElapsedGameTime
). Così facendo, più la nostra applicazione risulterà "lenta", maggiore sarà l'angolo di rotazione compiuto dalla telecamera ad ogni iterazione:
var dt = (float)gameTime.ElapsedGameTime.TotalSeconds;
Per calcolare l'angolo di rotazione useremo il metodo CreateFromYawPitchRoll, il quale crea un nuovo quaternione per memorizzare la rotazione intorno ai tre assi (nel nostro caso, useremo solo la rotazione intorno all'asse delle X):
public static Quaternion CreateFromYawPitchRoll (
float yaw,
float pitch,
float roll
)
Ruotiamo adesso la nostra telecamera in accordo con l'input dell'utente:
if (kb.IsKeyDown(Keys.Left))
rotation *= Quaternion.CreateFromYawPitchRoll(dt * ROTATE_SPEED, 0.0f, 0.0f);
if (kb.IsKeyDown(Keys.Right))
rotation *= Quaternion.CreateFromYawPitchRoll(-dt * ROTATE_SPEED, 0.0f, 0.0f);
Come si può vedere, la telecamera effettuerà una rotazione intorno al proprio asse verticale (yaw) in un senso o nell'altro a seconda dell'input dell'utente.
Definiamo ora allo spostamento della telecamera lungo i tre assi. Per prima cosa, impostiamo la velocità di spostamento:
var MOVE_SPEED = 100.0f;
Ciò detto, anche il calcolo dello spostamento della telecamera necessita di un pò di ragionamento, se si vogliono che i nostri movimenti risultino fluidi e, soprattutto, "naturali". Infatti, quando la telecamera si muove in avanti dobbiamo tenere in considerazione la rotazione corrente, dato che la definizione di "avanti" dipende da come l'osservatore è orientato. Immaginate di muovere tre passi in una qualunque direzione e poi di ruotare su voi stessi di 90 gradi in senso orario: adesso, il vostro "avanti" non punta più nella stessa direzione di prima (se lo facesse, vi muovereste lateralmente come un granchio), ma è variato in accordo con la rotazione.
Il che significa, ad esempio, che non potremmo semplicemente dire che
if (kb.IsKeyDown(Keys.W))
position += Vector3.Forward * dt * MOVE_SPEED;
if (kb.IsKeyDown(Keys.S))
position += Vector3.Backward * dt * MOVE_SPEED;
if (kb.IsKeyDown(Keys.D))
position += Vector3.Right * dt * MOVE_SPEED;
if (kb.IsKeyDown(Keys.A))
position += Vector3.Left * dt * MOVE_SPEED;
perché in questo modo l'utente avrebbe la sensazione di muoversi in direzioni indipendenti rispetto all'orientamento della telecamera, come se questa si muovesse su binari fissi. Per ovviare a questo problema calcoliamo per prima cosa la matrice di rotazione a partire dal nostro quaternione:
var camera_rotation = Matrix.CreateFromQuaternion(rotation);
ADESSO, CON CAMERA_ROTATION.FORWARD, .BACKWARD, .LEFT, .RIGHT, .UP E .DOWN POSSIAMO INDIVIDUARE LE DIREZIONI AVANTI, INDIETRO, ETC. RELATIVE ALLA ROTAZIONE. UNA MATRICE IDENTITÁ AVRÁ: + i.Forward {X:0 Y:0 Z:-1} Microsoft.Xna.Framework.Vector3 + i.Backward {X:0 Y:0 Z:1} Microsoft.Xna.Framework.Vector3 + i.Left {X:-1 Y:0 Z:0} Microsoft.Xna.Framework.Vector3 + i.Right {X:1 Y:0 Z:0} Microsoft.Xna.Framework.Vector3 + i.Up {X:0 Y:1 Z:0} Microsoft.Xna.Framework.Vector3 + i.Down {X:0 Y:-1 Z:0} Microsoft.Xna.Framework.Vector3 MENTRE UNA MATRICE DI ROTAZIONE R INTORNO ALL'ASSE Y DI p2 (90 GRADI A SINISTRA) AVRÁ: + r.Forward {X:-1 Y:0 Z:4,371139E-08} Microsoft.Xna.Framework.Vector3 + r.Backward {X:1 Y:0 Z:-4,371139E-08} Microsoft.Xna.Framework.Vector3 + r.Left {X:4,371139E-08 Y:0 Z:1} Microsoft.Xna.Framework.Vector3 + r.Right {X:-4,371139E-08 Y:0 Z:-1} Microsoft.Xna.Framework.Vector3 + r.Up {X:0 Y:1 Z:0} Microsoft.Xna.Framework.Vector3 + r.Down {X:0 Y:-1 Z:0} Microsoft.Xna.Framework.Vector3 POSSIAMO NOTARE CHE ALCUNE COORDINATE CHE DOVREBBERO ESSERE PARI A 0 IN REALTÁ SONO PICCOLISSIME MA NON NULLE (4.371139×10-8). GLI ERRORI DI APPROSSIMAZIONE SONO SEMPRE IN AGGUATO]
Adesso possiamo finalmente sommare alla posizione la direzione appropriata in risposta ai tasti premuti dall'utente (sempre normalizzando la velocità in base al tempo trascorso dall'ultima chiamata al metodo Update):
if (kb.IsKeyDown(Keys.W))
position += camera_rotation.Forward * dt * MOVE_SPEED;
if (kb.IsKeyDown(Keys.S))
position += camera_rotation.Backward * dt * MOVE_SPEED;
if (kb.IsKeyDown(Keys.D))
position += camera_rotation.Right * dt * MOVE_SPEED;
if (kb.IsKeyDown(Keys.A))
position += camera_rotation.Left * dt * MOVE_SPEED;