컨트롤 추가Add controls

Windows 10에서 UWP 앱에 대 한 [ 업데이트 되었습니다.[ Updated for UWP apps on Windows 10. Windows 8.x 문서의 경우 보관 을 참조 하세요 ]For Windows 8.x articles, see the archive ]

좋은 UWP(유니버설 Windows 플랫폼) 게임은 다양한 인터페이스를 지원합니다.A good Universal Windows Platform (UWP) game supports a wide variety of interfaces. 잠재적 플레이어는 물리적 단추가 없는 태블릿에 Windows 10을 포함 하거나, Xbox 컨트롤러가 연결 된 PC 또는 고성능 마우스 및 게임 키보드를 사용 하는 최신 데스크톱 게임 rig를 사용할 수 있습니다.A potential player might have Windows 10 on a tablet with no physical buttons, a PC with an Xbox controller attached, or the latest desktop gaming rig with a high-performance mouse and gaming keyboard. 게임에서 컨트롤은 MoveLookController 클래스에 구현되어 있습니다.In our game the controls are implemented in the MoveLookController class. 이 클래스는 세 가지 유형의 입력(마우스 및 키보드, 터치, 게임 패드) 모두를 단일 컨트롤러에 집계합니다.This class aggregates all three types of input (mouse and keyboard, touch, and gamepad) into a single controller. 그 결과로 1인칭 슈팅 게임에서 여러 디바이스에서 작동하는 장르 표준 이동 - 보기 컨트롤러를 사용할 수 있게 되었습니다.The end result is a first-person shooter that uses genre standard move-look controls that work with multiple devices.

참고

컨트롤에 대한 자세한 내용은 게임용 이동 - 보기 컨트롤게임용 터치 컨트롤을 참조하세요.For more info about controls, see Move-look controls for games and Touch controls for games.

목표Objective

이제 게임이 렌더링되고 있지만, 플레이어를 이동시키거나 대상에게 슈팅할 수는 없습니다.At this point we have a game that renders, but we can't move our player around or shoot the targets. UWP DirectX 게임에서 다음과 같은 유형의 입력에 대해 1인칭 슈팅 게임 이동 - 보기 컨트롤을 구현하는 방법에 대해 살펴보겠습니다.We'll take a look at how our game implements first person shooter move-look controls for the following types of input in our UWP DirectX game.

  • 마우스 및 키보드Mouse and keyboard
  • 터치Touch
  • 게임 패드Gamepad

참고

이 샘플의 최신 게임 코드를 다운로드하지 않은 경우 Direct3D 게임 샘플로 이동합니다.If you haven't downloaded the latest game code for this sample, go to Direct3D game sample. 이 샘플은 UWP 기능 샘플의 큰 컬렉션의 일부입니다.This sample is part of a large collection of UWP feature samples. 샘플을 다운로드하는 방법에 대한 지침은 GitHub에서 UWP 샘플 가져오기를 참조하세요.For instructions on how to download the sample, see Get the UWP samples from GitHub.

공용 컨트롤 동작Common control behaviors

터치 컨트롤 및 마우스/키보드 컨트롤의 핵심 구현은 매우 유사합니다.Touch controls and mouse/keyboard controls have a very similar core implementation. UWP 앱에서 포인터는 화면에 표시되는 점일 뿐입니다.In a UWP app, a pointer is simply a point on the screen. 마우스를 밀거나 터치 스크린에서 손가락을 밀어 이동할 수 있습니다.You can move it by sliding the mouse or sliding your finger on the touch screen. 따라서 단일 이벤트 집합을 등록할 수 있으며 플레이어가 포인터를 이동하고 누르는 데 마우스를 사용하는지 터치 스크린을 사용하는지에 대해 걱정하지 않아도 됩니다.As a result, you can register for a single set of events, and not worry about whether the player is using a mouse or a touch screen to move and press the pointer.

게임 샘플의 MoveLookController 클래스를 초기화하면 4개의 포인터 관련 이벤트와 1개의 마우스 관련 이벤트가 등록됩니다.When the MoveLookController class in the game sample is initialized, it registers for four pointer-specific events and one mouse-specific event:

이벤트Event 설명Description
CoreWindow::P ointerPressed 있습니다. CoreWindow::PointerPressed 마우스 왼쪽 또는 오른쪽 단추를 누르고 있거나 터치 표면을 터치했습니다.The left or right mouse button was pressed (and held), or the touch surface was touched.
CoreWindow::P ointerMoved 됨CoreWindow::PointerMoved 마우스를 이동하거나 터치 표면에서 끌기 작업을 수행했습니다.The mouse moved, or a drag action was made on the touch surface.
CoreWindow::P ointerReleasedCoreWindow::PointerReleased 마우스 왼쪽 단추를 놓았거나 터치 표면에 닿는 개체를 들어 올렸습니다.The left mouse button was released, or the object contacting the touch surface was lifted.
CoreWindow::P ointerExitedCoreWindow::PointerExited 포인터를 주 창 밖으로 이동했습니다.The pointer moved out of the main window.
Windows::D evices:: Input:: MouseMovedWindows::Devices::Input::MouseMoved 마우스를 특정 거리만큼 이동했습니다.The mouse moved a certain distance. 마우스 이동 델타 값에만 관심이 있고 현재 X-Y 위치는 표시하지 않습니다.Be aware that we are only interested in mouse movement delta values, and not the current X-Y position.

이러한 이벤트 처리기는 응용 프로그램 창에서 MoveLookController가 초기화되는 즉시 사용자 입력을 수신하기 시작하도록 설정되어 있습니다.These event handlers are set to start listening for user input as soon as the MoveLookController is initialized in the application window.

void MoveLookController::InitWindow(_In_ CoreWindow^ window)
{
    ResetState();

    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->PointerExited +=
        ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &MoveLookController::OnPointerExited);

    // There is a separate handler for mouse only relative mouse movement events.
    MouseDevice::GetForCurrentView()->MouseMoved +=
        ref new TypedEventHandler<MouseDevice^, MouseEventArgs^>(this, &MoveLookController::OnMouseMoved);
    //
    // ...
    //
}

InitWindow에 대한 전체 코드는 GitHub에서 확인할 수 있습니다.Complete code for InitWindow can be seen on GitHub.

게임이 특정 입력을 반드시 수신해야 하는 경우를 판단할 수 있도록 MoveLookController 클래스는 컨트롤러 유형에 관계 없이 3개의 컨트롤러 관련 상태를 가집니다.To determine when the game should be listening for certain input, the MoveLookController class has three controller-specific states, regardless of the controller type:

