게임용 이동-보기 컨트롤

DirectX 게임에 기존 마우스 및 키보드 이동 모양 컨트롤(mouselook 컨트롤이라고도 함)을 추가하는 방법을 알아봅니다.

또한 이동 컨트롤러가 방향 입력처럼 동작하는 화면의 좌측 하단 섹션으로 정의되고 화면의 나머지 부분에 대해 정의된 모양 컨트롤러를 사용하여 터치 장치에 대한 이동-보기 지원에 대해 설명합니다.

익숙하지 않은 컨트롤 개념인 경우, 키보드(또는 터치 기반 방향 입력 상자)가 이 3D 공간에서 다리를 제어하고 다리가 앞으로 또는 뒤로만 이동하거나 왼쪽과 오른쪽으로 스트래핑할 수 있는 것처럼 동작합니다. 마우스(또는 터치 포인터)가 머리를 제어합니다. 머리를 사용하여 왼쪽이나 오른쪽, 위쪽 또는 아래쪽 또는 그 평면의 어딘가에 있는 방향을 볼 수 있습니다. 보기에 대상이 있는 경우, 마우스를 사용하여 카메라 보기를 해당 대상의 가운데에 배치한 다음 앞으로 키를 눌러 앞으로 이동하거나 뒤로 이동하여 멀리 이동합니다. 대상에 동그라미를 그리려면 카메라 보기를 대상의 가운데에 두는 동시에 왼쪽 또는 오른쪽으로 이동합니다. 이것이 3D 환경을 탐색하는 매우 효과적인 제어 방법이라는 점을 확인할 수 있습니다!

이러한 컨트롤은 일반적으로 게임에서 WASD 컨트롤이라고 하며, 여기서 W, A, S, D 키는 x-z 평면 고정 카메라 이동에 사용되고 마우스는 x축과 y축 주위의 카메라 회전을 제어하는 데 사용됩니다.

목표

  • 마우스, 키보드, 터치 스크린 모두에 대한 DirectX 게임에 기본 이동 모양 컨트롤을 추가합니다.
  • 3D 환경을 탐색하는 데 사용되는 1인칭 카메라를 구현합니다.

터치 컨트롤 구현에 대한 참고 사항

터치 컨트롤의 경우 두 개의 컨트롤러, 즉 카메라의 보기 지점을 기준으로 x-z 평면의 움직임을 처리하는 이동 컨트롤러를 구현합니다. 이는 카메라의 모양을 목표로 하는 보기 컨트롤러입니다. 이동 컨트롤러는 키보드 WASD 버튼에 매핑되고 보기 컨트롤러는 마우스에 매핑됩니다. 그러나 터치 컨트롤의 경우 화면의 남은 부분이 보기 컨트롤의 입력 공간 역할을 하는 방향 입력 또는 가상 WASD 버튼 역할을 하는 화면 영역을 정의해야 합니다.

화면이 다음과 같이 보입니다.

the move-look controller layout

화면 좌측 하단에 있는 터치 포인터(마우스가 아님)를 움직일 때 위쪽으로 움직이면 카메라가 앞으로 이동합니다. 아래로 이동하면 카메라가 뒤로 이동합니다. 이동 컨트롤러의 포인터 공간 내에서 왼쪽 및 오른쪽 이동에 동일하게 적용됩니다. 그 공간 외부에서, 그리고 보기를 제어합니다. 단지 터치하거나 보고자 하는 위치로 카메라를 드래그합니다.

기본 입력 이벤트 인프라 설정

먼저 마우스 및 키보드의 입력 이벤트를 처리하는 데 사용하는 컨트롤 클래스를 생성하고 해당 입력에 따라 카메라 큐브 뷰를 업데이트해야 합니다. 이동-보기 컨트롤을 구현하고 있으므로 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

이제 이동-보기 컨트롤러 및 1인칭 카메라의 상태와 컨트롤을 구현하고, 카메라의 상태를 업데이트하는 기본 메서드 및 이벤트 처리기를 정의하는 헤더를 생성해 보겠습니다.

#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

코드에는 4개의 전용 필드 그룹이 포함되어 있습니다. 각 용도를 검토해 보겠습니다.

