Share via


Controlli move-look per giochi

Informazioni su come aggiungere controlli move-look tradizionali di mouse e tastiera (noti anche come controlli mouse-look) a un gioco DirectX.

Illustriamo il supporto move-look per dispositivi touch, con il controller "move" definito come sezione inferiore sinistra dello schermo che si comporta come input direzionale e il controller "look" definito per la restante parte dello schermo, con la telecamera centrata sull'ultimo punto toccato dal giocatore in quell'area.

Se si tratta di un concetto di controllo poco familiare, provare a pensarlo in questo modo: la tastiera (o la casella di input direzionale basata sul tocco) controlla le gambe in questo spazio 3D, e si comporta come se le gambe fossero in grado di muoversi avanti o indietro, o di spostarsi a sinistra e a destra. Il mouse (o il puntatore touch) controlla la testa. Si utilizza la testa per guardare in una direzione - sinistra o destra, su o giù, o da qualche parte in quel piano. Se è presente un target nella visuale, si utilizzerà il mouse per centrare la visuale della telecamera su tale target, quindi si premerà il tasto avanti per spostarsi verso di esso oppure il tasto indietro per allontanarsi da esso. Per cerchiare il target, si dovrebbe mantiene la visuale della telecamera centrata sul target e muoversi allo stesso tempo a sinistra o a destra. È possibile notare quanto questo sia un metodo di controllo molto efficace per navigare negli ambienti 3D!

Questi controlli sono comunemente noti come controlli WASD nel gaming, in cui i tasti W, A, S e D sono utilizzati per il movimento della telecamera sul piano x-z, mentre il mouse è utilizzato per controllare la rotazione della telecamera intorno agli assi x e y.

Obiettivi

  • Aggiungere controlli move-look di base a un gioco DirectX per mouse e tastiera e per gli schermi touch.
  • Implementare una telecamera in prima persona utilizzata per navigare in un ambiente 3D.

Nota sulle implementazioni di controlli touch

Per i controlli touch, implementiamo due controller: il controller di movimento (move), che gestisce il movimento nel piano x-z relativo al punto di osservazione della telecamera; e il controller di visuale (look), che dirige il punto di visuale della telecamera. Il controller di movimento esegue il mapping dei pulsanti WASD della tastiera, mentre il controller di visuale esegue il mapping del mouse. Per i controlli touch, tuttavia, dobbiamo definire un'area dello schermo che funga da input direzionale, o da pulsanti WASD virtuali, con il resto della schermata che funge da spazio di input per i controlli di visuale.

La nostra schermata sarà simile alla seguente:

the move-look controller layout

Quando si sposta il puntatore touch (non il mouse!) nella parte inferiore sinistra dello schermo, qualsiasi movimento verso l'alto farà avanzare la telecamera. Qualsiasi movimento verso il basso farà arretrare la telecamera. Lo stesso vale per lo movimento a sinistra e a destra all'interno dello spazio del puntatore del controller di movimento. Al di fuori di questo spazio, dove diventa un controller di visuale, basta toccare o trascinare la telecamera nel punto in cui si desidera orientarla.

Configurare l'infrastruttura di eventi di input di base

Prima di tutto, dobbiamo creare la classe di controllo che utilizziamo per gestire gli eventi di input dal mouse e dalla tastiera e aggiornare la prospettiva della telecamera in base a tale input. Poiché siamo nell'ambito dei controlli move-look, la definiamo MoveLookController.

using namespace Windows::UI::Core;
using namespace Windows::System;
using namespace Windows::Foundation;
using namespace Windows::Devices::Input;
#include <DirectXMath.h>

// Methods to get input from the UI pointers
ref class MoveLookController
{
};  // class MoveLookController

A questo punto, creiamo un'intestazione che definisce lo stato del controller move-look e della sua telecamera in prima persona, oltre ai metodi di base e ai gestori eventi che implementano i controlli e che aggiornano lo stato della telecamera.

#define ROTATION_GAIN 0.004f    // Sensitivity adjustment for the look controller
#define MOVEMENT_GAIN 0.1f      // Sensitivity adjustment for the move controller