StateState 설명Description
없음None 컨트롤러의 초기화된 상태입니다.This is the initialized state for the controller. 게임에서 어떤 컨트롤러 입력도 예상되지 않기 때문에 모든 입력이 무시됩니다.All input is ignored since the game is not anticipating any controller input.
WaitForInputWaitForInput 컨트롤러는 왼쪽 마우스 클릭, 터치 이벤트 또는 게임 패드의 메뉴 단추를 사용하여 플레이어가 게임에서 메시지를 승인하는 동안 기다립니다.The controller is waiting for the player to acknowledge a message from the game by either using a left mouse click, a touch event, ot the menu button on a gamepad.
활성Active 컨트롤러는 활성 게임 플레이 모드에 있습니다.The controller is in active game play mode.

WaitForInput 상태와 게임 일시 중지WaitForInput state and pausing the game

일시 중지되면 게임은 WaitForInput 상태가 됩니다.The game enters the WaitForInput state when the game has been paused. 플레이어가 게임의 주 창 밖에서 포인터를 이동시키거나 일시 중지 단추(P 키나 게임 패드의 시작 단추)를 누를 때 이러한 일시 중지가 발생합니다.This happens when the player moves the pointer outside the main window of the game, or presses the pause button (the P key or the gamepad Start button). MoveLookController는 이러한 단추 누름을 등록하고 IsPauseRequested 메서드가 호출될 때 게임 루프에 알립니다.The MoveLookController registers the press, and informs the game loop when it calls the IsPauseRequested method. 이때 IsPauseRequested에서 true를 반환하면 게임 루프에서 MoveLookControllerWaitForPress를 호출하여 컨트롤러를 WaitForInput 상태로 전환합니다.At that point if IsPauseRequested returns true, the game loop then calls WaitForPress on the MoveLookController to move the controller into the WaitForInput state.

WaitForInput 상태가 되면 활성 상태로 되돌아갈 때까지 게임에서 거의 모든 게임 플레이 입력 이벤트의 처리가 중지됩니다.Once in the WaitForInput state, the game stops processing almost all gameplay input events until it returns to the Active state. 예외는 일시 중지 단추인데, 이 단추를 누르면 게임이 활성 상태로 되돌아갈 수 있습니다.The exception is the pause button, with a press of this causing the game to go back to the active state. 게임을 활성 상태로 되돌리기 위해 플레이어에서 메뉴 항목을 선택 해야 하는 일시 중지 단추를 제외 합니다.Other than the pause button, in order for the game to go back to the Active state the player needs to select a menu item.

활성 상태The Active state

활성 상태 동안에는 MoveLookController 인스턴스가 활성화된 모든 입력 장치에서 이벤트를 처리하고 플레이어의 의도를 해석합니다.During the Active state, the MoveLookController instance is processing events from all enabled input devices and interpreting the player's intentions. 그 결과로, 플레이어 보기에서 속도 및 보기 방향을 업데이트하고 게임 루프에서 업데이트가 호출된 후 업데이트된 데이터를 게임과 공유합니다.As a result, it updates the velocity and look direction of the player's view and shares the updated data with the game after Update is called from the game loop.

모든 포인터 입력은 활성 상태에서 추적되며 포인터 작업에 따라 서로 다른 포인터 ID를 사용합니다.All pointer input is tracked in the Active state, with different pointer IDs corresponding to different pointer actions. PointerPressed 이벤트를 받으면 MoveLookController는 창에서 만든 포인터 ID 값을 가져옵니다.When a PointerPressed event is received, the MoveLookController obtains the pointer ID value created by the window. 포인터 ID는 특정 유형의 입력을 나타냅니다.The pointer ID represents a specific type of input. 예를 들어 멀티 터치 장치에는 여러 가지 다른 활성 입력이 동시에 있을 수 있습니다.For example, on a multi-touch device, there may be several different active inputs at the same time. 이 ID는 플레이어가 사용하는 입력을 추적하는 데 사용됩니다.The IDs are used to keep track of which input the player is using. 한 이벤트가 터치 스크린의 이동 사각형에 있으면 포인터 ID가 할당되어 이동 사각형의 모든 포인터 이벤트를 추적합니다.If one event is in the move rectangle of the touch screen, a pointer ID is assigned to track any pointer events in the move rectangle. 실행 사각형의 다른 포인터 이벤트는 별도의 포인터 ID를 사용하여 별도로 추적됩니다.Other pointer events in the fire rectangle are tracked separately, with a separate pointer ID.

참고

마우스에서의 입력과 게임 패드의 오른쪽 섬스틱에서의 입력은 별도로 처리되는 ID를 갖습니다.Input from the mouse and right thumbstick of a gamepad also have IDs that are handled separately.

포인터 이벤트를 특정 게임 작업에 매핑했으면 이제 MoveLookController 개체가 주 게임 루프와 공유하는 데이터를 업데이트합니다.After the pointer events have been mapped to a specific game action, it's time to update the data the MoveLookController object shares with the main game loop.

Game 샘플의 Update 메서드는 호출 되 면 입력을 처리 하 고 속도 및 모양 변수 (m_속도m_lookdirection)를 업데이트 합니다. 그러면 게임 루프가 public 속도lookdirection 메서드를 호출 하 여 검색 합니다.When called, the Update method in the game sample processes the input and updates the velocity and look direction variables (m_velocity and m_lookdirection), which the game loop then retrieves by calling the public Velocity and LookDirection methods.

참고

Update 메서드에 대한 자세한 내용은 이 페이지 뒷부분에서 확인할 수 있습니다.More details about the Update method can be seen later on this page.

게임 루프는 MoveLookController 인스턴스에서 IsFiring 메서드를 호출하여 플레이어가 실행 중인지 여부를 알아보기 위한 테스트를 할 수 있습니다.The game loop can test to see if the player is firing by calling the IsFiring method on the MoveLookController instance. MoveLookController는 플레이어가 세 개의 입력 유형 중 하나에서 실행 단추를 눌렀는지 확인합니다.The MoveLookController checks to see if the player has pressed the fire button on one of the three input types.

bool MoveLookController::IsFiring()
{
    if (m_state == MoveLookControllerState::Active)
    {
        if (m_autoFire)
        {
            return (m_fireInUse || (m_mouseInUse && m_mouseLeftInUse) || PollingFireInUse());
        }
        else
        {
            if (m_firePressed)
            {
                m_firePressed = false;
                return true;
            }
        }
    }
    return false;
}

이제 세 가지 컨트롤 유형의 구현에 대해 좀더 자세히 살펴보겠습니다.Now, let's look at the implementation of each of the three control types in a little more detail.

상대 마우스 컨트롤 추가Adding relative mouse controls