먼저 카메라 보기의 업데이트된 정보를 포함하는 몇 가지 유용한 필드를 정의합니다.

  • m_position은 3D 장면에서 장면 좌표를 사용하는 카메라의 위치이므로 뷰 평면입니다.
  • m_pitch는 카메라의 피치 또는 뷰 평면 x-축 기준의 위-아래 회전(라디안 단위)입니다.
  • m_yaw는 카메라의 요 또는 뷰 평면 y-축 기준의 왼쪽-오른쪽 회전(라디안 단위)입니다.

이제 컨트롤러의 상태 및 위치에 대한 정보를 저장하는 데 사용하는 필드를 정의하겠습니다. 먼저 터치 기반 이동 컨트롤러에 필요한 필드를 정의합니다. (이동 컨트롤러의 키보드 구현에 특별히 필요한 사항은 없습니다. 특정 처리기를 사용하여 키보드 이벤트를 읽습니다.)

  • m_moveInUse는 이동 컨트롤러가 사용 중인지 여부를 나타냅니다.
  • m_movePointerID는 현재 이동 포인터의 고유한 ID입니다. 포인터 ID 값을 검사할 때 보기 포인터와 이동 포인터를 구분하는 데 사용합니다.
  • m_moveFirstDown는 플레이어가 처음으로 이동 컨트롤러 포인터 영역을 터치한 화면의 점입니다. 나중에 이 값을 사용하여 작은 움직임이 보기를 방해하지 않도록 데드 존을 설정합니다.
  • m_movePointerPosition는 플레이어가 현재 포인터를 이동한 화면의 점입니다. m_moveFirstDown을 기준으로 이 값을 검사하여 플레이어가 이동하려고 한 방향을 확인하는 데 사용합니다.
  • m_moveCommand는 이동 컨트롤러에 대해 계산된 최종 명령으로 위쪽(앞으로), 아래쪽(뒤로), 왼쪽 또는 오른쪽입니다.

이제 보기 컨트롤러에 사용하는 필드(마우스 및 터치 구현 모두)를 정의합니다.

  • m_lookInUse는 보기 컨트롤이 사용 중인지 여부를 나타냅니다.
  • m_lookPointerID는 현재 보기 포인터의 고유한 ID입니다. 포인터 ID 값을 검사할 때 보기 포인터와 이동 포인터를 구분하는 데 사용합니다.
  • m_lookLastPoint는 이전 프레임에서 캡처된 마지막 점(장면 좌표로 표시)입니다.
  • m_lookLastDelta는 현재 m_positionm_lookLastPoint 간의 계산된 차이입니다.

마지막으로, 6도 이동에 대해 6개의 부울 값을 정의하며, 이는 각 방향 이동 동작의 현재 상태를 나타내는 데 사용합니다(켜기 또는 끄기).

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

6개의 이벤트 처리기를 사용하여 컨트롤러의 상태를 업데이트하는 데 사용하는 입력 데이터를 캡처합니다.

  • OnPointerPressed. 플레이어가 게임 화면에서 포인터를 사용하여 마우스 왼쪽 버튼을 누르거나 화면을 터치했습니다.
  • OnPointerMoved. 플레이어가 게임 화면에서 포인터를 사용하여 마우스를 이동하거나 화면에서 터치 포인터를 끌어 갔습니다.
  • OnPointerReleased. 플레이어가 게임 화면에서 포인터를 사용하여 마우스 왼쪽 버튼을 놓거나 화면을 터치를 중단했습니다.
  • OnKeyDown. 플레이어가 키를 눌렀습니다.
  • OnKeyUp. 플레이어가 키를 놓았습니다.

마지막으로 이러한 메서드와 속성을 사용하여 컨트롤러의 상태 정보를 초기화, 액세스, 업데이트합니다.

  • 초기화. 앱은 이 이벤트 처리기를 호출하여 컨트롤을 초기화하고 표시 창을 설명하는 CoreWindow 개체에 연결합니다.
  • SetPosition. 앱은 이 메서드를 호출하여 장면 공간에서 컨트롤의 좌표(x, y, z)를 설정합니다.
  • SetOrientation. 앱은 이 메서드를 호출하여 카메라의 피치와 요를 설정합니다.
  • get_Position. 앱은 이 속성에 액세스하여 장면 공간에서 카메라의 현재 위치를 가져옵니다. 현재 카메라 위치를 앱으로 전달하는 방법으로 이 속성을 사용합니다.
  • get_LookPoint. 앱은 이 속성에 액세스하여 컨트롤러 카메라가 지향하는 현재 지점을 가져옵니다.
  • 업데이트. 이동 상태를 읽고, 컨트롤러를 보며, 카메라 위치를 업데이트합니다. 앱의 기본 루프에서 이 메서드를 지속적으로 호출하여 카메라 컨트롤러 데이터와 장면 공간의 카메라 위치를 새로 고칩니다.

