Добавление входных и интерактивных данных в пример Marble Maze

игры универсальная платформа Windows (UWP) выполняются на различных устройствах, таких как настольные компьютеры, ноутбуки и планшеты. Устройство может иметь множество механизмов ввода и управления. В этом документе описываются основные методики, которые следует учитывать при работе с устройствами ввода и показывает, как Marble Maze применяет эти методики.

Примечание.

Пример кода, соответствующий этому документу, найден в примере игры DirectX Marble Maze.

  Ниже приведены некоторые ключевые моменты, которые обсуждаются в этом документе при работе с входными данными в игре:

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

  • Чтобы инициализировать сенсорный ввод, необходимо зарегистрировать события окна, например при активации, освобождении и перемещении указателя. Чтобы инициализировать акселерометр, создайте объект Windows::D evices::Sensor::Accelerometer при инициализации приложения. Игровой контроллер не требует инициализации.

  • Для игр с одним игроком рассмотрите возможность объединения входных данных со всех возможных контроллеров. Таким образом, вам не нужно отслеживать входные данные, поступающие от какого контроллера. Или просто отслеживать входные данные только из последнего добавленного контроллера, как мы делаем в этом примере.

  • Обработка событий Windows перед обработкой устройств ввода.

  • Игровой контроллер и акселерометр поддерживают опрос. То есть вы можете проискать данные, когда это нужно. Для сенсорного ввода записываются события касания в структурах данных, доступных в коде обработки ввода.

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

Устройства ввода, поддерживаемые Marble Maze

Marble Maze поддерживает игровой контроллер, мышь и касание для выбора элементов меню, а также контроллера игры, мыши, касания и акселерометра для управления игрой. Marble Maze использует API Windows::Gaming::Input для опроса контроллера для ввода. Сенсорный ввод позволяет приложениям отслеживать и реагировать на входные данные пальцем. Акселерометр — это датчик, который измеряет силу, применяемую вдоль осей X, Y и Z. С помощью среда выполнения Windows можно проверить текущее состояние устройства акселерометра, а также получать сенсорные события через механизм обработки событий среда выполнения Windows.

Примечание.

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

 

Примечание.

Манифест пакета задает "Альбом" в качестве единственного поддерживаемого поворота для игры, чтобы предотвратить изменение ориентации при повороте устройства, чтобы свернуть мрамор. Чтобы просмотреть манифест пакета, откройте Package.appxmanifest в Обозреватель решений в Visual Studio.

 

Инициализация устройств ввода

Игровой контроллер не требует инициализации. Чтобы инициализировать сенсорный ввод, необходимо зарегистрировать события окна, например при активации указателя (например, проигрыватель нажимает кнопку мыши или касается экрана), освобождается и перемещается. Чтобы инициализировать акселерометр, необходимо создать объект Windows::D evices::Sensor::Accelerometer при инициализации приложения.

В следующем примере показано, как метод App::SetWindow регистрируется для событий указателя Windows:UI::CoreWindow::P ointerPressed, Windows::UI::CoreWindow::CoreWindow::P ointerReleased и Windows::UI::CoreWindow:::P ointerMoved указателя. Эти события регистрируются во время инициализации приложения и перед циклом игры.

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

Дополнительные сведения о том, как инициализировано приложение, см . в структуре приложения Marble Maze.

window->PointerPressed += ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(
    this, 
    &App::OnPointerPressed);

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

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

Класс MarbleMazeMain также создает объект std::map для хранения событий касания. Ключом для этого объекта карты является значение, однозначно определяющее указатель ввода. Каждый ключ сопоставляется с расстоянием между каждой точкой касания и центром экрана. Marble Maze позже использует эти значения для вычисления суммы, на которую наклоняется лабиринт.

typedef std::map<int, XMFLOAT2> TouchMap;
TouchMap        m_touches;

Класс MarbleMazeMain также содержит объект Accelerometer .

Windows::Devices::Sensors::Accelerometer^           m_accelerometer;