마우스 이동이 감지되면 해당 이동을 사용하여 카메라의 새 피치와 요를 결정할 수 있습니다.If mouse movement is detected, we want to use that movement to determine the new pitch and yaw of the camera. 이를 위해 상대 마우스 컨트롤을 구현합니다. 여기서는 이동의 절대 x-y 픽셀 좌표 기록과 반대로 마우스가 이동한 상태 거리(이동의 시작과 정지 사이의 델타)를 처리합니다.We do that by implementing relative mouse controls, where we handle the relative distance the mouse has moved—the delta between the start of the movement and the stop—as opposed to recording the absolute x-y pixel coordinates of the motion.

이를 구현하기 위해 MouseMoved 이벤트에서 반환된 Windows::Device::Input::MouseEventArgs::MouseDelta 인수 개체의 MouseDelta::XMouseDelta::Y 필드를 검토하여 X(가로 이동) 및 Y(세로 이동) 좌표의 변경을 확인합니다.To do that, we obtain the changes in the X (the horizontal motion) and the Y (the vertical motion) coordinates by examining the MouseDelta::X and MouseDelta::Y fields on the Windows::Device::Input::MouseEventArgs::MouseDelta argument object returned by the MouseMoved event.

void MoveLookController::OnMouseMoved(
    _In_ MouseDevice^ /* mouseDevice */,
    _In_ MouseEventArgs^ args
    )
{
    // Handle Mouse Input via dedicated relative movement handler.

    switch (m_state)
    {
    case MoveLookControllerState::Active:
        XMFLOAT2 mouseDelta;
        mouseDelta.x = static_cast<float>(args->MouseDelta.X);
        mouseDelta.y = static_cast<float>(args->MouseDelta.Y);

        XMFLOAT2 rotationDelta;
        rotationDelta.x  = mouseDelta.x * MoveLookConstants::RotationGain;   // scale for control sensitivity
        rotationDelta.y  = mouseDelta.y * MoveLookConstants::RotationGain;

        // Update our orientation based on the command.
        m_pitch -= rotationDelta.y;
        m_yaw   += rotationDelta.x;

        // Limit pitch to straight up or straight down.
        float limit = XM_PI / 2.0f - 0.01f;
        m_pitch = __max(-limit, m_pitch);
        m_pitch = __min(+limit, m_pitch);

        // Keep longitude in same range by wrapping.
        if (m_yaw >  XM_PI)
        {
            m_yaw -= XM_PI * 2.0f;
        }
        else if (m_yaw < -XM_PI)
        {
            m_yaw += XM_PI * 2.0f;
        }
        break;
    }
}

터치 지원 추가Adding touch support

터치 컨트롤은 태블릿에서 사용자를 지원하기에 적합합니다.Touch controls are great for supporting users with tablets. 이 게임은 특정한 게임 내 작업에 맞게 각각을 정렬하면서 화면의 특정 영역에 대한 영역 지정을 해제하여 터치 입력을 수집합니다.This game gathers touch input by zoning off certain areas of the screen with each aligning to specific in-game actions. 이 게임의 터치 입력은 세 가지 영역을 사용합니다.This game's touch input uses three zones.

이동 - 보기 터치 레이아웃

다음 명령에는 터치 컨트롤 동작이 요약되어 있습니다.The following commands summarize our touch control behavior. 사용자 입력User input | 작업Action :------- | :-------- 이동 사각형Move rectangle | 터치 입력은 가상 조이스틱으로 변환되는데, 세로 이동은 전방/후방 위치 동작으로, 가로 이동은 왼쪽/오른쪽 위치 동작으로 해석됩니다.Touch input is converted into a virtual joystick where the vertical motion will be translated into forward/backward position motion and horizontal motion will be translated into left/right position motion. 실행 사각형Fire rectangle | 구를 실행합니다.Fire a sphere. 이동 및 실행 사각형 밖을 터치Touch outside of move and fire rectangle | 카메라 보기의 회전(피치 및 요)을 변경Change the rotation (the pitch and yaw) of the camera view.

MoveLookController는 포인터 ID를 검사하여 이벤트가 발생한 위치를 확인하고 다음 작업 중 하나를 수행합니다.The MoveLookController checks the pointer ID to determine where the event occurred, and takes one of the following actions:

  • PointerMoved 이벤트가이동 또는 발생 사각형에서 발생한 경우 컨트롤러의 포인터 위치를 업데이트합니다.If the PointerMoved event occurred in the move or fire rectangle, update the pointer position for the controller.
  • PointerMoved 이벤트가 화면의 나머지 영역에서 발생한 경우(보기 컨트롤로 정의됨) 보기 방향 벡터의 피치와 요 변화를 계산합니다.If the PointerMoved event occurred somewhere in the rest of the screen (defined as the look controls), calculate the change in pitch and yaw of the look direction vector.

일단 터치 스크롤이 구현되면 이전에 Direct2D를 사용해 그렸던 사각형이 이동, 실행 및 모양 영역이 어디인지를 플레이어에게 나타냅니다.Once we've implemented our touch controls, the rectangles we drew earlier using Direct2D will indicate to players where the move, fire, and look zones are.

터치 컨트롤

이제 각 컨트롤을 구현하는 방법을 살펴보겠습니다.Now let's take a look at how we implement each control.

이동 및 실행 컨트롤러Move and fire controller

화면의 왼쪽 아래 사분면에 있는 이동 컨트롤러 사각형은 방향 패드로 사용됩니다.The move controller rectangle in the lower left quadrant of the screen is used as a directional pad. 이 공간의 왼쪽과 오른쪽으로 엄지 손가락을 밀면 플레이어가 왼쪽과 오른쪽으로 이동하고 위쪽과 아래쪽으로 밀면 카메라가 앞뒤로 이동합니다.Sliding your thumb left and right within this space moves the player left and right, while up and down moves the camera forward and backward. 이 설정 후 화면의 왼쪽 아래 사분면에 있는 컨트롤러를 터치하면 구가 실행됩니다.After setting this up, tapping the fire controller in the lower right quadrant of the screen fires a sphere.

SetMoveRectSetFireRect 메서드는 2개의 2D 벡터를 가져와서 화면에서 각 사각형의 왼쪽 위와 오른쪽 아래 모서리 위치를 지정하여 입력 사각형을 만듭니다.The SetMoveRect and SetFireRect methods create our input rectangles, taking two, 2D vectors to specify each rectangles' upper left and lower right corner positions on the screen.

그러면 사용자가 사각형의 내에서 접촉 하 고 있는지 여부를 확인 하는 데 도움이 되는 m_fireUpperLeftm_fireLowerRight 에 매개 변수가 할당 됩니다.The parameters are then assigned to m_fireUpperLeft and m_fireLowerRight that will help us determine if the user is touching within on of the rectangles.