이제 이동 모양 컨트롤을 구현하는 데 필요한 모든 구성 요소가 여기에 있습니다. 따라서 이를 함께 연결해 보겠습니다.

기본 입력 이벤트 생성

Windows 런타임 이벤트 디스패처는 MoveLookController 클래스의 인스턴스가 처리할 5개의 이벤트를 제공합니다.

이러한 이벤트는 CoreWindow 형식에서 구현됩니다. 사용할 CoreWindow 개체가 있다고 가정합니다. 가져오는 방법을 모르는 경우, DirectX 보기를 표시하도록 UWP(유니버설 Windows 플랫폼) C++ 앱을 설정하는 방법을 참조하세요.

앱이 실행되는 동안 이러한 이벤트가 발생함에 따라 처리기는 전용 필드에 정의된 컨트롤러의 상태 정보를 업데이트합니다.

먼저 마우스 및 터치 포인터 이벤트 처리기를 채우겠습니다. 첫 번째 이벤트 처리기인 OnPointerPressed()에서는 사용자가 마우스를 클릭하거나 보기 컨트롤러 영역에서 화면을 터치할 때 표시를 관리하는 CoreWindow에서 포인터의 x-y 좌표를 가져옵니다.

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

이 이벤트 처리기는 포인터가 마우스가 아닌지(마우스와 터치를 모두 지원하는 이 샘플의 목적) 및 이동 컨트롤러 영역에 있는지 여부를 검사합니다. 두 조건이 모두 충족되면 방금 포인터를 눌렀는지 확인합니다. 특히 m_moveInUse가 false인지 테스트하여 이 클릭이 이전 이동 또는 보기 입력과 관련이 없는지 확인합니다. 그럴 경우 처리기는 누르기 동작이 발생한 이동 컨트롤러 영역에서 점을 캡처하고 m_moveInUse를 true로 설정하여 이 처리기가 다시 호출될 때 이동 컨트롤러 입력 상호 작업의 시작 위치를 덮어쓰지 않도록 합니다. 또한 이동 컨트롤러 포인터 ID를 현재 포인터의 ID로 업데이트합니다.

포인터가 마우스이거나 터치 포인터가 이동 컨트롤러 영역에 없는 경우, 보기 컨트롤러 영역에 있어야 합니다. 사용자가 마우스 버튼을 누르거나 터치하고 누른 현재 위치로 m_lookLastPoint를 설정하고 델타를 재설정하며 보기 컨트롤러의 포인터 ID를 현재 포인터 ID로 업데이트합니다. 또한 보기 컨트롤러의 상태를 활성으로 설정합니다.

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

OnPointerMoved 이벤트 처리기는 포인터가 이동할 때마다 발생합니다(이 경우 터치 스크린 포인터를 끌거나 왼쪽 버튼을 누를 때 마우스 포인터를 이동하는 경우). 포인터 ID가 이동 컨트롤러 포인터의 ID와 같을 경우, 이동 포인터입니다. 그렇지 않으면 활성 포인터인 모양 컨트롤러인지 검사합니다.

이동 컨트롤러인 경우, 포인터 위치만 업데이트합니다. 최종 위치를 OnPointerPressed 이벤트 처리기와 캡처한 첫 번째 위치와 비교하려고 하기 때문에 PointerMoved 이벤트가 계속 실행되는 동안 계속 업데이트합니다.

보기 컨트롤러인 경우, 상황이 좀 더 복잡합니다. 새로운 보기 지점을 계산하고 카메라를 가운데에 배치해야 하기 때문에 마지막 보기 지점과 현재 화면 위치 사이의 델타를 계산한 뒤, 배율 인수에 곱하여 화면 이동 거리를 기준으로 보기 움직임을 작거나 크게 조정할 수 있습니다. 해당 값을 사용하여 피치와 요를 계산합니다.

