Udostępnij za pośrednictwem


kontrolki Move-look dla gier

Dowiedz się, jak dodać tradycyjne kontrolki przenoszenia myszy i klawiatury (znane również jako kontrolki myszy) do gry DirectX.

Omawiamy także obsługę funkcji move-look dla urządzeń dotykowych, gdzie kontroler ruchu to dolna lewa część ekranu, działająca jak wejście kierunkowe, a kontroler widoku obejmuje resztę ekranu. Aparat wyśrodkowuje się na miejscu, które gracz ostatnio dotknął w tym obszarze.

Jeśli jest to nieznany ci koncept sterowania, pomyśl o tym w ten sposób: klawiatura (lub pole wprowadzania kierunkowego opartego na dotyku) kontroluje nogi w tej przestrzeni 3D i zachowuje się tak, jakby nogi były w stanie poruszać się do przodu lub do tyłu, lub poruszać się w bok w lewo i w prawo. Mysz (lub wskaźnik dotykowy) steruje Twoją głową. Patrzysz głową w kierunku — w lewo lub w prawo, w górę lub w dół, albo gdzieś w tej płaszczyźnie. Jeśli w widoku znajduje się obiekt docelowy, użyj myszy, aby wyśrodkować widok kamery na tym obiekcie docelowym, a następnie nacisnąć do przodu, aby przejść do niego, lub wrócić, aby odejść od niego. Aby okrążyć obiekt docelowy, należy zachować widok kamery wyśrodkowany na obiekcie docelowym i przenieść w lewo lub w prawo w tym samym czasie. Możesz zobaczyć, jak jest to bardzo skuteczna metoda sterowania nawigacją po środowiskach 3D!

Te kontrolki są powszechnie znane jako kontrolki WASD w grach, gdzie W, A, S i D są używane do stałego ruchu kamery x-z, a mysz służy do sterowania rotacją kamery wokół osi x i y.

Cele i zadania

  • Dodaj podstawowe kontrolki move-look do gry DirectX zarówno dla myszy, jak i klawiatury oraz ekranów dotykowych.
  • Zaimplementuj kamerę pierwszoosobową, aby nawigować po środowisku 3D.

Uwaga dotycząca implementacji kontrolek dotykowych

W przypadku kontrolek dotykowych implementujemy dwa kontrolery: kontroler przemieszczania, który obsługuje ruch w płaszczyźnie x-z względem punktu celowania kamery, oraz kontroler widoku, który ustawia punkt celowania kamery. Nasz kontroler ruchu mapuje się na przyciski WASD klawiatury, a kontroler widoku mapuje się na mysz. Jednak w przypadku kontrolek dotykowych musimy zdefiniować region ekranu, który służy jako dane wejściowe kierunkowe lub wirtualne przyciski WASD, z resztą ekranu służącą jako przestrzeń wejściowa dla kontrolek wyglądu.

Nasz ekran wygląda następująco.

układu kontrolera sterowania ruchem i widokiem

Po przesunięciu wskaźnika dotykowego (nie myszy!) w lewym dolnym rogu ekranu wszelkie ruchy w górę sprawią, że aparat przejdzie do przodu. Każdy ruch w dół sprawi, że aparat przejdzie do tyłu. To samo dotyczy ruchu w lewo i w prawo wewnątrz przestrzeni wskaźnika kontrolera ruchu. Poza tym miejscem staje się kontrolerem widoku — wystarczy dotknąć lub przeciągnąć kamerę do miejsca, w którym chcesz, aby była skierowana.

Konfigurowanie podstawowej infrastruktury zdarzeń wejściowych

Najpierw musimy utworzyć naszą klasę sterowania używaną do obsługi zdarzeń wejściowych za pomocą myszy i klawiatury oraz zaktualizować perspektywę aparatu na podstawie tych danych wejściowych. Ponieważ implementujemy kontrolki move-look, nazywamy ją 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

Teraz utwórzmy nagłówek, który definiuje stan kontrolera ruchu i wyglądu oraz jego kamery z perspektywy pierwszej osoby, a także podstawowe metody i obsługę zdarzeń, które implementują sterowanie i aktualizują stan kamery.

#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

Nasz kod zawiera 4 grupy pól prywatnych. Przyjrzyjmy się celowi każdego z nich.

Najpierw definiujemy kilka przydatnych pól, które przechowują zaktualizowane informacje o naszym widoku aparatu.

  • m_position jest położeniem kamery (a zatem płaszczyzną widokową) w scenie 3D przy użyciu współrzędnych sceny.
  • m_pitch to pochylenie kamery lub jej rotacja góra-dół wokół osi x planu widokowego, wyrażona w radianach.
  • m_yaw to yaw kamery lub jej rotacja w lewo i prawo wokół osi y płaszczyzny widokowej, w radianach.