m_fireUpperLeft  = upperLeft;
m_fireLowerRight = lowerRight;

화면 크기를 조정하는 경우 이러한 사각형은 적정 크기로 다시 그려집니다.If the screen is resized, these rectangles are redrawn to the approperiate size.

컨트롤에 대한 영역 지정이 해제되면 사용자가 실제로 이를 사용하는 시기를 결정합니다.Now that we've zoned off our controls, it's time to determine when a user is actually using them. 이를 위해 사용자가 포인터를 누르거나 이동하거나 해제하는 경우에 대해 MoveLookController::InitWindow 메서드에서 몇 가지 이벤트 처리기를 설정합니다.To do this, we set up some event handlers in the MoveLookController::InitWindow method for when the user presses, moves, or releases their pointer.

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

OnPointerPressed 메서드를 사용하여 이동 또는 실행 사각형 안을 사용자가 처음으로 누르면 어떻게 되는지를 가장 먼저 확인합니다.We'll first determine what happens when the user first presses within the move or fire rectangles using the OnPointerPressed method. 여기에서 컨트롤 터치 영역과 포인터가 해당 컨트롤러에 이미 있는지 여부를 확인합니다.Here we check where they're touching a control and if a pointer is already in that controller. 손가락으로 특정 컨트롤을 터치하는 것이 처음이라면 다음과 같이 합니다.If this is the first finger to touch the specific control, we do the following.

  • System.windows.uielement.touchdown>의 위치를 m_moveFirstDown 또는 m_fireFirstDown 에 2d 벡터로 저장 합니다.Store the location of the touchdown in m_moveFirstDown or m_fireFirstDown as a 2D vector.
  • M_movePointerID 또는 m_FIREPOINTERID에 포인터 ID를 할당 합니다.Assign the pointer ID to m_movePointerID or m_firePointerID.
  • 이제 해당 컨트롤에 대 한 활성 포인터가 있으므로 적절 한 여유 주소 플래그 (m_moveInUse 또는 m_fireInUse)를 true로 설정 합니다.Set the proper InUse flag (m_moveInUse or m_fireInUse) to true since we now have an active pointer for that control.
    PointerPoint^ point = args->CurrentPoint;
    uint32 pointerID = point->PointerId;
    Point pointerPosition = point->Position;
    PointerPointProperties^ pointProperties = point->Properties;
    auto pointerDevice = point->PointerDevice;
    auto pointerDeviceType = pointerDevice->PointerDeviceType;

    XMFLOAT2 position = XMFLOAT2(pointerPosition.X, pointerPosition.Y);

    case MoveLookControllerState::Active:
        switch (pointerDeviceType)
        {
        case Windows::Devices::Input::PointerDeviceType::Touch:
            // Check to see if this pointer is in the move control.
            if (position.x > m_moveUpperLeft.x &&
                position.x < m_moveLowerRight.x &&
                position.y > m_moveUpperLeft.y &&
                position.y < m_moveLowerRight.y)
            {
                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_movePointerID = pointerID;                // Store the pointer using this control
                    m_moveInUse = true;                         // Set InUse flag to signal there is an active move pointer
                }
            }
            // Check to see if this pointer is in the fire control.
            else if (position.x > m_fireUpperLeft.x &&
                position.x < m_fireLowerRight.x &&
                position.y > m_fireUpperLeft.y &&
                position.y < m_fireLowerRight.y)
            {
                if (!m_fireInUse)
                {
                    m_fireLastPoint = position;     // Save the location of the initial contact
                    m_firePointerID = pointerID;    // Store the pointer using this control
                    m_fireInUse = true;             // Set InUse flag to signal there is an active fire pointer
                }
            }
            ...

