Поделиться через


Добавление элементов управления

Примечание.

Этот раздел является частью серии руководств по созданию простой игры универсальная платформа Windows (UWP) с помощью DirectX. Эта ссылка задает контекст для ряда.

[ Обновлено для приложений UWP в Windows 10. Статьи Windows 8.x см. в архиве ]

Хорошая игра универсальная платформа Windows (UWP) поддерживает широкий спектр интерфейсов. Потенциальный игрок может иметь Windows 10 на планшете без физических кнопок, ПК с подключенным игровым контроллером или последней классической игровой установки с высокопроизводительной мышью и игровой клавиатурой. В нашей игре элементы управления реализуются в классе MoveLookController. Этот класс объединяет все три типа входных данных (мышь и клавиатура, сенсорный и геймпад) в один контроллер. Конечный результат — это стрелка первого лица, использующая стандартные элементы управления перемещением, которые работают с несколькими устройствами.

Примечание.

Дополнительные сведения об элементах управления см. в разделе "Элементы управления move-look" для игр и элементов управления Touch.

Цель

На этом этапе у нас есть игра, которая отрисовывает, но мы не можем перемещать наших игроков вокруг или стрелять цели. Мы рассмотрим, как наша игра реализует элементы управления перемещения первого человека стрелка перемещения для следующих типов входных данных в игре UWP DirectX.

  • Мышь и клавиатура
  • Сенсорный ввод
  • Игровой планшет

Примечание.

Если вы не скачали последний игровой код для этого примера, перейдите в пример игры Direct3D. Этот пример является частью большой коллекции примеров функций UWP. Инструкции по скачиванию примера см. в разделе "Примеры приложений для разработки Windows".

Распространенные характеристики элементов управления

Сенсорные элементы управления и элементы управления мышью и клавиатурой имеют очень похожую реализацию ядра. В приложении UWP указатель — это просто точка на экране. Вы можете переместить его, скользя мышью или скользя пальцем на сенсорном экране. В результате вы можете зарегистрировать один набор событий и не беспокоиться о том, используется ли проигрыватель мышь или сенсорный экран для перемещения и нажатия указателя.

Когда класс MoveLookController в образце игры инициализирован, он регистрируется для четырех событий, относящихся к указателю, и одного события, определенного для мыши:

Мероприятие Description
CoreWindow::P ointerPressed Нажата левая или правая кнопка мыши (и удерживалась), или касалась сенсорной поверхности.
CoreWindow::P ointerMoved Мышь переместилась или действие перетаскивания было сделано на сенсорной поверхности.
CoreWindow::P ointerReleased Была освобождена левая кнопка мыши или объект, связающийся с сенсорной поверхностью, был снят.
CoreWindow::P ointerExited Указатель перемещен из главного окна.
Windows::D evices::Input::MouseMoved Мышь переместилась на определенное расстояние. Помните, что мы заинтересованы только в значениях разностного перемещения мыши, а не текущей позиции X-Y.

Эти обработчики событий настроены для начала прослушивания ввода пользователем, как только moveLookController инициализирован в окне приложения.

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

    window.PointerPressed({ this, &MoveLookController::OnPointerPressed });

    window.PointerMoved({ this, &MoveLookController::OnPointerMoved });

    window.PointerReleased({ this, &MoveLookController::OnPointerReleased });

    window.PointerExited({ this, &MoveLookController::OnPointerExited });

    ...

    // There is a separate handler for mouse-only relative mouse movement events.
    MouseDevice::GetForCurrentView().MouseMoved({ this, &MoveLookController::OnMouseMoved });

    ...
}

Полный код initWindow можно просмотреть на сайте GitHub.

Чтобы определить, когда игра должна прослушивать определенные входные данные, класс MoveLookController имеет три состояния контроллера независимо от типа контроллера:

State Description
Не допускается Это инициализированное состояние для контроллера. Все входные данные игнорируются, так как игра не ожидает входных данных контроллера.
WaitForInput Контроллер ожидает, чтобы игрок признал сообщение из игры с помощью левого щелчка мыши, сенсорного события, от кнопки меню на геймпаде.
Активные Контроллер находится в активном режиме игры.