Teraz zdefiniujmy pola, których używamy do przechowywania informacji o stanie i pozycji naszych kontrolerów. Najpierw zdefiniujemy pola potrzebne dla naszego kontrolera przenoszenia opartego na dotyku. (Nie ma niczego specjalnego potrzebnego do implementacji klawiszy dla kontrolera ruchu. Po prostu odczytujemy zdarzenia klawiatury z określonymi procedurami obsługi).

  • m_moveInUse wskazuje, czy kontroler przenoszenia jest używany.
  • m_movePointerID jest unikatowym identyfikatorem bieżącego wskaźnika ruchu. Używamy go do rozróżniania między wskaźnikiem wzrokowym a wskaźnikiem ruchu podczas sprawdzania wartości identyfikatora wskaźnika.
  • m_moveFirstDown jest punktem na ekranie, na którym gracz po raz pierwszy dotknął obszaru wskaźnika kontrolera ruchu. Użyjemy tej wartości później, aby ustawić martwą strefę, aby zapobiec drganiu widoku na skutek małych ruchów.
  • m_movePointerPosition jest punktem na ekranie, do którego gracz przeniósł wskaźnik. Używamy go, aby określić kierunek, w jakim gracz chciał się poruszać, sprawdzając go względem m_moveFirstDown.
  • m_moveCommand jest ostatnim obliczonym poleceniem kontrolera przenoszenia: w górę (do przodu), w dół (z powrotem), w lewo lub w prawo.

Teraz definiujemy pola, których używamy dla kontrolera widoku, zarówno dla implementacji myszy, jak i dotykowej.

  • m_lookInUse wskazuje, czy kontrolka wyglądu jest używana.
  • m_lookPointerID jest unikatowym identyfikatorem bieżącego wskaźnika widoku. Używamy go do rozróżniania między wskaźnikiem wzrokowym a wskaźnikiem ruchu podczas sprawdzania wartości identyfikatora wskaźnika.
  • m_lookLastPoint jest ostatnim punktem we współrzędnych sceny, który został uchwycony w poprzedniej klatce.
  • m_lookLastDelta jest obliczoną różnicą pomiędzy aktualnym m_position a m_lookLastPoint.

Na koniec definiujemy 6 wartości logicznych dla 6 stopni ruchu, których używamy do wskazywania bieżącego stanu każdej akcji ruchu kierunkowego (włączonej lub wyłączonej):

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

Używamy 6 procedur obsługi zdarzeń do przechwytywania danych wejściowych używanych do aktualizowania stanu naszych kontrolerów:

  • OnPointerPressed. Gracz nacisnął lewy przycisk myszy ze wskaźnikiem na ekranie gry lub dotknął ekranu.
  • OnPointerMoved. Gracz przeniósł mysz ze wskaźnikiem na ekranie gry lub przeciągnął wskaźnik dotykowy na ekranie.
  • OnPointerReleased. Gracz zwolnił lewy przycisk myszy ze wskaźnikiem na ekranie gry lub przestał dotykać ekranu.
  • OnKeyDown. Gracz nacisnął klawisz.
  • OnKeyUp. Gracz puścił klucz.

Na koniec używamy tych metod i właściwości do inicjowania, uzyskiwania dostępu i aktualizowania informacji o stanie kontrolerów.

  • Zainicjuj. Nasza aplikacja wywołuje tę procedurę obsługi zdarzeń, aby zainicjować kontrolki i dołączyć je do obiektu CoreWindow opisującego nasze okno wyświetlania.
  • UstawPozycję. Nasza aplikacja wywołuje tę metodę w celu ustawienia współrzędnych (x, y i z) naszych kontrolek w obszarze sceny.
  • SetOrientation. Nasza aplikacja wywołuje tę metodę, aby ustawić pochylenie i odchylenie aparatu.
  • get_Position. Nasza aplikacja uzyskuje dostęp do tej właściwości, aby uzyskać bieżącą pozycję aparatu w przestrzeni sceny. Ta właściwość jest używana jako metoda komunikowania bieżącej pozycji aparatu z aplikacją.
  • get_LookPoint. Nasza aplikacja uzyskuje dostęp do tej właściwości, aby uzyskać bieżący kierunek, w którym skierowana jest kamera kontrolera.
  • Aktualizacja. Odczytuje stan kontrolerów ruchu i widzenia oraz aktualizuje pozycję kamery. Ta metoda jest stale wywoływana z pętli głównej aplikacji w celu odświeżenia danych kontrolera aparatu i położenia aparatu w przestrzeni sceny.