사용자가 이동 컨트롤을 터치하고 있는지 실행 컨트롤을 터치하고 있는지 판단했다면 이제는 플레이어가 손가락을 눌러 이동을 하고 있는지 확인합니다.Now that we've determined whether the user is touching a move or fire control, we see if the player is making any movements with their pressed finger. MoveLookController::OnPointerMoved 메서드를 사용하여 어떤 포인터가 이동되었는지 확인하고 새 위치를 2D 벡터로 저장합니다.Using the MoveLookController::OnPointerMoved method, we check what pointer has moved and then store its new position as a 2D vector.

    PointerPoint^ point = args->CurrentPoint;
    uint32 pointerID = point->PointerId;
    Point pointerPosition = point->Position;
    PointerPointProperties^ pointProperties = point->Properties;
    auto pointerDevice = point->PointerDevice;

    XMFLOAT2 position = XMFLOAT2(pointerPosition.X, pointerPosition.Y);     // convert to allow math
    
    switch (m_state)
    {
    case MoveLookControllerState::Active:
        // Decide which control this pointer is operating.
        
        // Move control
        if (pointerID == m_movePointerID)
        {
            m_movePointerPosition = position;       // Save the current position.
        }
        // Look control
        else if (pointerID == m_lookPointerID)
        {
            // ...
        }
        // Fire control
        else if (pointerID == m_firePointerID)
        {
            m_fireLastPoint = position;
        }

사용자가 컨트롤 내에서 제스처를 하면 포인터가 해제됩니다.Once the user has made their gestures within the controls, they'll release the pointer. MoveLookController::OnPointerReleased 메서드를 사용하여 어떤 포인터가 해제되었는지 확인하고 일련의 재설정을 수행합니다.Using the MoveLookController::OnPointerReleased method, we determine which pointer has been released and do a series of resets.

이동 컨트롤이 해제된 경우에는 다음을 수행합니다.If the move control has been released, we do the following.

  • 게임 시 이동되는 일이 없도록 모든 방향에서 플레이어의 속도를 0으로 설정합니다.Set the velocity of the player to 0 in all directions to prevent them from moving in the game.
  • 사용자가 더 이상 이동 컨트롤러에 접촉 하지 않으므로 m_moveInUsefalse로 전환 합니다.Switch m_moveInUse to false since the user is no longer touching the move controller.
  • 이동 컨트롤러에 더 이상 포인터가 없기 때문에 이동 포인터 ID를 0으로 설정합니다.Set the move pointer ID to 0 since there's no longer a pointer in the move controller.
       if (pointerID == m_movePointerID)
        {
            m_velocity = XMFLOAT3(0, 0, 0);      // Stop on release.
            m_moveInUse = false;
            m_movePointerID = 0;
        }

실행 컨트롤이 해제된 경우에는 실행 컨트롤에 더 이상 포인터가 없기 때문에 m_fireInUse 플래그를 false로, 실행 포인터 ID를 0으로 전환하기만 하면 됩니다.For the fire control, if it has been released all we do is switch the m_fireInUse flag to false and the fire pointer ID to 0 since there's no longer a pointer in the fire control.

        else if (pointerID == m_firePointerID)
        {
            m_fireInUse = false;
            m_firePointerID = 0;
        }

보기 컨트롤러Look controller

화면에서 사용되지 않은 영역에 대한 터치 디바이스 포인터 이벤트는 보기 컨트롤러로 처리됩니다.We treat touch device pointer events for the unused regions of the screen as the look controller. 이 영역 주위를 손가락을 밀어 플레이어 카메라의 피치와 요(회전)를 변경합니다.Sliding your finger around this zone changes the pitch and yaw (rotation) of the player camera.

MoveLookController::OnPointerPressed 이벤트가 이 영역의 터치 디바이스에서 발생하고 게임 상태가 활성으로 설정이 되어 있으면 포인터 ID가 할당됩니다.If the MoveLookController::OnPointerPressed event is raised on a touch device in this region and the game state is set to Active, it's assigned a pointer ID.

    if (!m_lookInUse)   // If no pointer is in this control yet:
    {
        m_lookLastPoint = position;                   // Save the pointer for a later move.
        m_lookPointerID = pointerID;                  // Store the pointer using this control.
        m_lookLastDelta.x = m_lookLastDelta.y = 0;    // These are for smoothing.
        m_lookInUse = true;
    }

GitHub에서 MoveLookController::OnPointerPressed 메서드에 대한 전체 코드를 확인할 수 있습니다.You can see the complete code for the MoveLookController::OnPointerPressed method on GitHub.

여기에서 MoveLookController는 이벤트를 실행한 포인터의 포인터 ID를 보기 영역에 해당되는 특정 변수에 할당합니다.Here the MoveLookController assigns the pointer ID for the pointer that fired the event to a specific variable that corresponds to the look region. 모양 영역에서 터치가 발생 하는 경우 m_lookPointerID 변수는 이벤트를 발생 시킨 포인터 ID로 설정 됩니다.In the case of a touch occurring in the look region, the m_lookPointerID variable is set to the pointer ID that fired the event. 부울 변수인 m_lookInUse는 컨트롤이 아직 해제 되지 않았음을 나타내기 위해 설정 됩니다.A boolean variable, m_lookInUse, is also set to indicate that the control has not yet been released.

이제 게임 샘플이 PointerMoved 터치 스크린 이벤트를 처리하는 방법에 대해 살펴보겠습니다.Now, let's look at how the game sample handles the PointerMoved touch screen event.

MoveLookController::OnPointerMoved 메서드 내에서 어떤 종류의 포인터 ID가 이벤트에 할당되었는지 확인합니다.Within the MoveLookController::OnPointerMoved method, we check to see what kind of pointer ID has been assigned to the event. m_lookPointerID의 경우에는 포인터의 위치에서 변경을 계산합니다.If it's m_lookPointerID, we calculate the change in position of the pointer. 그런 다음, 이 델타 값을 사용하여 회전을 어느 정도 변경해야 하는지를 계산합니다.We then use this delta to calculate how much the rotation should change. 마지막으로 게임에서 플레이어 회전을 변경 하는 데 사용 되는 m_피치m_요 를 업데이트할 수 있습니다.Finally we're at a point where we can update the m_pitch and m_yaw to be used in the game to change the player rotation.

    else if (pointerID == m_lookPointerID)     // This is the look pointer.
    {
        // Look control
        XMFLOAT2 pointerDelta;
        pointerDelta.x = position.x - m_lookLastPoint.x;        // How far did the pointer move.
        pointerDelta.y = position.y - m_lookLastPoint.y;

        XMFLOAT2 rotationDelta;
        rotationDelta.x = pointerDelta.x * MoveLookConstants::RotationGain;       // Scale for control sensitivity.
        rotationDelta.y = pointerDelta.y * MoveLookConstants::RotationGain;
        m_lookLastPoint = position;                             // Save for the next time through.

        // Update our orientation based on the command.
        m_pitch -= rotationDelta.y;
        m_yaw   += rotationDelta.x;

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

마지막으로 게임 샘플이 PointerReleased 터치 스크린 이벤트를 어떻게 처리하는지 살펴보겠습니다.The last piece we'll look at is how the game sample handles the PointerReleased touch screen event. 사용자가 터치 제스처를 완료하고 화면에서 손을 떼면 MoveLookController::OnPointerReleased가 시작됩니다.Once the user has finished the touch gesture and removed their finger from the screen, MoveLookController::OnPointerReleased is initiated. PointerReleased 이벤트를 실행한 포인터의 ID가 이전에 기록된 이동 포인터의 ID이면 플레이어가 보기 영역에 대한 터치를 중지한 것이기 때문에 MoveLookController에서 속도가 0으로 설정됩니다.If the ID of the pointer that fired the PointerReleased event is the ID of the previously recorded move pointer, the MoveLookController sets the velocity to 0 because the player has stopped touching the look area.

    else if (pointerID == m_lookPointerID)
    {
        m_lookInUse = false;
        m_lookPointerID = 0;
    }

마우스 및 키보드 지원 추가Adding mouse and keyboard support

이 게임은 키보드 및 마우스에 대해 다음과 같은 컨트롤 레이아웃을 가지고 있습니다.This game has the following control layout for keyboard and mouse.

사용자 입력User input 작업Action
WW 플레이어를 앞으로 이동Move player forward
세 번째 유형은A 플레이어를 왼쪽으로 이동Move player left
SS 플레이어를 뒤로 이동Move player backward
DD 플레이어를 오른쪽으로 이동Move player right
XX 보기를 위로 이동Move view up
스페이스바Space bar 보기를 아래로 이동Move view down
PP 게임을 일시 중지Pause the game
마우스 이동Mouse movement 카메라 보기의 회전(피치 및 요) 변경Change the rotation (the pitch and yaw) of the camera view
왼쪽 마우스 단추Left mouse button 구 실행Fire a sphere

키보드를 사용하기 위해 게임 샘플은 MoveLookController::InitWindow 메서드 내에 2개의 새로운 이벤트(CoreWindow::KeyUpCoreWindow::KeyDown)를 등록합니다.To use the keyboard, the game sample registers two new events, CoreWindow::KeyUp and CoreWindow::KeyDown, within the MoveLookController::InitWindow method. 이러한 이벤트는 키 누름과 해제를 처리합니다.These events handle the press and release of a key.

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

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

마우스는 포인터를 사용하기는 하지만 터치 스크롤과 약간 다르게 처리됩니다.The mouse is treated a little differently from the touch controls even though it uses a pointer. 컨트롤 레이아웃에 맞춰지도록 MoveLookController는 마우스가 이동될 때마다 카메라를 회전시키고 왼쪽 마우스 단추를 누를 때 이벤트를 실행합니다.To align with our control layout, the MoveLookController rotates the camera whenever the mouse is moved, and fires when the left mouse button is pressed.

이는 MoveLookControllerOnPointerPressed 메서드에서 처리됩니다.This is handled in the OnPointerPressed method of the MoveLookController.

이 메서드에서 Windows::Devices::Input::PointerDeviceType 열거형 값에서 어떤 종류의 포인터 디바이스가 사용되고 있는지 확인합니다.In this method we check to see what type of pointer device is being used with the Windows::Devices::Input::PointerDeviceType enum. 게임이 활성 상태이고 PointerDeviceType터치가 아니면 이를 마우스 입력으로 가정합니다.If the game is Active and the PointerDeviceType isn't Touch, we assume it's mouse input.

    case MoveLookControllerState::Active:
        switch (pointerDeviceType)
        {
        case Windows::Devices::Input::PointerDeviceType::Touch:
            // Behavior for touch controls
        
        default:
        // Behavior for mouse controls
            bool rightButton = pointProperties->IsRightButtonPressed;
            bool leftButton = pointProperties->IsLeftButtonPressed;

            if (!m_autoFire && (!m_mouseLeftInUse && leftButton))
            {
                m_firePressed = true;
            }

            if (!m_mouseInUse)
            {
                m_mouseInUse = true;
                m_mouseLastPoint = position;
                m_mousePointerID = pointerID;
                m_mouseLeftInUse = leftButton;
                m_mouseRightInUse = rightButton;
                m_lookLastDelta.x = m_lookLastDelta.y = 0;          // These are for smoothing.
            }
            break;
        }

플레이어가 마우스 단추 중 하나를 누르는 것을 중지하면 CoreWindow::PointerReleased 마우스 이벤트가 발생하면서 MoveLookController::OnPointerReleased 메서드가 호출되고 입력이 완료됩니다.When the player stops pressing one of the mouse buttons, the CoreWindow::PointerReleased mouse event is raised, calling the MoveLookController::OnPointerReleased method, and the input is complete. 이 때 왼쪽 마우스 단추를 누르고 있다가 놓으면 구의 실행이 중지됩니다.At this point, spheres will stop firing if the left mouse button was being pressed and is now released. 보기 이벤트는 항상 활성화되어 있기 때문에 게임에서 동일한 마우스 포인터를 사용하여 진행 중인 보기 이벤트를 추적할 수 있습니다.Because look is always enabled, the game continues to use the same mouse pointer to track the ongoing look events.

    case MoveLookControllerState::Active:
        // Touch points
        if (pointerID == m_movePointerID)
        {
            // Stop movement
        }
        else if (pointerID == m_lookPointerID)
        {
            // Stop look rotation
        }
        // Fire button has been released
        else if (pointerID == m_firePointerID)
        {
            // Stop firing
        }
        // Mouse point
        else if (pointerID == m_mousePointerID)
        {
            bool rightButton = pointProperties->IsRightButtonPressed;
            bool leftButton = pointProperties->IsLeftButtonPressed;

            // Mouse no longer in use so stop firing
            m_mouseInUse = false;

            // Don't clear the mouse pointer ID so that Move events still result in Look changes.
            // m_mousePointerID = 0;
            m_mouseLeftInUse = leftButton;
            m_mouseRightInUse = rightButton;
        }
        break;
    }
}