Состояние WaitForInput и приостановка игры

Игра входит в состояние WaitForInput , когда игра была приостановлена. Это происходит, когда игрок перемещает указатель за пределами главного окна игры или нажимает кнопку приостановки (клавиша P или кнопка "Пуск геймпада"). MoveLookController регистрирует прессу и сообщает цикл игры при вызове метода IsPauseRequested. На этом этапе, если IsPauseRequested возвращает значение true, то цикл игры вызывает WaitForPress в MoveLookController для перемещения контроллера в состояние WaitForInput.

Как только в состоянии WaitForInput игра перестает обрабатывать почти все события ввода игрового процесса, пока не вернется в активное состояние. Исключением является кнопка паузы, при этом нажатие этой проблемы приводит к возврату игры к активному состоянию. Кроме кнопки приостановки, чтобы игра вернулась к активному состоянию, игроку нужно выбрать пункт меню.

Активное состояние

Во время активного состояния экземпляр MoveLookController обрабатывает события со всех включенных устройств ввода и интерпретирует намерения игрока. В результате он обновляет скорость и направление просмотра представления игрока и делится обновленными данными с игрой после вызова обновления из цикла игры.

Все входные данные указателя отслеживаются в активном состоянии с различными идентификаторами указателей, соответствующими различным действиям указателя. При получении события PointerPressed объект MoveLookController получает значение идентификатора указателя, созданное окном. Идентификатор указателя представляет определенный тип входных данных. Например, на устройстве с несколькими сенсорными устройствами одновременно может быть несколько активных входных данных. Идентификаторы используются для отслеживания того, какие входные данные использует проигрыватель. Если одно событие находится в прямоугольнике перемещения сенсорного экрана, идентификатор указателя назначается для отслеживания любых событий указателя в прямоугольнике перемещения. Другие события указателя в прямоугольнике пожара отслеживаются отдельно с отдельным идентификатором указателя.

Примечание.

Входные данные из мыши и правого отпечатка геймпада также имеют идентификаторы, которые обрабатываются отдельно.

После того как события указателя сопоставлены с определенным игровым действием, пришло время обновить данные объекта MoveLookController с основным циклом игры.

При вызове метод Update в примере игры обрабатывает входные данные и обновляет переменные скорости и направления просмотра (m_velocity и m_lookdirection), которые затем извлекается, вызывая общедоступные методы Velocity и LookDirection.

Примечание.

Дополнительные сведения о методе Update можно увидеть позже на этой странице.

Цикл игры может проверить, выполняется ли игрок, вызвав метод IsFiring в экземпляре MoveLookController . MoveLookController проверяет, нажимает ли проигрыватель кнопку "Пожар" в одном из трех типов входных данных.

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

Теперь рассмотрим реализацию каждого из трех типов элементов управления немного подробнее.

Добавление относительных элементов управления мышью

Если обнаружено движение мыши, мы хотим использовать это движение, чтобы определить новый шаг и рывок камеры. Мы делаем это путем реализации относительных элементов управления мышью, где мы обрабатываем относительное расстояние, которое мышь перемещена ( разность между началом движения и остановкой), а не запись абсолютных координат пикселей x-y движения.

Для этого мы получаем изменения в координатах X (горизонтальное движение) и Y (вертикальное движение), изучая поля MouseDelta::X и MouseDelta::Y в полях Windows::D evice::Input::MouseEventArgs::MouseDelta аргумент, возвращаемый событием MouseMoved.

void MoveLookController::OnMouseMoved(
    _In_ MouseDevice const& /* mouseDevice */,
    _In_ MouseEventArgs const& 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;
        // Scale for control sensitivity.
        rotationDelta.x = mouseDelta.x * MoveLookConstants::RotationGain;
        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 sane 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;
    }
}

Добавление поддержки сенсорного ввода

Сенсорные элементы управления отлично подходят для поддержки пользователей с планшетами. Эта игра собирает сенсорные входные данные путем зонирования определенных областей экрана с каждым выравниванием по конкретным действиям в игре. Сенсорный ввод в этой игре использует три зоны.