Teraz masz tutaj wszystkie składniki potrzebne do zaimplementowania kontrolek move-look. Połączmy te elementy razem.

Tworzenie podstawowych zdarzeń wejściowych

Dyspozytor zdarzeń środowiska uruchomieniowego systemu Windows udostępnia 5 zdarzeń, które chcemy, aby wystąpienia klasy MoveLookController obsługiwały:

Te zdarzenia są implementowane w typie CoreWindow. Zakładamy, że masz obiekt CoreWindow do pracy. Jeśli nie wiesz, jak go uzyskać, zobacz Jak skonfigurować swoją aplikację Universal Windows Platform (UWP) w C++, aby wyświetlała widok DirectX.

Gdy te zdarzenia są uruchamiane podczas działania naszej aplikacji, programy obsługi aktualizują informacje o stanie kontrolerów zdefiniowane w naszych polach prywatnych.

Najpierw wypełnijmy procedury obsługi zdarzeń wskaźnika myszy i wskaźnika dotykowego. W pierwszej procedurze obsługi zdarzeń OnPointerPressed()otrzymujemy współrzędne x-y wskaźnika z CoreWindow, który zarządza naszym wyświetlaczem, gdy użytkownik kliknie mysz lub dotyka ekranu w regionie kontrolera wyglądu.

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;
        }
    }
}

Ta procedura obsługi zdarzeń sprawdza, czy wskaźnik nie jest myszą (na potrzeby tego przykładu, który obsługuje zarówno mysz, jak i dotyk) oraz czy znajduje się w obszarze kontrolera ruchu. Jeśli oba kryteria są prawdziwe, sprawdza, czy wskaźnik został właśnie naciśnięty, w szczególności, czy to kliknięcie nie ma związku z poprzednim przesunięciem lub poruszeniem polem widzenia, testując, czy m_moveInUse jest fałszem. Jeśli tak, program obsługi przechwytuje punkt w obszarze kontrolera przenoszenia, w którym nastąpiło naciśnięcie i ustawia m_moveInUse na wartość true, tak aby gdy ten program obsługi jest wywoływany ponownie, nie nadpisze początkowej pozycji interakcji wejściowej kontrolera przenoszenia. Aktualizuje również identyfikator kontrolera ruchu do identyfikatora tego bieżącego wskaźnika.

Jeśli wskaźnik to mysz lub wskaźnik dotykowy nie znajduje się w obszarze kontrolera ruchu, musi znajdować się w obszarze kontrolera wyglądu. Ustawia m_lookLastPoint na bieżące położenie, w którym użytkownik nacisnął przycisk myszy lub dotknął i nacisnął, resetuje zmianę i aktualizuje identyfikator wskaźnika kontrolera wyglądu do bieżącego identyfikatora wskaźnika. Ustawia również stan kontrolera wyglądu na aktywny.

WskaźnikPrzesunięty

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);
    }
}

Procedura obsługi zdarzeń OnPointerMoved jest uruchamiana za każdym razem, gdy wskaźnik jest przesuwany (w tym przypadku, jeśli wskaźnik ekranu dotykowego jest przeciągany, lub jeśli wskaźnik myszy jest przesuwany podczas naciśnięcia lewego przycisku). Jeśli identyfikator wskaźnika jest taki sam jak identyfikator wskaźnika kontrolera ruchu, to jest to wskaźnik ruchu, a w przeciwnym razie sprawdzamy, czy jest to kontroler widoku, który jest aktywnym wskaźnikiem.

Jeśli jest to kontroler przenoszenia, po prostu zaktualizujemy położenie wskaźnika. Aktualizujemy go tak długo, jak długo zdarzenie PointerMoved jest nadal wyzwalane, ponieważ chcemy porównać ostateczną pozycję z pierwszą, którą przechwyciliśmy za pomocą obsługi zdarzenia OnPointerPressed.

Jeśli jest to kontroler wyglądu, rzeczy są nieco bardziej skomplikowane. Musimy obliczyć nowy punkt widzenia i wyśrodkować kamerę na nim, więc obliczamy różnicę między ostatnim punktem widzenia a bieżącą pozycją ekranu, a następnie mnożymy przez nasz współczynnik skalowania, który możemy dostosować, aby ruch punktu widzenia był mniejszy lub większy w stosunku do odległości ruchu ekranu. Przy użyciu tej wartości obliczamy pochylenie i odchylenie.