Объект Accelerometer инициализирован в конструкторе MarbleMazeMazeMain , как показано в следующем примере. Метод Windows::D evices::Sensor::Accelerometer::GetDefault возвращает экземпляр акселерометра по умолчанию. Если нет акселерометра по умолчанию, Accelerometer::GetDefault возвращает nullptr.

// Returns accelerometer ref if there is one; nullptr otherwise.
m_accelerometer = Windows::Devices::Sensors::Accelerometer::GetDefault();

Для навигации по меню можно использовать мышь, касание или игровой контроллер, как показано ниже.

  • Используйте панель направления для изменения активного элемента меню.
  • Используйте сенсорный экран, кнопку "А" или кнопку меню, чтобы выбрать пункт меню или закрыть текущее меню, например таблицу высокой оценки.
  • Нажмите кнопку меню, чтобы приостановить или возобновить игру.
  • Щелкните пункт меню с мышью, чтобы выбрать это действие.

Отслеживание входных данных игрового контроллера

Чтобы отслеживать геймпады, подключенные к устройству, MarbleMazeMain определяет переменную члена, m_myGamepads, которая является коллекцией объектов Windows::Gaming::Input::Gamepad. Это инициализируется в конструкторе следующим образом:

m_myGamepads = ref new Vector<Gamepad^>();

for (auto gamepad : Gamepad::Gamepads)
{
    m_myGamepads->Append(gamepad);
}

Кроме того, конструктор MarbleMazeMain регистрирует события при добавлении или удалении геймпадов:

Gamepad::GamepadAdded += 
    ref new EventHandler<Gamepad^>([=](Platform::Object^, Gamepad^ args)
{
    m_myGamepads->Append(args);
    m_currentGamepadNeedsRefresh = true;
});

Gamepad::GamepadRemoved += 
    ref new EventHandler<Gamepad ^>([=](Platform::Object^, Gamepad^ args)
{
    unsigned int indexRemoved;

    if (m_myGamepads->IndexOf(args, &indexRemoved))
    {
        m_myGamepads->RemoveAt(indexRemoved);
        m_currentGamepadNeedsRefresh = true;
    }
});

При добавлении геймпад добавляется в m_myGamepads; при удалении геймпада мы проверка, если геймпад находится в m_myGamepads, и если он есть, мы удаляем его. В обоих случаях для m_currentGamepadNeedsRefresh задано значение true, указывающее, что необходимо переназначить m_gamepad.

Наконец, мы назначим геймпад m_gamepad и задайте дляm_currentGamepadNeedsRefresh значение false:

m_gamepad = GetLastGamepad();
m_currentGamepadNeedsRefresh = false;

В методе Update мы проверка, если m_gamepad необходимо переназначить:

if (m_currentGamepadNeedsRefresh)
{
    auto mostRecentGamepad = GetLastGamepad();

    if (m_gamepad != mostRecentGamepad)
    {
        m_gamepad = mostRecentGamepad;
    }

    m_currentGamepadNeedsRefresh = false;
}

Если m_gamepad необходимо переназначить, мы назначаем ему последний добавленный геймпад с помощью GetLastGamepad, который определяется следующим образом:

Gamepad^ MarbleMaze::MarbleMazeMain::GetLastGamepad()
{
    Gamepad^ gamepad = nullptr;

    if (m_myGamepads->Size > 0)
    {
        gamepad = m_myGamepads->GetAt(m_myGamepads->Size - 1);
    }

    return gamepad;
}

Этот метод просто возвращает последний геймпад в m_myGamepads.

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

Метод MarbleMazeMain::Update опрашивает геймпад для ввода:

if (m_gamepad != nullptr)
{
    m_oldReading = m_newReading;
    m_newReading = m_gamepad->GetCurrentReading();
}

Мы отслеживаем входное чтение, которое мы получили в последнем кадре с m_oldReading, и последнее чтение входных данных с помощью m_newReading, которое мы получаем путем вызова Gamepad::GetCurrentReading. Возвращается объект GamepadReading , содержащий сведения о текущем состоянии геймпада.