이제 마지막 지원 컨트롤 유형인 게임 패드에 대해 살펴보겠습니다.Now let's look at the last control type we'll be supporting: gamepads. 게임 패드는 포인터 개체를 사용하지 않기 때문에 터치 및 마우스 컨트롤과 별개로 처리됩니다.Gamepads are handled separately from the touch and mouse controls since they doesn't use the pointer object. 이 때문에 몇 가지 이벤트 처리기와 메서드를 새로 추가해야 합니다.Because of this, a few new event handlers and methods will need to be added.

게임 패드 지원 추가Adding gamepad support

이 게임에서는 Windows.Gaming.Input API 호출을 통해 게임 패드 지원이 추가됩니다.For this game, gamepad support is added by calls to the Windows.Gaming.Input APIs. 이 API 집합은 레이싱 휠, 플라이트 스틱 같은 게임 컨트롤러 입력에 액세스할 수 있도록 해줍니다.This set of APIs provides access to game controller inputs like racing wheels and flight sticks.

게임 패드 컨트롤은 다음과 같습니다.The following will be our gamepad controls.

사용자 입력User input 작업Action
왼쪽 아날로그 스틱Left analog stick 플레이어를 이동Move player
오른쪽 아날로그 스틱Right analog stick 카메라 보기의 회전(피치 및 요) 변경Change the rotation (the pitch and yaw) of the camera view
오른쪽 트리거Right trigger 구 실행Fire a sphere
시작/메뉴 단추Start/Menu button 게임 일시 중지/재개Pause or resume the game

InitWindow 메서드에서 게임 패드가 추가 상태인지 제거 상태인지 판단하기 위해 2개의 이벤트가 새로 추가되었습니다.In the InitWindow method, we add two new events to determine if a gamepad has been added or removed. 이러한 이벤트는 m_gamepadsChanged 속성을 업데이트합니다.These events update the m_gamepadsChanged property. UpdatePollingDevices 메서드에서는 알려진 gamepads 목록이 변경 되었는지 여부를 확인 하는 데 사용 됩니다.This is used in the UpdatePollingDevices method to check if the list of known gamepads has changed.

    // Detect gamepad connection and disconnection events.
    Gamepad::GamepadAdded +=
        ref new EventHandler<Gamepad^>(this, &MoveLookController::OnGamepadAdded);

    Gamepad::GamepadRemoved +=
        ref new EventHandler<Gamepad^>(this, &MoveLookController::OnGamepadRemoved);

참고

앱이 포커스를 두지 않는 동안 UWP 앱은 Xbox One 컨트롤러에서 입력을 받을 수 없습니다.UWP apps cannot receive input from an Xbox One Controller while the app is not in focus.

UpdatePollingDevices 메서드The UpdatePollingDevices method

MoveLookController 인스턴스의 UpdatePollingDevices 메서드는 즉시 게임 패드의 연결 여부를 확인합니다.The UpdatePollingDevices method of the MoveLookController instance immediately checks to see if a gamepad is connected. 연결이 되어 있으면 Gamepad.GetCurrentReading로 상태 판독을 시작합니다.If one is, we'll start reading its state with Gamepad.GetCurrentReading. 이렇게 하면 GamepadReading 구조체가 반환되기 때문에 어떤 단추가 클릭되었는지, 어떤 섬스틱이 이동되었는지 확인할 수 있습니다.This returns the GamepadReading struct, allowing us to check what buttons have been clicked or thumbsticks moved.