Перемещение макета сенсорного ввода

Следующие команды обобщают поведение сенсорного элемента управления. Входные данные пользователя | Действие :------- | :-------- Перемещение прямоугольника | Сенсорные входные данные преобразуются в виртуальный джойстик, где вертикальное движение будет переведено в движение вперед или назад, а горизонтальное движение преобразуется в движение влево или вправо. Огонь прямоугольник | Загореть сферу. Касание за пределами перемещения и стрельбы прямоугольника | Измените поворот (шаг и рывок) представления камеры.

MoveLookController проверяет идентификатор указателя, чтобы определить, где произошло событие, и выполняет одно из следующих действий:

  • Если событие PointerMoved произошло в прямоугольнике перемещения или пожара, обновите положение указателя для контроллера.
  • Если событие PointerMoved произошло где-то в остальной части экрана (определяется как элементы управления внешним видом), вычислите изменение поля и рывок вектора направления взгляда.

После реализации наших сенсорных элементов управления прямоугольники, которые мы нарисовали ранее с помощью Direct2D, будут указывать игрокам, где находятся зоны перемещения, пожара и просмотра.

сенсорные элементы управления

Теперь давайте рассмотрим, как мы реализуем каждый элемент управления.

Перемещение и срабатывание контроллера

Прямоугольник контроллера перемещения в левом нижнем квадранте экрана используется в качестве направленной панели. Скольжение пальца влево и вправо в этом пространстве перемещает игрока влево и вправо, в то время как вверх и вниз перемещает камеру вперед и назад. После настройки этого коснитесь контроллера огня в правом нижнем квадранте экрана срабатывает сферу.

Методы SetMoveRect и SetFireRect создают наши входные прямоугольники, принимая два, 2D-вектора, чтобы указать верхние и правые позиции прямоугольников на экране.

Затем параметры назначаются m_fireUpperLeft и m_fireLowerRight , которые помогут нам определить, касается ли пользователь в прямоугольниках.

m_fireUpperLeft = upperLeft;
m_fireLowerRight = lowerRight;

Если экран изменен, эти прямоугольники будут перезаписаны в соответствующий размер.

Теперь, когда мы отключили наши элементы управления, пришло время определить, когда пользователь фактически использует их. Для этого мы настроим некоторые обработчики событий в методе MoveLookController::InitWindow , когда пользователь нажимает, перемещает или освобождает указатель.

window.PointerPressed({ this, &MoveLookController::OnPointerPressed });

window.PointerMoved({ this, &MoveLookController::OnPointerMoved });

window.PointerReleased({ this, &MoveLookController::OnPointerReleased });

Сначала мы определим, что происходит, когда пользователь сначала нажимает в прямоугольниках перемещения или пожара с помощью метода OnPointerPressed. Здесь мы проверяем, где они касаются элемента управления, и если указатель уже находится в этом контроллере. Если это первый палец для касания определенного элемента управления, мы делаем следующее.

  • Сохраните расположение приземления в m_moveFirstDown или m_fireFirstDown в виде 2D-вектора.
  • Назначьте идентификатор указателя m_movePointerID или m_firePointerID.
  • Установите правильный флаг InUse (m_moveInUse или m_fireInUse), true так как теперь у нас есть активный указатель для этого элемента управления.
PointerPoint point = args.CurrentPoint();
uint32_t 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 winrt::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 no pointer is in this control yet.
            if (!m_moveInUse)
            {
                // Process a DPad touch down event.
                // Save the location of the initial contact
                m_moveFirstDown = position;
                // Store the pointer using this control
                m_movePointerID = pointerID;
                // Set InUse flag to signal there is an active move pointer
                m_moveInUse = true;
            }
        }
        // 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)
            {
                // Save the location of the initial contact
                m_fireLastPoint = position;
                // Store the pointer using this control
                m_firePointerID = pointerID;
                // Set InUse flag to signal there is an active fire pointer
                m_fireInUse = true;
                ...
            }
        }
        ...