Чтобы проверка, если кнопка была просто нажата или выпущена, мы определяем MarbleMazeMazeMain::ButtonJustPressed и MarbleMazeMazeMain::ButtonJustReleased, которые сравнивают чтение кнопок из этого кадра и последний кадр. Таким образом, мы можем выполнить действие только в то время, когда кнопка изначально нажимается или освобождается, а не при его проведении:

bool MarbleMaze::MarbleMazeMain::ButtonJustPressed(GamepadButtons selection)
{
    bool newSelectionPressed = (selection == (m_newReading.Buttons & selection));
    bool oldSelectionPressed = (selection == (m_oldReading.Buttons & selection));
    return newSelectionPressed && !oldSelectionPressed;
}

bool MarbleMaze::MarbleMazeMain::ButtonJustReleased(GamepadButtons selection)
{
    bool newSelectionReleased = 
        (GamepadButtons::None == (m_newReading.Buttons & selection));

    bool oldSelectionReleased = 
        (GamepadButtons::None == (m_oldReading.Buttons & selection));

    return newSelectionReleased && !oldSelectionReleased;
}

Чтения GamepadButtons сравниваются с помощью побитовых операций— мы проверка, если кнопка нажимается побитовой и (&>). Мы определяем, была ли кнопка просто нажата или освобождена, сравнивая старое чтение и новое чтение.

Используя приведенные выше методы, мы проверка, если определенные кнопки были нажаты и выполняют какие-либо соответствующие действия, которые должны произойти. Например, при нажатии кнопки меню (GamepadButtons::Menu) состояние игры изменяется с активного на приостановку или приостановку на активный.

if (ButtonJustPressed(GamepadButtons::Menu) || m_pauseKeyPressed)
{
    m_pauseKeyPressed = false;

    if (m_gameState == GameState::InGameActive)
    {
        SetGameState(GameState::InGamePaused);
    }  
    else if (m_gameState == GameState::InGamePaused)
    {
        SetGameState(GameState::InGameActive);
    }
}

Мы также проверка, если игрок нажимает кнопку "Вид", в этом случае мы перезагрузим игру или очистим таблицу высокой оценки:

if (ButtonJustPressed(GamepadButtons::View) || m_homeKeyPressed)
{
    m_homeKeyPressed = false;

    if (m_gameState == GameState::InGameActive ||
        m_gameState == GameState::InGamePaused ||
        m_gameState == GameState::PreGameCountdown)
    {
        SetGameState(GameState::MainMenu);
        m_inGameStopwatchTimer.SetVisible(false);
        m_preGameCountdownTimer.SetVisible(false);
    }
    else if (m_gameState == GameState::HighScoreDisplay)
    {
        m_highScoreTable.Reset();
    }
}

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

// Handle menu navigation.
bool chooseSelection = 
    (ButtonJustPressed(GamepadButtons::A) 
    || ButtonJustPressed(GamepadButtons::Menu));

bool moveUp = ButtonJustPressed(GamepadButtons::DPadUp);
bool moveDown = ButtonJustPressed(GamepadButtons::DPadDown);

switch (m_gameState)
{
case GameState::MainMenu:
    if (chooseSelection)
    {
        m_audio.PlaySoundEffect(MenuSelectedEvent);
        if (m_startGameButton.GetSelected())
        {
            m_startGameButton.SetPressed(true);
        }
        if (m_highScoreButton.GetSelected())
        {
            m_highScoreButton.SetPressed(true);
        }
    }
    if (moveUp || moveDown)
    {
        m_startGameButton.SetSelected(!m_startGameButton.GetSelected());
        m_highScoreButton.SetSelected(!m_startGameButton.GetSelected());
        m_audio.PlaySoundEffect(MenuChangeEvent);
    }
    break;

case GameState::HighScoreDisplay:
    if (chooseSelection || anyPoints)
    {
        SetGameState(GameState::MainMenu);
    }
    break;

case GameState::PostGameResults:
    if (chooseSelection || anyPoints)
    {
        SetGameState(GameState::HighScoreDisplay);
    }
    break;

case GameState::InGamePaused:
    if (m_pausedText.IsPressed())
    {
        m_pausedText.SetPressed(false);
        SetGameState(GameState::InGameActive);
    }
    break;
}