게임의 상태가 WaitForInput이면 게임이 재개될 수 있도록 컨트롤러의 시작/메뉴 단추에 대해서만 수신 대기를 합니다 .If the state of the game is WaitForInput, we only listen for the Start/Menu button of the controller so that the game can be resumed.

상태가 활성이면 사용자 입력을 확인하고 어떤 게임 내 작업이 필요한지 판단합니다.If it's Active, we check the user's input and determine what in-game action needs to happen. 예를 들어, 사용자가 특정 방향으로 왼쪽 아날로그 스틱을 이동한다는 것은 스틱이 이동되는 방향으로 플레이어를 이동시켜야 한다는 것을 의미합니다.For instance, if the user moved the left analog stick in a specific direction, this lets the game know we need to move the player in the direction the stick is being moved. 데드존 반경은 플레이어의 엄지 손가락이 스틱 위에 있을 때 컨트롤러가 이 손가락으로부터 순간 이동을 선택하는 경우를 나타내는 "드리프트"를 제공하는 데 필요합니다.The movement of the stick in a specific direction must register as larger than the radius of the dead zone; otherwise, nothing will happen. 이 데드존 반경은 "드리프팅"을 막기 위해 반드시 필요합니다. 스틱 위에 놓여 있는 플레이어의 엄지 손가락의 작은 이동까지 컨트롤러가 잡아낼 때 이러한 드리프팅이 발생합니다.This dead zone radius is necessary to prevent "drifting," which is when the controller picks up small movements from the player's thumb as it rests on the stick. 데드존이 없으면 사용자의 움직임에 너무 민감하게 컨트롤이 이루어질 수 있습니다.Without dead zones, the controls can appear too sensitive to the user.

섬스틱 입력은 X축과 Y축 모두에서 -1에서 1 사이의 값을 갖습니다.Thumbstick input is between -1 and 1 for both the x and y axis. 다음 상수는 섬스틱 데드존의 반경을 지정합니다.The following consant specifies the radius of the thumbstick dead zone.

#define THUMBSTICK_DEADZONE 0.25f

이 변수를 사용하여 실행 가능한 섬스틱 입력을 처리하게 됩니다.Using this variable, we'll then begin processing actionable thumbstick input. 한쪽 축에서 [-1, -.26] 또는 [.26, 1]의 값으로 이동이 이루어집니다.Movement would occur with a value from [-1, -.26] or [.26, 1] on either axis.

섬스틱에 대한 데드존

UpdatePollingDevices 메서드의 이 부분은 왼쪽 및 오른쪽 섬스틱을 처리합니다.This piece of the UpdatePollingDevices method handles the left and right thumbsticks. 각 스틱의 X 값과 Y 값을 확인하여 데드존을 벗어났는지 여부를 알아봅니다.Each stick's X and Y values are checked to see if they are outside of the dead zone. 하나 또는 둘 모두가 있는 경우, 해당되는 구성 요소를 업데이트합니다.If one or both are, we'll update the corresponding component. 예를 들어 왼쪽 섬스틱이 X축을 따라 왼쪽으로 이동 중인 경우에는 m_moveCommand 벡터의 x 구성 요소에 -1을 추가합니다.For example, if the left thumbstick is being moved left along the X axis, we'll add -1 to the x component of the m_moveCommand vector. 이 vector는 모든 디바이스에서이 모든 이동을 집계하는 데 사용되고, 추후 플레이어의 이동 범위를 계산하는 데 사용됩니다.This vector is what will be used to aggregate all movements across all devices and will later be used to calculate where the player should move.

        // Use the left thumbstick to control the eye point position (position of the player)

        // Check if left thumbstick is outside of dead zone on x axis
        if (reading.LeftThumbstickX > THUMBSTICK_DEADZONE ||
            reading.LeftThumbstickX < -THUMBSTICK_DEADZONE)
        {
            // Get value of left thumbstick's position on x axis
            float x = static_cast<float>(reading.LeftThumbstickX);
            // Set the x of the move vector to 1 if the stick is being moved right.
            // Set to -1 if moved left. 
            m_moveCommand.x -= (x > 0) ? 1 : -1;
        }

        // Check if left thumbstick is outside of dead zone on y axis
        if (reading.LeftThumbstickY > THUMBSTICK_DEADZONE ||
            reading.LeftThumbstickY < -THUMBSTICK_DEADZONE)
        {
            // Get value of left thumbstick's position on y axis
            float y = static_cast<float>(reading.LeftThumbstickY);
            // Set the y of the move vector to 1 if the stick is being moved forward.
            // Set to -1 if moved backwards. 
            m_moveCommand.y += (y > 0) ? 1 : -1;
        }

왼쪽 스틱이 이동을 컨트롤하는 방법과 유사하게 오른쪽 스틱도 카메라 회전을 컨트롤합니다.Similar to how the left stick controls movement, the right stick controls the rotation of the camera.

오른쪽 섬스틱 동작은 마우스 및 키보드 제어 설정에 나와 있는 마우스 이동 동작에 맞춰집니다.The right thumb stick behavior aligns with the behavior of mouse movement in our mouse and keyboard control setup. 스틱이 데드존을 벗어나면 현재 포인터 위치와 사용자가 확인하려는 위치 간의 차이를 계산합니다.If the stick is outside of the dead zone, we calculate the difference between the current pointer position and where the user is now trying to look. 포인터 위치의 이러한 변경 값(pointerDelta)은 추후 Update 메서드에 적용되도록 카메라 회전의 피치 및 요를 업데이트하는 데 사용됩니다.This change in pointer position (pointerDelta) is then used to update the pitch and yaw of the camera rotation that later get applied in our Update method. pointerDelta 벡터는 마우스 및 터치 입력에서 포인터 위치의 변경을 추적하기 위해 MoveLookController::OnPointerMoved 메서드에서도 사용되기 때문에 비슷하게 보일 수 있습니다.The pointerDelta vector may look familiar because it's also used in the MoveLookController::OnPointerMoved method to keep track of change in pointer position for our mouse and touch inputs.

        // Use the right thumbstick to control the look at position

        XMFLOAT2 pointerDelta;

        // Check if right thumbstick is outside of deadzone on x axis
        if (reading.RightThumbstickX > THUMBSTICK_DEADZONE ||
            reading.RightThumbstickX < -THUMBSTICK_DEADZONE)
        {
            float x = static_cast<float>(reading.RightThumbstickX);
            // Register the change in the pointer along the x axis
            pointerDelta.x = x * x * x; 
        }
        // No actionable thumbstick movement. Register no change in pointer.
        else
        {
            pointerDelta.x = 0.0f;
        }
        // Check if right thumbstick is outside of deadzone on y axis
        if (reading.RightThumbstickY > THUMBSTICK_DEADZONE ||
            reading.RightThumbstickY < -THUMBSTICK_DEADZONE)
        {
            float y = static_cast<float>(reading.RightThumbstickY);
            // Register the change in the pointer along the y axis
            pointerDelta.y = y * y * y;
        }
        else
        {
            pointerDelta.y = 0.0f;
        }

        XMFLOAT2 rotationDelta;
        rotationDelta.x = pointerDelta.x *  0.08f;       // Scale for control sensitivity.
        rotationDelta.y = pointerDelta.y *  0.08f;

        // Update our orientation based on the command.
        m_pitch += rotationDelta.y;
        m_yaw += rotationDelta.x;

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