ref class MoveLookController
{
private:
    // Properties of the controller object
    DirectX::XMFLOAT3 m_position;               // The position of the controller
    float m_pitch, m_yaw;           // Orientation euler angles in radians

    // Properties of the Move control
    bool m_moveInUse;               // Specifies whether the move control is in use
    uint32 m_movePointerID;         // Id of the pointer in this control
    DirectX::XMFLOAT2 m_moveFirstDown;          // Point where initial contact occurred
    DirectX::XMFLOAT2 m_movePointerPosition;   // Point where the move pointer is currently located
    DirectX::XMFLOAT3 m_moveCommand;            // The net command from the move control

    // Properties of the Look control
    bool m_lookInUse;               // Specifies whether the look control is in use
    uint32 m_lookPointerID;         // Id of the pointer in this control
    DirectX::XMFLOAT2 m_lookLastPoint;          // Last point (from last frame)
    DirectX::XMFLOAT2 m_lookLastDelta;          // For smoothing

    bool m_forward, m_back;         // States for movement
    bool m_left, m_right;
    bool m_up, m_down;


public:

    // Methods to get input from the UI pointers
    void OnPointerPressed(
        _In_ Windows::UI::Core::CoreWindow^ sender,
        _In_ Windows::UI::Core::PointerEventArgs^ args
        );

    void OnPointerMoved(
        _In_ Windows::UI::Core::CoreWindow^ sender,
        _In_ Windows::UI::Core::PointerEventArgs^ args
        );

    void OnPointerReleased(
        _In_ Windows::UI::Core::CoreWindow^ sender,
        _In_ Windows::UI::Core::PointerEventArgs^ args
        );

    void OnKeyDown(
        _In_ Windows::UI::Core::CoreWindow^ sender,
        _In_ Windows::UI::Core::KeyEventArgs^ args
        );

    void OnKeyUp(
        _In_ Windows::UI::Core::CoreWindow^ sender,
        _In_ Windows::UI::Core::KeyEventArgs^ args
        );

    // Set up the Controls that this controller supports
    void Initialize( _In_ Windows::UI::Core::CoreWindow^ window );

    void Update( Windows::UI::Core::CoreWindow ^window );
    
internal:
    // Accessor to set position of controller
    void SetPosition( _In_ DirectX::XMFLOAT3 pos );

    // Accessor to set position of controller
    void SetOrientation( _In_ float pitch, _In_ float yaw );

    // Returns the position of the controller object
    DirectX::XMFLOAT3 get_Position();

    // Returns the point  which the controller is facing
    DirectX::XMFLOAT3 get_LookPoint();


};  // class MoveLookController

Il codice contiene 4 gruppi di campi privati. Esaminiamo lo scopo di ognuno di essi.

Prima di tutto, definiamo alcuni campi utili che contengono le nostre informazioni aggiornate sulla visuale della telecamera.

  • m_position è la posizione della telecamera (e quindi il viewplane) nella scena 3D, che utilizza le coordinate della scena.
  • m_pitch è l'inclinazione della telecamera o la sua rotazione verticale intorno all'asse x del viewplane, in radianti.
  • m_yaw è l'imbardata della telecamera, o la rotazione orizzontale intorno all'asse y del viewplane, in radianti.