Отслеживание сенсорных и мыши входных данных

Для ввода сенсорного ввода и мыши элемент меню выбирается, когда пользователь прикасается к нему или щелкает его. В следующем примере показано, как метод MarbleMazeMain::Update обрабатывает входные данные указателя для выбора элементов меню. Переменная члена m_pointQueue отслеживает расположения, в которых пользователь коснулся или щелкнул экран. Способ сбора входных данных указателя Marble Maze описан более подробно в этом документе в разделе "Обработка указателя".

// Check whether the user chose a button from the UI. 
bool anyPoints = !m_pointQueue.empty();
while (!m_pointQueue.empty())
{
    UserInterface::GetInstance().HitTest(m_pointQueue.front());
    m_pointQueue.pop();
}

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

void UserInterface::HitTest(D2D1_POINT_2F point)
{
    for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
    {
        if (!(*iter)->IsVisible())
            continue;

        TextButton* textButton = dynamic_cast<TextButton*>(*iter);
        if (textButton != nullptr)
        {
            D2D1_RECT_F bounds = (*iter)->GetBounds();
            textButton->SetPressed(PointInRect(point, bounds));
        }
    }
}

Обновление состояния игры

После того как метод MarbleMazeMain::Update обрабатывает контроллер и сенсорный ввод, он обновляет состояние игры, если какая-либо кнопка была нажата.

// Update the game state if the user chose a menu option. 
if (m_startGameButton.IsPressed())
{
    SetGameState(GameState::PreGameCountdown);
    m_startGameButton.SetPressed(false);
}
if (m_highScoreButton.IsPressed())
{
    SetGameState(GameState::HighScoreDisplay);
    m_highScoreButton.SetPressed(false);
}

Управление игрой

Цикл игры и метод MarbleMazeMain::Update работают вместе, чтобы обновить состояние игровых объектов. Если игра принимает входные данные с нескольких устройств, вы можете накапливать входные данные со всех устройств в одном наборе переменных, чтобы можно было писать код, который проще поддерживать. Метод MarbleMazeMain::Update определяет один набор переменных, накапливающих перемещение со всех устройств.

float combinedTiltX = 0.0f;
float combinedTiltY = 0.0f;

Механизм ввода может отличаться от одного устройства ввода к другому. Например, входные данные указателя обрабатываются с помощью модели обработки событий среда выполнения Windows. И наоборот, вы опрашивуете входные данные из игрового контроллера при необходимости. Рекомендуется всегда следовать механизму ввода, который предписывается для данного устройства. В этом разделе описывается, как Marble Maze считывает входные данные с каждого устройства, как обновляет объединенные входные значения и как он использует объединенные входные значения для обновления состояния игры.

Обработка входных данных указателя

При работе с вводом указателя вызовите метод Windows::UI::Core:CoreDispatcher::P rocessEvents для обработки событий окна. Вызовите этот метод в цикле игры перед обновлением или отображением сцены. Marble Maze вызывает это в методе App::Run :

while (!m_windowClosed)
{
    if (m_windowVisible)
    {
        CoreWindow::GetForCurrentThread()->
            Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);

        m_main->Update();

        if (m_main->Render())
        {
            m_deviceResources->Present();
        }
    }
    else
    {
        CoreWindow::GetForCurrentThread()->
            Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
    }
}

Если окно видно, мы передаем CoreProcessEventsOption::P rocessAllIfPresentв ProcessEvents, чтобы обрабатывать все события очереди и возвращать немедленно; в противном случае мы передаём CoreProcessEventsOption::P rocessOneAndAllPending для обработки всех событий очереди и ожидания следующего нового события. После обработки событий отрисовывается Marble Maze и представляет следующий кадр.