게임의 컨트롤은 구 실행 기능이 없으면 완전할 수 없습니다!The game's controls wouldn't be complete without the ability to fire spheres!

UpdatePollingDevices 메서드는 오른쪽 트리거의 누름 여부를 확인합니다.This UpdatePollingDevices method also checks if the right trigger is being pressed. 누름인 경우, m_firePressed 속성이 true로 전환되면서 구 실행을 시작하도록 게임에 알립니다.If it is, our m_firePressed property is flipped to true, signaling to the game that spheres should start firing.

    if (reading.RightTrigger > TRIGGER_DEADZONE)
    {
        if (!m_autoFire && !m_gamepadTriggerInUse)
        {
            m_firePressed = true;
        }

        m_gamepadTriggerInUse = true;
    }
    else
    {
        m_gamepadTriggerInUse = false;
    }

Update 메서드The Update method

마무리를 위해 Update 메서드에 대해 자세히 알아보겠습니다.To wrap things up, let's dig deeper into the Update method. 이 메서드는 지원되는 모든 입력에서 플레이어가 수행한 모든 이동 또는 회전을 병합하여 속도 벡터를 생성하고 게임 루프가 액세스할 수 있도록 피치 및 요 값을 업데이트합니다.This method merges any movements or rotations that the player made with any supported input to generate a velocity vector and update our pitch and yaw values for our game loop to access.

Update 메서드는 UpdatePollingDevices를 호출하여 컨트롤러 상태를 업데이트함으로써 처리를 시작합니다.The Update method kicks things off by calling UpdatePollingDevices to update the state of the controller. 이 메서드는 게임 패드에서 입력을 수집하고 m_moveCommand 벡터에 이동 이벤트를 추가합니다.This method also gathers any input from a gamepad and adds its movements to the m_moveCommand vector.

Update 메서드에서 다음과 같이 입력을 확인합니다.In our Update method we then perform the following input checks.

  • 플레이어가 이동 컨트롤러 사각형을 사용하는 경우에는 포인터 위치의 변경 여부를 판단하고, 사용자가 컨트롤러의 데드존 밖으로 포인터를 이동시킨 경우에는 이를 토대로 계산을 합니다.If the player is using the move controller rectangle, we'll then determine the change in pointer position and use that to calculate if the user has moved the pointer out of the controller's dead zone. 데드존을 벗어난 경우에는 m_moveCommand 벡터 속성이 가상 조이스틱 값으로 업데이트됩니다.If outside of the dead zone, the m_moveCommand vector property is then updated with the virtual joystick value.
  • 이동 키보드 입력을 누르면 1.0f 또는 -1.0f 값이 m_moveCommand 벡터 —의 해당 구성 요소에 추가 되 고 앞으로 1.0f 됩니다.If any of the movement keyboard inputs are pressed, a value of 1.0f or -1.0f are added in the corresponding component of the m_moveCommand vector — 1.0f for forward, and -1.0f for backward.

모든 이동 입력을 고려했다면 이제는 m_moveCommand 벡터를 실행하여 몇 가지 계산을 통해 게임 환경과 관련해 플레이어의 방향을 보여주는 새 벡터를 생성합니다.Once all movement input has been taken into account, we then run the m_moveCommand vector through some calculations to generate a new vector that represents the direction of the player with regards to the game world. 환경과 관련된 이동을 계산하고 이를 해당 방향의 속도로 플레이어에게 적용합니다.We then take our movements in relation to the world and apply them to the player as velocity in that direction. 마지막으로 m_moveCommand 벡터를 (0.0f, 0.0f, 0.0f)로 재설정하여 다음 게임 프레임에 대한 만반의 준비를 합니다.Finally we reset the m_moveCommand vector to (0.0f, 0.0f, 0.0f) so that everything is ready for the next game frame.

void MoveLookController::Update()
{
    // Get any gamepad input and update state
    UpdatePollingDevices();

    // Get change in 
    if (m_moveInUse)
    {
        XMFLOAT2 pointerDelta;

        pointerDelta.x = m_movePointerPosition.x - m_moveFirstDown.x;
        pointerDelta.y = m_movePointerPosition.y - m_moveFirstDown.y;

        // Figure out the command from the virtual joystick.
        XMFLOAT3 commandDirection = XMFLOAT3(0.0f, 0.0f, 0.0f);
        if (fabsf(pointerDelta.x) > 16.0f)         // Leave 32 pixel-wide dead spot for being still.
            m_moveCommand.x -= pointerDelta.x/fabsf(pointerDelta.x);

        if (fabsf(pointerDelta.y) > 16.0f)
            m_moveCommand.y -= pointerDelta.y/fabsf(pointerDelta.y);
    }

    // Poll our state bits 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 45deg cases are not faster.
    if (fabsf(m_moveCommand.x) > 0.1f ||
        fabsf(m_moveCommand.y) > 0.1f ||
        fabsf(m_moveCommand.z) > 0.1f)
    {
        XMStoreFloat3(&m_moveCommand, XMVector3Normalize(XMLoadFloat3(&m_moveCommand)));
    }

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

    // Scale for sensitivity adjustment.
    // Our velocity is based on the command. Y is up.
    m_velocity.x = -wCommand.x * MoveLookConstants::MovementGain;
    m_velocity.z =  wCommand.y * MoveLookConstants::MovementGain;
    m_velocity.y =  wCommand.z * MoveLookConstants::MovementGain;

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

다음 단계Next steps

컨트롤이 추가되었다면 이제 몰입감 높은 게임을 위해 추가해야 할 또 다른 기능이 있습니다. 사운드가 바로 그것입니다!Now that we have added our controls, there's another feature we need to add to create an immersive game: sound! 음악과 사운드 효과는 모든 게임에 있어 중요합니다. 사운드 추가에 대해서는 다음에 살펴보겠습니다.Music and sound effects are important to any game, so let's discuss adding sound next.