Definiamo ora i campi che utilizziamo per archiviare informazioni sullo stato e sulla posizione dei nostri controller. Prima di tutto, definiremo i campi necessari per il controller di movimento basato sul tocco. (Non è necessario nulla di particolare per l'implementazione della tastiera del controller di movimento. Leggiamo solo gli eventi della tastiera con gestori specifici.)

  • m_moveInUse indica se il controller di movimento è in uso.
  • m_movePointerID è l'ID univoco per il puntatore di movimento corrente. Viene utilizzato per distinguere tra il puntatore di visuale e il puntatore di movimento quando controlliamo il valore ID del puntatore.
  • m_moveFirstDown è il punto sullo schermo in cui il giocatore ha toccato per la prima volta l'area del puntatore del controller di movimento. Utilizziamo questo valore in un secondo momento per impostare una zona morta per evitare che eventuali piccoli movimenti rendano instabile la visualizzazione.
  • m_movePointerPosition è il punto sullo schermo in cui il giocatore ha spostato correntemente il puntatore. Lo utilizziamo per determinare la direzione in cui il giocatore voleva spostarsi esaminandolo rispetto a m_moveFirstDown.
  • m_moveCommand è il comando calcolato finale per il controller di movimento: su (avanti), giù (indietro), a sinistra o a destra.

Ora definiamo i campi che utilizziamo per il controller di visuale, sia le implementazioni del mouse che del touch.

  • m_lookInUse indica se il controller di visuale è in uso.
  • m_lookPointerID è l'ID univoco per il puntatore della visuale corrente. Viene utilizzato per distinguere tra il puntatore di visuale e il puntatore di movimento quando controlliamo il valore ID del puntatore.
  • m_lookLastPoint è l'ultimo punto, nelle coordinate della scena, acquisito nel frame precedente.
  • m_lookLastDelta è la differenza calcolata tra m_position corrente e m_lookLastPoint.

Infine, definiamo 6 valori booleani per i 6 gradi di movimento, che utilizziamo per indicare lo stato corrente di ogni azione di spostamento direzionale (on o off):

  • m_forward, m_back, m_left, m_right, m_up and m_down.

Utilizziamo i 6 gestori di eventi per acquisire i dati di input che utilizziamo per aggiornare lo stato dei nostri controller:

  • OnPointerPressed. Il giocatore ha premuto il pulsante sinistro del mouse con il puntatore nella nostra schermata di gioco o ha toccato lo schermo.
  • OnPointerMoved. Il giocatore ha spostato il mouse con il puntatore nella nostra schermata di gioco o ha trascinato il puntatore touch sullo schermo.
  • OnPointerReleased. Il giocatore ha rilasciato il pulsante sinistro del mouse con il puntatore nella nostra schermata di gioco o ha interrotto il touch sullo schermo.
  • OnKeyDown. Il giocatore ha premuto un tasto.
  • OnKeyUp. Il giocatore ha rilasciato un tasto.

Infine, utilizziamo questi metodi e proprietà per inizializzare, accedere e aggiornare le informazioni si stato dei controller.

  • Initialize. La nostra app chiama questo gestore di eventi per inizializzare i controlli e collegarli all'oggetto CoreWindow che descrive la nostra finestra di visualizzazione.
  • SetPosition. La nostra app chiama questo metodo per impostare le coordinate (x, y e z) dei nostri controlli nello spazio di scena.
  • SetOrientation. La nostra app chiama questo metodo per impostare l'inclinazione e l'imbardata della telecamera.
  • get_Position. La nostra app accede a questa proprietà per ottenere la posizione corrente della telecamera nello spazio di scena. Utilizziamo questa proprietà come metodo di comunicazione della posizione corrente della telecamera all'app.
  • get_LookPoint. La nostra app accede a questa proprietà per ottenere il punto corrente verso cui è orientata la telecamera del controller.
  • Aggiornamento. Legge lo stato dei controller di movimento e visuale e aggiorna la posizione della telecamera. Si chiama continuamente questo metodo dal ciclo principale dell'app per aggiornare i dati del controller della telecamera e la posizione della telecamera nello spazio di scena.

A questo punto, sono disponibili tutti i componenti necessari per implementare i controlli move-look. Quindi, ora colleghiamo questi pezzi insieme.

Creare gli eventi di input di base

Il dispatcher di eventi di Windows Runtime fornisce 5 eventi che desideriamo che le istanze della classe MoveLookController gestiscano:

Questi eventi vengono implementati nel tipo CoreWindow. Supponiamo di avere un oggetto CoreWindow con cui lavorare. Se non si sa come ottenerne uno, vedere Come configurare un'app UWP (Universal Windows Platform) C++ e visualizzare una vista DirectX.

Man mano che questi eventi vengono generati durante l'esecuzione dell'app, i gestori aggiornano le informazioni sullo stato dei controller definite nei nostri campi privati.

Prima di tutto, popoliamo i gestori di eventi del mouse e del puntatore touch. Nel primo gestore di eventi, OnPointerPressed(), otteniamo le coordinate x-y del puntatore da CoreWindow che gestisce la nostra visualizzazione quando l'utente fa clic sul mouse o tocca lo schermo nell'area del controller di visuale.

OnPointerPressed

void MoveLookController::OnPointerPressed(
_In_ CoreWindow^ sender,
_In_ PointerEventArgs^ args)
{
    // Get the current pointer position.
    uint32 pointerID = args->CurrentPoint->PointerId;
    DirectX::XMFLOAT2 position = DirectX::XMFLOAT2( args->CurrentPoint->Position.X, args->CurrentPoint->Position.Y );

    auto device = args->CurrentPoint->PointerDevice;
    auto deviceType = device->PointerDeviceType;
    if ( deviceType == PointerDeviceType::Mouse )
    {
        // Action, Jump, or Fire
    }

    // Check  if this pointer is in the move control.
    // Change the values  to percentages of the preferred screen resolution.
    // You can set the x value to <preferred resolution> * <percentage of width>
    // for example, ( position.x < (screenResolution.x * 0.15) ).

    if (( position.x < 300 && position.y > 380 ) && ( deviceType != PointerDeviceType::Mouse ))
    {
        if ( !m_moveInUse ) // if no pointer is in this control yet
        {
            // Process a DPad touch down event.
            m_moveFirstDown = position;                 // Save the location of the initial contact.
            m_movePointerPosition = position;
            m_movePointerID = pointerID;                // Store the id of the pointer using this control.
            m_moveInUse = TRUE;
        }
    }
    else // This pointer must be in the look control.
    {
        if ( !m_lookInUse ) // If no pointer is in this control yet...
        {
            m_lookLastPoint = position;                         // save the point for later move
            m_lookPointerID = args->CurrentPoint->PointerId;  // store the id of pointer using this control
            m_lookLastDelta.x = m_lookLastDelta.y = 0;          // these are for smoothing
            m_lookInUse = TRUE;
        }
    }
}

Questo gestore di eventi controlla se il puntatore non è il mouse (ai fini di questo esempio, che supporta sia il mouse che il touch) e se si trova nell'area del controller di movimento. Se entrambi i criteri sono "true", controlla se il puntatore sia stato premuto, in particolare se questo clic non è correlato a un input di movimento o di visuale precedente, verificando se m_moveInUse sia "false". In tal caso, il gestore acquisisce il punto nell'area del controller di movimento in cui si è verificata la pressione e imposta m_moveInUse su "true", in modo che quando questo gestore viene chiamato di nuovo, non sovrascriverà la posizione iniziale dell'interazione di input del controller di movimento. Aggiorna anche l'ID del puntatore del controller di movimento all'ID del puntatore corrente.

Se il puntatore è il mouse o se il puntatore touch non si trova nell'area del controller di movimento, deve trovarsi nell'area del controller di visuale. Imposta m_lookLastPoint sulla posizione corrente in cui l'utente ha premuto il tasto del mouse o ha toccato e premuto, reimposta il delta e aggiorna l'ID puntatore del controller di visuale all'ID puntatore corrente. Imposta anche lo stato del controller di visuale su attivo.

OnPointerMoved

void MoveLookController::OnPointerMoved(
    _In_ CoreWindow ^sender,
    _In_ PointerEventArgs ^args)
{
    uint32 pointerID = args->CurrentPoint->PointerId;
    DirectX::XMFLOAT2 position = DirectX::XMFLOAT2(args->CurrentPoint->Position.X, args->CurrentPoint->Position.Y);

    // Decide which control this pointer is operating.
    if (pointerID == m_movePointerID)           // This is the move pointer.
    {
        // Move control
        m_movePointerPosition = position;       // Save the current position.

    }
    else if (pointerID == m_lookPointerID)      // This is the look pointer.
    {
        // Look control

        DirectX::XMFLOAT2 pointerDelta;
        pointerDelta.x = position.x - m_lookLastPoint.x;        // How far did pointer move
        pointerDelta.y = position.y - m_lookLastPoint.y;

        DirectX::XMFLOAT2 rotationDelta;
        rotationDelta.x = pointerDelta.x * ROTATION_GAIN;   // Scale for control sensitivity.
        rotationDelta.y = pointerDelta.y * ROTATION_GAIN;

        m_lookLastPoint = position;                     // Save for the next time through.

                                                        // Update our orientation based on the command.
        m_pitch -= rotationDelta.y;                     // Mouse y increases down, but pitch increases up.
        m_yaw -= rotationDelta.x;                       // Yaw is defined as CCW around the y-axis.

                                                        // Limit the pitch to straight up or straight down.
        m_pitch = (float)__max(-DirectX::XM_PI / 2.0f, m_pitch);
        m_pitch = (float)__min(+DirectX::XM_PI / 2.0f, m_pitch);
    }
}

Il gestore eventi OnPointerMoved viene attivato ogni volta che il puntatore si sposta (in questo caso, se viene trascinato un puntatore touch screen o se viene spostato il puntatore del mouse mentre viene premuto il pulsante sinistro). Se l'ID puntatore è uguale all'ID del puntatore del controller di movimento, allora si tratta del puntatore di movimento; in caso contrario, controlliamo se si tratta del controller di visuale che è il puntatore attivo.

Se si tratta del controller di movimento, è sufficiente aggiornare la posizione del puntatore. Continuiamo ad aggiornarla finché l'evento PointerMoved continua a essere attivato, in quanto desideriamo confrontare la posizione finale con la prima acquisita con il gestore di eventi OnPointerPressed.

Se si tratta del controller di visuale, le cose sono un po' più complicate. Dobbiamo calcolare un nuovo punto di visuale e centrare la telecamera su di esso, quindi calcoliamo il delta tra l'ultimo punto di visuale e la posizione dello schermo corrente, e poi moltiplichiamo rispetto al nostro fattore di scala, che possiamo modificare per rendere i movimenti di visuale più piccoli o più grandi rispetto alla distanza dello movimento dello schermo. Utilizzando tale valore, calcoliamo la inclinazione e l'imbardata.

Infine, è necessario disattivare i comportamenti del controller di movimento o di visuale quando il giocatore smette di spostare il mouse o di toccare lo schermo. Utilizziamo OnPointerReleased, che chiamiamo quando viene generato PointerReleased, per impostare m_moveInUse o m_lookInUse su FALSE e interrompere il movimento panoramico della telecamera, e per azzerare l'ID puntatore.

OnPointerReleased

void MoveLookController::OnPointerReleased(
_In_ CoreWindow ^sender,
_In_ PointerEventArgs ^args)
{
    uint32 pointerID = args->CurrentPoint->PointerId;
    DirectX::XMFLOAT2 position = DirectX::XMFLOAT2( args->CurrentPoint->Position.X, args->CurrentPoint->Position.Y );


    if ( pointerID == m_movePointerID )    // This was the move pointer.
    {
        m_moveInUse = FALSE;
        m_movePointerID = 0;
    }
    else if (pointerID == m_lookPointerID ) // This was the look pointer.
    {
        m_lookInUse = FALSE;
        m_lookPointerID = 0;
    }
}

Finora abbiamo gestito tutti gli eventi dello schermo touch. A questo punto, gestiamo gli eventi di input dei tasti per un controller di movimento basato su tastiera.

OnKeyDown

void MoveLookController::OnKeyDown(
                                   __in CoreWindow^ sender,
                                   __in KeyEventArgs^ args )
{
    Windows::System::VirtualKey Key;
    Key = args->VirtualKey;

    // Figure out the command from the keyboard.
    if ( Key == VirtualKey::W )     // Forward
        m_forward = true;
    if ( Key == VirtualKey::S )     // Back
        m_back = true;
    if ( Key == VirtualKey::A )     // Left
        m_left = true;
    if ( Key == VirtualKey::D )     // Right
        m_right = true;
}

Se si preme uno di questi tasti, questo gestore di eventi imposta lo stato di movimento direzionale corrispondente su "true".

OnKeyUp

void MoveLookController::OnKeyUp(
                                 __in CoreWindow^ sender,
                                 __in KeyEventArgs^ args)
{
    Windows::System::VirtualKey Key;
    Key = args->VirtualKey;

    // Figure out the command from the keyboard.
    if ( Key == VirtualKey::W )     // forward
        m_forward = false;
    if ( Key == VirtualKey::S )     // back
        m_back = false;
    if ( Key == VirtualKey::A )     // left
        m_left = false;
    if ( Key == VirtualKey::D )     // right
        m_right = false;
}

E quando il tasto viene rilasciato, questo gestore di eventi lo reimposta su "false". Quando chiamiamo Update, controlla questi stati di spostamento direzionale e sposta la telecamera di conseguenza. Tutto questo è un po' più semplice rispetto all'implementazione touch!

Inizializzare i controlli touch e lo stato del controller

Ora associamo gli eventi e inizializziamo tutti i campi di stato del controller.

Initialize

void MoveLookController::Initialize( _In_ CoreWindow^ window )
{

    // Opt in to receive touch/mouse events.
    window->PointerPressed += 
    ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &MoveLookController::OnPointerPressed);

    window->PointerMoved += 
    ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &MoveLookController::OnPointerMoved);

    window->PointerReleased += 
    ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &MoveLookController::OnPointerReleased);

    window->CharacterReceived +=
    ref new TypedEventHandler<CoreWindow^, CharacterReceivedEventArgs^>(this, &MoveLookController::OnCharacterReceived);

    window->KeyDown += 
    ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(this, &MoveLookController::OnKeyDown);

    window->KeyUp += 
    ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(this, &MoveLookController::OnKeyUp);

    // Initialize the state of the controller.
    m_moveInUse = FALSE;                // No pointer is in the Move control.
    m_movePointerID = 0;

    m_lookInUse = FALSE;                // No pointer is in the Look control.
    m_lookPointerID = 0;

    //  Need to init this as it is reset every frame.
    m_moveCommand = DirectX::XMFLOAT3( 0.0f, 0.0f, 0.0f );

    SetOrientation( 0, 0 );             // Look straight ahead when the app starts.

}