Среда выполнения Windows вызывает зарегистрированный обработчик для каждого события, которое произошло. Метод App::SetWindow регистрирует события и перенаправляет сведения указателя на класс MarbleMazeMain .

void App::OnPointerPressed(
    Windows::UI::Core::CoreWindow^ sender, 
    Windows::UI::Core::PointerEventArgs^ args)
{
    m_main->AddTouch(args->CurrentPoint->PointerId, args->CurrentPoint->Position);
}

void App::OnPointerReleased(
    Windows::UI::Core::CoreWindow^ sender, 
    Windows::UI::Core::PointerEventArgs^ args)
{
    m_main->RemoveTouch(args->CurrentPoint->PointerId);
}

void App::OnPointerMoved(
    Windows::UI::Core::CoreWindow^ sender, 
    Windows::UI::Core::PointerEventArgs^ args)
{
    m_main->UpdateTouch(args->CurrentPoint->PointerId, args->CurrentPoint->Position);
}

Класс MarbleMazeMain реагирует на события указателя, обновив объект карты, содержащий события касания. Метод MarbleMazeMain::AddTouch вызывается при первом нажатии указателя, например при первоначальном нажатии указателя на экране на сенсорном устройстве. Метод MarbleMazeMain::UpdateTouch вызывается при перемещении позиции указателя. Метод MarbleMazeMain::RemoveTouch вызывается при выпуске указателя, например, когда пользователь перестает касаться экрана.

void MarbleMazeMain::AddTouch(int id, Windows::Foundation::Point point)
{
    m_touches[id] = PointToTouch(point, m_deviceResources->GetLogicalSize());

    m_pointQueue.push(D2D1::Point2F(point.X, point.Y));
}

void MarbleMazeMain::UpdateTouch(int id, Windows::Foundation::Point point)
{
    if (m_touches.find(id) != m_touches.end())
        m_touches[id] = PointToTouch(point, m_deviceResources->GetLogicalSize());
}

void MarbleMazeMain::RemoveTouch(int id)
{
    m_touches.erase(id);
}

Функция PointToTouch преобразует текущее положение указателя таким образом, чтобы источник находится в центре экрана, а затем масштабирует координаты таким образом, чтобы они диапазон примерно от –1,0 до +1,0. Это упрощает вычисление наклона лабиринта согласованным способом между различными входными методами.

inline XMFLOAT2 PointToTouch(Windows::Foundation::Point point, Windows::Foundation::Size bounds)
{
    float touchRadius = min(bounds.Width, bounds.Height);
    float dx = (point.X - (bounds.Width / 2.0f)) / touchRadius;
    float dy = ((bounds.Height / 2.0f) - point.Y) / touchRadius;

    return XMFLOAT2(dx, dy);
}

Метод MarbleMazeMain::Update обновляет объединенные входные значения путем увеличения коэффициента наклона по значению постоянного масштабирования. Это значение масштабирования было определено экспериментом с несколькими разными значениями.

// Account for touch input.
for (TouchMap::const_iterator iter = m_touches.cbegin(); 
    iter != m_touches.cend(); 
    ++iter)
{
    combinedTiltX += iter->second.x * m_touchScaleFactor;
    combinedTiltY += iter->second.y * m_touchScaleFactor;
}

Обработка входных данных акселерометра

Для обработки входных данных акселерометра метод MarbleMazeMain::Update вызывает метод Windows::D evices::Sensor::Accelerometer::GetCurrentReading . Этот метод возвращает объект Windows::D evices::Sensor::AccelerometerReading , представляющий чтение акселерометра. Свойства Windows::D evices::Sensor::AccelerometerReading::AccelerationX и Windows::D evices::Sensor::AccelerometerReading::AccelerationY удерживают ускорение g-force вдоль осей X и Y соответственно.

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

// Account for sensors.
if (m_accelerometer != nullptr)
{
    Windows::Devices::Sensors::AccelerometerReading^ reading =
        m_accelerometer->GetCurrentReading();

    if (reading != nullptr)
    {
        combinedTiltX += 
            static_cast<float>(reading->AccelerationX) * m_accelerometerScaleFactor;

        combinedTiltY += 
            static_cast<float>(reading->AccelerationY) * m_accelerometerScaleFactor;
    }
}