마지막으로 플레이어가 마우스 이동 또는 화면 터치를 중지할 때 이동 또는 보기 컨트롤러 동작을 비활성화해야 합니다. PointerReleased가 실행될 때 호출하는 OnPointerReleased를 사용하여 m_moveInUse 또는 m_lookInUse를 FALSE로 설정하고 카메라 팬 이동을 해제한 다음 포인터 ID를 0으로 설정합니다.

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

지금까지 모든 터치 스크린 이벤트를 처리했습니다. 이제 키보드 기반 이동 컨트롤러에 대한 키 입력 이벤트를 처리하겠습니다.

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

이러한 키 중 하나를 누르면 이 이벤트 처리기는 해당 방향 이동 상태를 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;
}

그리고 키를 놓으면 이 이벤트 처리기는 키를 false로 다시 설정합니다. 업데이트를 호출할 때 이러한 방향 이동 상태를 검사하고 그에 따라 카메라를 이동합니다. 터치 구현보다 약간 더 간편합니다!

터치 컨트롤 및 컨트롤러 상태 초기화

이제 이벤트를 연결하고 모든 컨트롤러 상태 필드를 초기화하겠습니다.

초기화

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.

}

초기화는 앱의 CoreWindow 인스턴스를 매개 변수로 참조하고 개발한 이벤트 처리기를 해당 CoreWindow의 적절한 이벤트에 등록합니다. 이동 및 보기 포인터의 ID를 초기화하고, 터치 스크린 이동 컨트롤러 구현에 대한 명령 벡터를 0으로 설정하며, 앱이 시작될 때 카메라를 똑바로 바라보도록 설정합니다.

카메라의 위치 및 방향 가져오기 및 설정

뷰포트와 관련하여 카메라의 위치를 가져와서 설정하는 몇 가지 방법을 정의하겠습니다.

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

컨트롤러 상태 정보 업데이트

이제 m_movePointerPosition에서 추적된 포인터 좌표 정보를 표준 좌표계의 월드 좌표 정보로 변환하는 계산을 수행합니다. 앱은 기본 앱 루프를 새로 고칠 때마다 이 메서드를 호출합니다. 따라서 뷰포트로 프로젝션하기 전에 보기 행렬을 업데이트하기 위해 앱에 전달하려는 새로운 보기 지점 위치 정보를 계산합니다.

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

}

플레이어가 터치 기반 이동 컨트롤러를 사용할 때 불안한 움직임을 원하지 않기 때문에 32픽셀 직경의 포인터 주위에 가상 데드 존을 설정합니다. 또한 명령 값과 이동 게인 속도인 속도도 추가합니다. (이 동작을 원하는 대로 조정하여 포인터가 이동 컨트롤러 영역에서 이동하는 거리에 따라 이동 속도를 늦추거나 속도를 높일 수 있습니다.)

또한 속도를 계산할 때 이동에서 받은 좌표를 변환하고 보기 컨트롤러를 장면에 대한 뷰 행렬을 계산하는 메서드로 보내는 실제 보기 지점의 이동으로 변환합니다. 먼저 보기 컨트롤러를 사용하여 왼쪽이나 오른쪽으로 클릭하거나 끌면 카메라가 중앙 축을 기준으로 스윙할 수 있기 때문에 보기 지점이 장면에서 반대 방향으로 회전하기 때문에 x 좌표를 반전합니다. 그런 다음 이동 컨트롤러의 위/아래 키 누르기 또는 터치 끌기 동작(y축 동작으로 읽기)이 화면 내부 또는 외부로 보기 지점을 이동하는 카메라 동작(z축)으로 변환되어야 하므로 y축과 z축을 교환합니다.

플레이어에 대한 보기 지점의 최종 위치는 마지막 위치에 계산된 속도를 더한 위치이며 렌더러에서 get_Position 메서드를 호출할 때(대부분 각 프레임을 설정하는 동안) 읽는 값입니다. 그런 다음 이동 명령을 0으로 다시 설정합니다.

새 카메라 위치로 뷰 행렬 업데이트

카메라에 초점을 맞춘 장면 공간 좌표를 확보할 수 있으며, 앱에 이를 지시할 때마다 업데이트됩니다(예: 기본 앱 루프에서 60초마다). 이 의사 코드는 구현할 수 있는 호출 동작을 제안합니다.

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

축하합니다! 게임에서 터치 스크린과 키보드/마우스 입력 터치 컨트롤 모두에 대한 기본 이동 모양 컨트롤을 구현했습니다!