Теперь, когда мы определили, касается ли пользователь перемещения или управления огнем, мы видим, делает ли игрок какие-либо движения с нажатым пальцем. Используя метод MoveLookController::OnPointerMoved, мы проверяем, что указатель перемещен, а затем сохраните новое положение в виде 2D-вектора.

PointerPoint point = args.CurrentPoint();
uint32_t pointerID = point.PointerId();
Point pointerPosition = point.Position();
PointerPointProperties pointProperties = point.Properties();
auto pointerDevice = point.PointerDevice();

// convert to allow math
XMFLOAT2 position = XMFLOAT2(pointerPosition.X, pointerPosition.Y);

switch (m_state)
{
case MoveLookControllerState::Active:
    // Decide which control this pointer is operating.

    // Move control
    if (pointerID == m_movePointerID)
    {
        // Save the current position.
        m_movePointerPosition = position;
    }
    // Look control
    else if (pointerID == m_lookPointerID)
    {
        ...
    }
    // Fire control
    else if (pointerID == m_firePointerID)
    {
        m_fireLastPoint = position;
    }
    ...

После того как пользователь сделал свои жесты в элементах управления, он выпустит указатель. Используя метод MoveLookController::OnPointerReleased, мы определяем, какой указатель был выпущен и выполняет ряд сбросов.

Если элемент управления перемещением выпущен, выполните указанные ниже действия.

  • Установите скорость игрока 0 во всех направлениях, чтобы предотвратить их перемещение в игре.
  • Переключение m_moveInUsefalse, так как пользователь больше не касается контроллера перемещения.
  • Задайте идентификатор 0 указателя перемещения, так как в контроллере перемещения больше нет указателя.
if (pointerID == m_movePointerID)
{
    // Stop on release.
    m_velocity = XMFLOAT3(0, 0, 0);
    m_moveInUse = false;
    m_movePointerID = 0;
}

Для управления огнем, если он был выпущен все, что мы делаем, переключение флага m_fireInUse на false и идентификатор 0 указателя огня, так как больше нет указателя в пожарном контроле.

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

Внешний контроллер

Мы обрабатываем события указателя сенсорного устройства для неиспользуемых областей экрана в качестве контроллера внешнего вида. Скользя пальцем вокруг этой зоны, изменяется шаг и рывок (поворот) камеры проигрывателя.

Если событие MoveLookController::OnPointerPressed вызывается на сенсорном устройстве в этом регионе, а состояние игры имеет значение "Активный", оно назначается идентификатор указателя.

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

Здесь MoveLookController назначает идентификатор указателя для указателя, который запустил событие в определенную переменную, соответствующую региону внешнего вида. В случае касания в области просмотра переменная m_lookPointerID задается идентификатором указателя, который запустил событие. Логическое значение переменной, m_lookInUse, также имеет значение, указывающее, что элемент управления еще не выпущен.

Теперь давайте рассмотрим, как пример игры обрабатывает событие сенсорного экрана PointerMoved .

В методе MoveLookController::OnPointerMoved мы проверяем, какой идентификатор указателя был назначен событию. Если это m_lookPointerID, мы вычислим изменение положения указателя. Затем мы используем эту дельту, чтобы вычислить, сколько должно измениться поворот. Наконец, мы в точке, где мы можем обновить m_pitch и m_yaw использовать в игре, чтобы изменить поворот игрока.

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

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

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

Последний фрагмент мы рассмотрим, как пример игры обрабатывает событие pointerReleased сенсорного экрана. После завершения сенсорного жеста и удаления пальца с экрана инициируется MoveLookController::OnPointerReleased. Если идентификатор указателя, запускающего событие PointerReleased , является идентификатором ранее записанного указателя перемещения, MoveLookController задает скорость 0 , так как проигрыватель перестал касаться области просмотра.

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

Добавление поддержки мыши и клавиатуры

Эта игра имеет следующий макет элемента управления для клавиатуры и мыши.

Пользовательский ввод Действие
Ср. Перемещение игрока вперед
а Перемещение игрока влево
S Перемещение проигрывателя назад
D Перемещение игрока вправо
X Перемещение представления вверх
Полоса пробела Перемещение представления вниз
P Приостановка игры
Перемещение мыши Изменение поворота (шаг и язь) представления камеры
Левая кнопка мыши Пожар сферы

Для использования клавиатуры пример игры регистрирует два новых события CoreWindow::KeyUp и CoreWindow::KeyDown в методе MoveLookController::InitWindow. Эти события обрабатывают прессу и выпуск ключа.

window.KeyDown({ this, &MoveLookController::OnKeyDown });

window.KeyUp({ this, &MoveLookController::OnKeyUp });

Мышь обрабатывается немного иначе от сенсорных элементов управления, даже если она использует указатель. Чтобы выровнять макет элемента управления, moveLookController поворачивает камеру при перемещении мыши и запускается при нажатии левой кнопки мыши.

Это обрабатывается в методе OnPointerPressed объекта MoveLookController.

В этом методе мы проверяем, какой тип устройства указателя используется с перечислением Windows::Devices::Input::PointerDeviceType . Если игра активна, а PointerDeviceType не touch, мы предполагаем, что это ввод мыши.

case MoveLookControllerState::Active:
    switch (pointerDeviceType)
    {
    case winrt::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;
            // These are for smoothing.
            m_lookLastDelta.x = m_lookLastDelta.y = 0;
        }
        break;
    }
    break;