Initialize accetta un riferimento all'istanza CoreWindow dell'app come parametro e registra i gestori di eventi sviluppati negli eventi appropriati in tale CoreWindow. Inizializza gli ID del puntatore di movimento e di visuale, imposta il vettore di comando per la nostra implementazione del controller di movimento touch screen su zero e imposta la telecamera in modo che guardi dritto in avanti all'avvio dell'app.

Ottenere e impostare la posizione e l'orientamento della telecamera

Definiamo alcuni metodi per ottenere e impostare la posizione della telecamera rispetto alla viewport.

void MoveLookController::SetPosition( _In_ DirectX::XMFLOAT3 pos )
{
    m_position = pos;
}

// Accessor to set the position of the controller.
void MoveLookController::SetOrientation( _In_ float pitch, _In_ float yaw )
{
    m_pitch = pitch;
    m_yaw = yaw;
}

// Returns the position of the controller object.
DirectX::XMFLOAT3 MoveLookController::get_Position()
{
    return m_position;
}

// Returns the point at which the camera controller is facing.
DirectX::XMFLOAT3 MoveLookController::get_LookPoint()
{
    float y = sinf(m_pitch);        // Vertical
    float r = cosf(m_pitch);        // In the plane
    float z = r*cosf(m_yaw);        // Fwd-back
    float x = r*sinf(m_yaw);        // Left-right
    DirectX::XMFLOAT3 result(x,y,z);
    result.x += m_position.x;
    result.y += m_position.y;
    result.z += m_position.z;

    // Return m_position + DirectX::XMFLOAT3(x, y, z);
    return result;
}

