VS85 Team's profileXNALearnersBlogListsGuestbook Tools Help

Blog


    3/13/2008

    Gestione dell’input in XNA

    Come far interagire l’utente con il nostro gioco!

    by Giuseppe Maggiore

    Introduzione

    In 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 XNA

    XNA 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’input

    Leggere 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);
    KeyboardState kbs = Keyboard.GetState();
    MouseState mss = Mouse.GetState();

    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 tempo

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

    clip_image002

    allora durante la pressione del tasto ‘A’, verranno sparati

     clip_image004

    missili, uno per fotogramma. Il risultato che otteniamo e’ questo, anche per pressioni apparentemente istantanee:

    clip_image006

    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:

    clip_image008

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

    clip_image010clip_image012

    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:

    clip_image014

    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 Camera

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

    Conclusione

    Il 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)

    Please wait...
    Sorry, the comment you entered is too long. Please shorten it.
    You didn't enter anything. Please try again.
    Sorry, we can't add your comment right now. Please try again later.
    To add a comment, you need permission from your parent. Ask for permission
    Your parent has turned off comments.
    Sorry, we can't delete your comment right now. Please try again later.
    You've exceeded the maximum number of comments that can be left in one day. Please try again in 24 hours.
    Your account has had the ability to leave comments disabled because our systems indicate that you may be spamming other users. If you believe that your account has been disabled in error please contact Windows Live support.
    Complete the security check below to finish leaving your comment.
    The characters you type in the security check must match the characters in the picture or audio.

    To add a comment, sign in with your Windows Live ID (if you use Hotmail, Messenger, or Xbox LIVE, you have a Windows Live ID). Sign in


    Don't have a Windows Live ID? Sign up

    VS85 Teamwrote:
    Grazie! Per quanto riguarda la lentezza nel download, ovviamente non dipende da noi ma dai server che utilizziamo... vedremo di indagare, perche' non sei il primo ad avere questi problemi... Grazie della segnalazione intanto!
     
    Giulia Costantini
    Mar. 17
    Alexwrote:
    Ottimo tutorial! ^^

    Ora mi guardo anche il webcast... come mai il download procede a 4kb/s ??? Mi ci vorrà una vita per scaricare il codice e il video XD

    Va beh... intanto me lo guardo in stream  ^o^
    Mar. 14

    Trackbacks

    The trackback URL for this entry is:
    http://vs85team.spaces.live.com/blog/cns!B49FFA0EB319A219!303.trak
    Weblogs that reference this entry
    • None