Когда проигрыватель перестает нажимать одну из кнопок мыши, вызывается событие мыши CoreWindow::P ointerReleased, вызывая метод MoveLookController::OnPointerReleased, а входные данные завершены. На этом этапе сферы перестают работать, если левая кнопка мыши нажимается и теперь освобождается. Так как внешний вид всегда включен, игра продолжает использовать тот же указатель мыши для отслеживания текущих событий внешнего вида.

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;

Теперь давайте рассмотрим последний тип элемента управления, который мы будем поддерживать: геймпады. Геймпады обрабатываются отдельно от элементов управления касанием и мышью, так как они не используют объект указателя. Из-за этого необходимо добавить несколько новых обработчиков событий и методов.

Добавление поддержки геймпада

Для этой игры поддержка геймпадов добавляется вызовами API Windows.Gaming.Input . Этот набор API обеспечивает доступ к входным данным игрового контроллера, таким как гоночные колеса и пролетные палки.

Ниже приведены элементы управления геймпадом.

Пользовательский ввод Действие
Левая аналоговая палка Перемещение проигрывателя
Правая аналоговая палка Изменение поворота (шаг и язь) представления камеры
Правый триггер Пожар сферы
Кнопка "Пуск и меню" Приостановка или возобновление игры

В методе InitWindow мы добавим два новых события, чтобы определить, был ли добавлен или удален геймпад. Эти события обновляют свойство m_gamepadsChanged . Это используется в методе UpdatePollingDevices , чтобы проверить, изменился ли список известных игровых контроллеров.

// Detect gamepad connection and disconnection events.
Gamepad::GamepadAdded({ this, &MoveLookController::OnGamepadAdded });

Gamepad::GamepadRemoved({ this, &MoveLookController::OnGamepadRemoved });

Примечание.

Приложения UWP не могут получать входные данные от игрового контроллера, пока приложение не находится в фокусе.

Метод UpdatePollingDevices

Метод UpdatePollingDevices экземпляра MoveLookController немедленно проверяет, подключен ли геймпад. Если это так, мы начнем считывать его состояние с Gamepad.GetCurrentReading. Это возвращает структуру GamepadReading , позволяя нам проверить, какие кнопки были перемещены или смещены.

Если состояние игры — WaitForInput, мы прослушиваем только кнопку "Пуск" или "Меню" контроллера, чтобы можно было возобновить игру.

Если это активно, мы проверяем входные данные пользователя и определяем, что нужно выполнить в игре. Например, если пользователь переместил левую аналоговую палку в определенном направлении, это позволяет игре знать, что нам нужно переместить игрока в направлении перемещения палки. Перемещение палки в определенном направлении должно регистрироваться как больше радиуса мертвой зоны; в противном случае ничего не произойдет. Этот радиус мертвой зоны необходим, чтобы предотвратить "смещение", то есть когда контроллер берет небольшие движения от пальца игрока, как он находится на палке. Без мертвых зон элементы управления могут оказаться слишком чувствительными к пользователю.