Aggiornamento delle informazioni sullo stato del controller

A questo punto, eseguiamo i nostri calcoli che convertono le informazioni sulle coordinate del puntatore rilevate in m_movePointerPosition in nuove informazioni sulle coordinate del nostro sistema di coordinate globale. La nostra app chiama questo metodo ogni volta che aggiorniamo il ciclo principale dell'app. Quindi, è qui che calcoliamo le nuove informazioni sulla posizione del punto di visuale che vogliamo trasferire all'app per aggiornare la matrice di visualizzazione prima della proiezione nella viewport.

void MoveLookController::Update(CoreWindow ^window)
{
    // Check for input from the Move control.
    if (m_moveInUse)
    {
        DirectX::XMFLOAT2 pointerDelta(m_movePointerPosition);
        pointerDelta.x -= m_moveFirstDown.x;
        pointerDelta.y -= m_moveFirstDown.y;

        // Figure out the command from the touch-based virtual joystick.
        if (pointerDelta.x > 16.0f)      // Leave 32 pixel-wide dead spot for being still.
            m_moveCommand.x = 1.0f;
        else
            if (pointerDelta.x < -16.0f)
            m_moveCommand.x = -1.0f;

        if (pointerDelta.y > 16.0f)      // Joystick y is up, so change sign.
            m_moveCommand.y = -1.0f;
        else
            if (pointerDelta.y < -16.0f)
            m_moveCommand.y = 1.0f;
    }

    // Poll our state bits that are set by the keyboard input events.
    if (m_forward)
        m_moveCommand.y += 1.0f;
    if (m_back)
        m_moveCommand.y -= 1.0f;

    if (m_left)
        m_moveCommand.x -= 1.0f;
    if (m_right)
        m_moveCommand.x += 1.0f;

    if (m_up)
        m_moveCommand.z += 1.0f;
    if (m_down)
        m_moveCommand.z -= 1.0f;

    // Make sure that 45 degree cases are not faster.
    DirectX::XMFLOAT3 command = m_moveCommand;
    DirectX::XMVECTOR vector;
    vector = DirectX::XMLoadFloat3(&command);

    if (fabsf(command.x) > 0.1f || fabsf(command.y) > 0.1f || fabsf(command.z) > 0.1f)
    {
        vector = DirectX::XMVector3Normalize(vector);
        DirectX::XMStoreFloat3(&command, vector);
    }
    

    // Rotate command to align with our direction (world coordinates).
    DirectX::XMFLOAT3 wCommand;
    wCommand.x = command.x*cosf(m_yaw) - command.y*sinf(m_yaw);
    wCommand.y = command.x*sinf(m_yaw) + command.y*cosf(m_yaw);
    wCommand.z = command.z;

    // Scale for sensitivity adjustment.
    wCommand.x = wCommand.x * MOVEMENT_GAIN;
    wCommand.y = wCommand.y * MOVEMENT_GAIN;
    wCommand.z = wCommand.z * MOVEMENT_GAIN;

    // Our velocity is based on the command.
    // Also note that y is the up-down axis. 
    DirectX::XMFLOAT3 Velocity;
    Velocity.x = -wCommand.x;
    Velocity.z = wCommand.y;
    Velocity.y = wCommand.z;

    // Integrate
    m_position.x += Velocity.x;
    m_position.y += Velocity.y;
    m_position.z += Velocity.z;

    // Clear movement input accumulator for use during the next frame.
    m_moveCommand = DirectX::XMFLOAT3(0.0f, 0.0f, 0.0f);

}

