| VS85 Team's profileXNALearnersBlogListsGuestbook | Help |
|
3/13/2008 Gestione dell’input in XNACome far interagire l’utente con il nostro gioco! by Giuseppe Maggiore IntroduzioneIn questo documento spiegheremo brevemente come si gestisce l’input in XNA, in modo da rendere finalmente interattive le nostre opere J. La gestione dell’input pero’ non e’ cosi’ semplice come ci potremmo aspettare: l’input ha luogo (diciamo che si manifestano le interazioni dell’utente) con una frequenza molto differente e molto piu’ bassa rispetto a quella con cui l’applicazione si aggiorna. In un semplice gioco casual le interazioni tra utente e macchina sono dell’ordine di qualcuna al secondo (pochi Hz), mentre il gioco si aggiornera’ a svariate decine di frame al secondo (decine di Hz). Quindi servira’ filtrare la velocita’ di aggiornamento dell’applicazione in relazione all’input. Periferiche supportate da XNAXNA offre pieno e nativo supporto a tre periferiche: - Mouse - Tastiera - Controller XBox 360 Tali periferiche sono state scelte non a caso, ma perche’ sono quelle tipicamente trovate connesse ad una delle piattaforme su cui XNA gira, ossia Windows o l’Xbox 360. Altre periferiche possono essere impiegate, ma bisognera’ usare una libreria diversa da XNA per gestirle e ovviamente perderemo il supporto per l’XBox 360... Lettura dell’inputLeggere l’input consiste nel farsi fornire una copia dello stato istantaneo di un dato controller in un dato momento: GamePadState gps = GamePad.GetState(PlayerIndex.One); Ovviamente tale stato non sara’ valido per sempre, ne’ tantomeno si aggiornera’ da solo: per questo motivo dovremo considerare queste variabili come delle “fotografie istantanee” dell’input di un qualche controller in un dato istante di tempo. Se volessimo fare uscire il nostro gioco alla pressione di un qualsiasi tasto (magari il tasto back del controller XBox 360) allora dovremmo aggiungere questo controllo alla nostra funzione Update: protected override void Update(GameTime gameTime) { gps = GamePad.GetState(PlayerIndex.One); if (gps.Buttons.Back == ButtonState.Pressed) this.Exit(); } In cui si puo’ notare come aggiorniamo come prima cosa ad ogni Update lo stato della periferica di cui usiamo l’input. Problematiche rispetto al tempoIpotizziamo di avere costruito una semplice applicazione che mostra al centro dello schermo un’astronave, e vogliamo che ogni volta che l’utente preme il tasto ‘A’ del controller XBox l’astronave spari un missile. L’approccio naive a questo problema sarebbe una cosa del tipo: if (gps.Buttons.A == ButtonState.Pressed) shoot(); purtroppo per noi le cose non sono cosi’ semplici. Immaginiamo che l’utente tenga premuto il pulsante per un tempo piuttosto breve, diciamo una frazione di due decimi di secondo e che l’applicazione vada a 60 fps:
allora durante la pressione del tasto ‘A’, verranno sparati missili, uno per fotogramma. Il risultato che otteniamo e’ questo, anche per pressioni apparentemente istantanee: Dobbiamo tenere traccia di pressioni prolungate, ma non vorremmo farlo esplicitamente (magari con un array di booleani che ci dicono se un tasto era gia’ premuto precedentemente o no). Beh, il modo piu’ immediato per ottenere questo scopo e’ quello di memorizzare non solo uno stato dell’input (GamePadState) ma anche quello del fotogramma precedente: protected override void Update(GameTime gameTime) { prevGamePadState = gamePadState; gamePadState = GamePad.GetState(PlayerIndex.One); /*…*/ } Grazie a questa seconda struttura di supporto, possiamo trasformare il codice con cui decidiamo se sparare o meno un missile in: if (gamePadState.Buttons.A == ButtonState.Pressed && prevGamePadState.Buttons.A == ButtonState.Released) { Missile m = new Missile(); m.p = Vector3.Zero; m.v = Vector3.Forward; missiles.Add(m); } Con cui una singola (anche lunga) pressione del tasto ‘A’ produce l’effetto desiderato, ossia un solo missile viene lanciato: Con una variazione su questo stesso tema possiamo ottenere anche altri risultati interessanti oltre al timing dei singoli tasti. Immaginiamo di voler far sparare la nostra astronave in tutte le direzioni. In particolare, vogliamo che l’astronave spari verso dove e’ rivolto lo stick analogico destro del controller XBox (evidenziato nella figura): Il primo tentativo e’ analogo a quello che abbiamo fatto prima con il tasto ‘A’: se lo stick e’ premuto piu’ di un certo valore, spariamo il missile: if (gamePadState.ThumbSticks.Left.Length() > 0.25f) { Missile m = new Missile(); m.p = Vector3.Zero; m.v = new Vector3(gamePadState.ThumbSticks.Left.X, 0.0f, gamePadState.ThumbSticks.Left.Y); missiles.Add(m); } Chiaramente sappiamo gia’ come va a finire, ovvero un sacco di missili verranno sparati per pressioni apparentemente anche molto brevi dello stick: Ora potremmo anche usare lo stato del gamepad al frame precedente, ma la soluzione migliore non e’ necessariamente questa. Ricordiamoci che stiamo parlando di uno stick analogico, e vorremmo che il giocatore rilasci la levetta prima di sparare un secondo missile. Quindi dobbiamo tenere traccia piu’ esplicitamente del fatto che in un dato frame stiamo aspettando che l’utente rilasci lo stick analogico prima di poter sparare di nuovo: if (waitThumbstick == true) { if (gamePadState.ThumbSticks.Left.Length() < 0.15f) waitThumbstick = false; } else { if (gamePadState.ThumbSticks.Left.Length() > 0.25f) { Missile m = new Missile(); m.p = Vector3.Zero; m.v = new Vector3(gamePadState.ThumbSticks.Left.X, 0.0f, gamePadState.ThumbSticks.Left.Y); missiles.Add(m); } } Grazie alla variabile waitThumbstick il nostro gestore dell’input aspettera’ che l’utente abbia rilasciato il thumbstick ad almeno lo 0.15 della sua pressione totale, prima di poter sparare di nuovo. Il comportamento di questa soluzione e’ quello desiderato! Una semplice classe CameraImmaginiamo di voler gestire tramite l’input del giocatore lo sguardo dell’utente. In particolare desideriamo che la matrice View venga costruita e aggiornata sulla base di due angoli di rotazione, orizzontale e verticale, che poi vengono aggiornati tramite il mouse o un thumbstick del controller XBox. Spostare il mouse (o il thumbstick) a destra o sinistra fara’ girare la telecamera a destra o sinistra, e similarmente gestiremo la rotazione nella direzione verticale. Come implementare questa utilissima funzione? Beh, intanto dichiariamo gli angoli di rotazione verticale e orizzontale come un vettore a due dimensioni: Vector2 cameraRotationVector = Vector2.Zero; e poi nella funzione Update lo aggiorneremo con la rotazione richiesta dall’utente (moltiplicata per la durata del frame precedente, cosi’ che lo sguardo si sposti alla velocita’ dell’applicazione, e non in modo veloce proporzionalmente al numero di frame per secondo): cameraRotationVector += gamePadState.ThumbSticks.Right *
(float)gameTime.ElapsedGameTime.TotalSeconds;
A questo punto dobbiamo costruire la matrice View, e naturalmente per questo ci sono decine di modi per farlo, differenti a seconda del tipo di telecamera che si vuole implementare e di una enorme quantita’ di altri fattori. Un esempio e’ quello di creare la camera inizialmente sempre nello stesso modo, tramite una chiamata a Matrix.CreateLookAt, e quindi applicare la rotazione desiderata: Matrix View { get { Matrix cameraRotation = Matrix.CreateRotationY(cameraRotationVector.X) * Matrix.CreateRotationX(cameraRotationVector.Y); return Matrix.CreateLookAt(Vector3.Up * 5000.0f, Vector3.Zero, Vector3.Forward) * cameraRotation; } } Se usiamo questa proprieta’ al posto della matrice View in tutte le chiamate di rendering, avremo effettivamente implementato una telecamera capace di ruotare su se stessa. Per spostare avanti e indietro la posizione dell’osservatore, potremo usare cameraRotation.Forward, cameraRotation.Backward, etc. ConclusioneIl sorgente completo di questo sample si puo’ trovare online, assieme al webcast in cui viene mostrata e spiegata la stesura di tale codice. E’ anche possibile guardare il webcast direttamente qui sotto: Comments (2)
TrackbacksThe trackback URL for this entry is: http://vs85team.spaces.live.com/blog/cns!B49FFA0EB319A219!303.trak Weblogs that reference this entry
|
|
|