Входные данные от -1 до 1 для оси x и y. Следующая константа указывает радиус мертвой зоны отпечатка.

#define THUMBSTICK_DEADZONE 0.25f

С помощью этой переменной мы приступим к обработке практических входных данных. Перемещение будет происходить со значением [-1, -.26] или [.26, 1] на любой оси.

мертвая зона для отпечатков

Этот элемент метода UpdatePollingDevices обрабатывает левые и правые палец. Значения X и Y для каждой палки проверяются, если они находятся за пределами мертвой зоны. При наличии одного или обоих компонентов мы обновим соответствующий компонент. Например, если левый палец перемещается по оси X, мы добавим -1 к компоненту x вектора m_moveCommand . Этот вектор используется для агрегирования всех движений на всех устройствах и позже будет использоваться для вычисления места перемещения проигрывателя.

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

Аналогично тому, как движение слева на палке, правый палка управляет поворотом камеры.

Правильное поведение палки соответствует поведению перемещения мыши в настройке мыши и клавиатуры. Если палка находится за пределами мертвой зоны, мы вычисляем разницу между текущей позицией указателя и местом, где пользователь пытается выглядеть. Это изменение положения указателя (pointerDelta) затем используется для обновления шага и поворота камеры, которые позже применяются в нашем методе Update . Вектор pointerDelta может выглядеть знакомым, так как он также используется в методе MoveLookController::OnPointerMoved для отслеживания изменения положения указателя для наших входных данных мыши и сенсорного ввода.

// 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;
// Scale for control sensitivity.
rotationDelta.x = pointerDelta.x * 0.08f;
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);

Элементы управления игры не будут завершены без возможности пожара в сфере!

Этот метод UpdatePollingDevices также проверяет, нажимается ли правильный триггер. Если это так, наше свойство m_firePressed перевернуто на true, сигналив игре, что сферы должны начать стрелять.

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

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

Метод Update

Чтобы упаковать вещи, давайте глубже рассмотрим метод Update . Этот метод объединяет любые движения или повороты, которые игрок сделал с любыми поддерживаемыми входными данными для создания вектора скорости и обновления значений шага и яжирования для доступа к циклу игры.

Метод Update запускает все, вызвав UpdatePollingDevices для обновления состояния контроллера. Этот метод также собирает входные данные из геймпада и добавляет его движения в вектор m_moveCommand .

В нашем методе Update мы выполните следующие входные проверки.

  • Если проигрыватель использует прямоугольник контроллера перемещения, мы определим изменение положения указателя и используйте это для вычисления того, перемещен ли пользователь указатель из мертвой зоны контроллера. Если вне мертвой зоны, свойство вектора m_moveCommand обновляется с помощью значения виртуального джойстика.
  • Если любой из входных данных с помощью клавиатуры перемещения нажимается, значение 1.0f или -1.0f добавляется в соответствующий компонент вектора m_moveCommand1.0f для переадресации и -1.0f обратно.

После того как все входные данные движения были учтены, мы затем запустите вектор m_moveCommand через некоторые вычисления, чтобы создать новый вектор, представляющий направление игрока относительно игрового мира. Затем мы берем наши движения в отношении мира и применяем их к игроку как скорость в этом направлении. Наконец, мы сбросим вектор m_moveCommand , (0.0f, 0.0f, 0.0f) чтобы все готово к следующему игровому кадру.

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

    if (m_moveInUse)
    {
        // Move control.
        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);
        // Leave 32 pixel-wide dead spot for being still.
        if (fabsf(pointerDelta.x) > 16.0f)
            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);
}

Следующие шаги

Теперь, когда мы добавили наши элементы управления, есть еще одна функция, которую необходимо добавить, чтобы создать иммерсивную игру: звук! Музыка и звуковые эффекты важны для любой игры, поэтому давайте обсудим добавление звука далее.