Poiché non vogliamo un movimento di instabilità quando il giocatore utilizza il nostro controller di movimento basato sul tocco, impostiamo una zona morta virtuale intorno al puntatore con un diametro di 32 pixel. Aggiungiamo anche velocità, ovvero il valore del comando più un tasso di guadagno di movimento. (È possibile regolare questo comportamento in base alle proprie esigenze, per rallentare o velocizzare la velocità di movimento in base alla distanza in cui il puntatore si sposta nell'area del controller di movimento.)

Quando calcoliamo la velocità, traduciamo anche le coordinate ricevute dai controller di movimento e visuale nel movimento del punto di visuale effettivo che inviamo al metodo che calcola la nostra matrice di visualizzazione per la scena. In primo luogo, invertiamo la coordinata x, perché se facciamo clic-e muoviamo o trasciniamo a sinistra o a destra con il controller di visuale, il punto di visuale ruota nella direzione opposta nella scena, come una telecamera potrebbe oscillare attorno al suo asse centrale. Quindi, scambiamo gli assi y e z, perché la pressione di un tasto su/giù o un movimento di trascinamento touch (letto come un comportamento dell'asse y) nel controller di movimento deve tradursi in un'azione della telecamera che sposta il punto di visuale all'interno o all'esterno della schermata (l'asse z).

La posizione finale del punto di visuale per il giocatore è l'ultima posizione più la velocità calcolata, e questo è ciò che viene letto dal renderer quando chiama il metodo get_Position (molto probabilmente durante la configurazione per ciascun frame). Successivamente, reimpostiamo il comando di movimento su zero.

Aggiornamento della matrice di visualizzazione con la nuova posizione della telecamera

Possiamo ottenere una coordinata dello spazio di scena su cui si concentra la nostra telecamera e che viene aggiornata ogni volta che si indica all'app di farlo (ad esempio ogni 60 secondi nel ciclo principale dell'app). Questo pseudocodice suggerisce il comportamento di chiamata che è possibile implementare:

myMoveLookController->Update( m_window );   

// Update the view matrix based on the camera position.
myFirstPersonCamera->SetViewParameters(
                 myMoveLookController->get_Position(),       // Point we are at
                 myMoveLookController->get_LookPoint(),      // Point to look towards
                 DirectX::XMFLOAT3( 0, 1, 0 )                   // Up-vector
                 ); 

Complimenti. Sono stati implementati i controlli move-look di base per gli schermi touch e i controlli touch da input da tastiera/mouse in un gioco!