Na koniec musimy dezaktywować zachowanie kontrolera ruchu lub widoku, gdy gracz przestanie używać myszy lub dotykać ekranu. Używamy OnPointerReleased, który wywołujemy, gdy PointerReleased jest wyzwalany, aby ustawić wartość m_moveInUse lub m_lookInUse na FALSE, wyłączyć ruch panoramiczny kamery i zresetować identyfikator wskaźnika.

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;
    }
}

Do tej pory obsłużyliśmy wszystkie wydarzenia ekranu dotykowego. Teraz obsłużmy kluczowe zdarzenia wejściowe dla kontrolera ruchu opartego na klawiaturze.

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;
}

Tak długo, jak jeden z tych jest naciśnięty, ten program obsługi zdarzeń ustawia odpowiedni stan ruchu kierunkowego na 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;
}

Po wydaniu klucza ten program obsługi zdarzeń ustawia go z powrotem na wartość false. Gdy wywołujemy Update, sprawdza stany ruchu w różnych kierunkach i odpowiednio przesuwa kamerę. Jest to nieco prostsze niż implementacja dotykowa!

Inicjowanie kontrolek dotykowych i stanu kontrolera

Podłączmy teraz zdarzenia i zainicjujmy wszystkie pola stanu kontrolera.

Zainicjuj

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 przyjmuje odwołanie do wystąpienia CoreWindow aplikacji jako parametru i rejestruje programy obsługi zdarzeń opracowane dla odpowiednich zdarzeń w tym CoreWindow. Inicjuje identyfikatory wskaźników ruchu i widoku, ustawia wektor poleceń naszej implementacji kontrolera ruchu na ekranie dotykowym na zero oraz ustawia kamerę tak, aby patrzyła prosto przed siebie, gdy aplikacja się uruchamia.

Pobieranie i ustawianie położenia i orientacji aparatu

Zdefiniujmy kilka metod do pobierania i ustawiania pozycji kamery względem widoku końcowego.

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;
}

Aktualizowanie informacji o stanie kontrolera

Teraz przeprowadzamy obliczenia, które przekształcają śledzone informacje o współrzędnych wskaźnika w m_movePointerPosition na nowe współrzędne odpowiednie dla naszego układu współrzędnych świata. Nasza aplikacja wywołuje tę metodę za każdym razem, gdy odświeżymy pętlę głównej aplikacji. Dlatego w tym miejscu obliczamy nowe informacje o pozycji punktu widokowego, które chcemy przekazać do aplikacji w celu zaktualizowania macierzy widoku przed projekcją do okna widoku.

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

}

Ponieważ nie chcemy nierównego ruchu, gdy gracz używa naszego kontrolera ruchu dotykowego, ustawiamy wirtualną strefę martwą wokół wskaźnika o średnicy 32 pikseli. Dodamy również szybkość, która jest wartością polecenia oraz współczynnikiem przyrostu ruchu. (To zachowanie można dostosować do swoich potrzeb, aby spowolnić lub przyspieszyć szybkość ruchu na podstawie odległości, z jaką wskaźnik porusza się w obszarze kontrolera ruchu).

Gdy obliczamy prędkość, przekształcamy również współrzędne odbierane z kontrolerów ruchu i widzenia do ruchu rzeczywistego punktu, który wysyłamy do metody obliczającej macierz widoku dla sceny. Najpierw odwracamy współrzędną x, ponieważ jeśli klikamy lub przeciągamy w lewo lub w prawo z kontrolerem wyglądu, punkt wyszukiwania obraca się w przeciwnym kierunku w scenie, ponieważ aparat może kołysać się wokół jego osi centralnej. Następnie zamieniamy osie y i z, ponieważ naciśnięcie w górę/w dół lub ruch przeciągania dotyku (odczytane jako zachowanie osi y) na kontrolerze ruchu powinno przełożyć się na akcję aparatu, która przenosi punkt widzenia do lub z ekranu (oś z).

Ostateczna pozycja punktu patrzenia dla gracza to ostatnia pozycja plus obliczona prędkość, i to jest to, co jest odczytywane przez renderer, gdy wywołuje metodę get_Position (najprawdopodobniej podczas ustawień dla każdej ramki). Następnie resetujemy polecenie przenoszenia do zera.

Aktualizowanie macierzy widoków przy użyciu nowej pozycji aparatu

Możemy uzyskać współrzędną przestrzeni sceny, na której koncentruje się nasza kamera, i która jest aktualizowana za każdym razem, gdy poinformujesz aplikację o tym (na przykład co 60 sekund w pętli aplikacji głównej). Ten pseudokod sugeruje zachowanie wywołujące, które można zaimplementować:

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

Gratulacje! Zaimplementowano podstawowe kontrolki typu move-look zarówno dla ekranów dotykowych, jak i kontrolek dotykowych klawiatury/myszy w grze!