Так как вы не можете быть уверены, что акселерометр присутствует на компьютере пользователя, всегда убедитесь, что у вас есть допустимый объект Accelerometer перед опросом акселерометра.

Обработка входных данных игрового контроллера

В методе MarbleMazeMain::Update мы используем m_newReading для обработки входных данных из левой аналоговой палки:

float leftStickX = static_cast<float>(m_newReading.LeftThumbstickX);
float leftStickY = static_cast<float>(m_newReading.LeftThumbstickY);

auto oppositeSquared = leftStickY * leftStickY;
auto adjacentSquared = leftStickX * leftStickX;

if ((oppositeSquared + adjacentSquared) > m_deadzoneSquared)
{
    combinedTiltX += leftStickX * m_controllerScaleFactor;
    combinedTiltY += leftStickY * m_controllerScaleFactor;
}

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

Важно!

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

 

Применение входных данных к состоянию игры

Устройства сообщают входные значения различными способами. Например, входные данные указателя могут находиться в координатах экрана, а входные данные контроллера могут находиться в совершенно другом формате. Одной из проблем с объединением входных данных из нескольких устройств в один набор входных значений является нормализация или преобразование значений в общий формат. Marble Maze нормализует значения, масштабируя их до диапазона [-1.0, 1.0]. Функция PointToTouch , которая ранее описана в этом разделе, преобразует координаты экрана в нормализованные значения, которые варьируются примерно от -1.0 до +1.0.

Совет

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

 

После ввода метода MarbleMazeMazeMain::Update создается вектор, представляющий эффект наклона лабиринта на мраморе. В следующем примере показано, как Marble Maze использует функцию XMVector3Normalize для создания нормализованного вектора гравитации. Максимальная переменнаяTilt ограничивает объем, по которому лабиринт наклоняется и предотвращает наклон лабиринта на его стороне.

const float maxTilt = 1.0f / 8.0f;

XMVECTOR gravity = XMVectorSet(
    combinedTiltX * maxTilt, 
    combinedTiltY * maxTilt, 
    1.0f, 
    0.0f);

gravity = XMVector3Normalize(gravity);

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

XMFLOAT3A g;
XMStoreFloat3(&g, gravity);
m_physics.SetGravity(g);

if (m_gameState == GameState::InGameActive)
{
    // Only update physics when gameplay is active.
    m_physics.UpdatePhysicsSimulation(static_cast<float>(m_timer.GetElapsedSeconds()));

    // ...Code omitted for simplicity...

}

// ...Code omitted for simplicity...

// Check whether the marble fell off of the maze. 
const float fadeOutDepth = 0.0f;
const float resetDepth = 80.0f;
if (marblePosition.z >= fadeOutDepth)
{
    m_targetLightStrength = 0.0f;
}
if (marblePosition.z >= resetDepth)
{
    // Reset marble.
    memcpy(&marblePosition, &m_checkpoints[m_currentCheckpoint], sizeof(XMFLOAT3));
    oldMarblePosition = marblePosition;
    m_physics.SetPosition((const XMFLOAT3&)marblePosition);
    m_physics.SetVelocity(XMFLOAT3(0, 0, 0));
    m_lightStrength = 0.0f;
    m_targetLightStrength = 1.0f;

    m_resetCamera = true;
    m_resetMarbleRotation = true;
    m_audio.PlaySoundEffect(FallingEvent);
}

В этом разделе не описывается, как работает моделирование физики. Дополнительные сведения об этом см. в разделе "Физика.h " и "Физика.cpp " в источниках Marble Maze.

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

Ознакомьтесь с примером "Добавление звука в Marble Maze", чтобы узнать о некоторых ключевых методиках, которые следует учитывать при работе с звуком. В документе описывается, как Marble Maze использует Microsoft Media Foundation и XAudio2 для загрузки, смешивания и воспроизведения звуковых ресурсов.