Compartilhar via


Adicionar entrada e interatividade ao exemplo do Marble Maze

Os jogos da Plataforma Universal do Windows (UWP) são executados em uma ampla variedade de dispositivos, como computadores desktop, laptops e tablets. Um dispositivo pode ter uma infinidade de mecanismos de entrada e controle. Este documento descreve as principais práticas para ter em mente quando você trabalha com dispositivos de entrada e mostra como o Marble Maze aplica essas práticas.

Observação

O código de exemplo que corresponde a este documento é encontrado no exemplo de jogo DirectX Marble Maze.

  Aqui estão alguns dos principais pontos que este documento discute quando você trabalha com a entrada em seu jogo:

  • Quando possível, dê suporte a vários dispositivos de entrada para permitir que seu jogo acomode uma gama maior de preferências e habilidades entre seus clientes. Embora o uso do controlador de jogo e do sensor seja opcional, é altamente recomendável aprimorar a experiência do jogador. Criamos o controlador de jogo e as APIs do sensor para ajudá-lo a integrar esses dispositivos de entrada com mais facilidade.

  • Para inicializar o toque, você deve registrar-se para eventos de janela, como quando o ponteiro é ativado, liberado e movido. Para inicializar o acelerômetro, crie um objeto Windows::D evices::Sensors::Accelerometer ao inicializar o aplicativo. Um controlador de jogo não requer inicialização.

  • Para jogos de um jogador, considere a possibilidade de combinar a entrada de todos os controles possíveis. Dessa forma, você não precisa acompanhar qual entrada vem de qual controlador. Ou, basta acompanhar a entrada apenas do controlador adicionado mais recentemente, como fazemos neste exemplo.

  • Processe eventos do Windows antes de processar dispositivos de entrada.

  • O controlador de jogo e o acelerômetro dão suporte à sondagem. Ou seja, você pode pesquisar dados quando precisar. Para toque, registre eventos de toque em estruturas de dados que estão disponíveis para o código de processamento de entrada.

  • Considere se os valores de entrada devem ser normalizados em um formato comum. Isso pode simplificar a maneira como a entrada é interpretada por outros componentes do seu jogo, como a simulação física, e pode facilitar a gravação de jogos que funcionam em diferentes resoluções de tela.

Dispositivos de entrada compatíveis com o Marble Maze

O Marble Maze dá suporte ao controlador de jogo, ao mouse e ao toque para selecionar itens de menu e ao controlador de jogo, ao mouse, ao toque e ao acelerômetro para controlar o jogo. O Marble Maze usa as APIs Windows::Gaming::Input para sondar o controlador para obter entrada. O toque permite que os aplicativos acompanhem e respondam à entrada da ponta do dedo. Um acelerômetro é um sensor que mede a força que é aplicada ao longo dos eixos X, Y e Z. Usando o Windows Runtime, você pode sondar o estado atual do dispositivo acelerômetro, bem como receber eventos de toque por meio do mecanismo de manipulação de eventos do Windows Runtime.

Observação

Este documento usa o toque para se referir à entrada e ponteiro do mouse e ao toque para se referir a qualquer dispositivo que use eventos de ponteiro. Como o toque e o mouse usam eventos de ponteiro padrão, você pode usar qualquer dispositivo para selecionar itens de menu e controlar o jogo.

 

Observação

O manifesto do pacote define Paisagem como a única rotação com suporte para o jogo para impedir que a orientação mude quando você gira o dispositivo para rolar a bolinha. Para exibir o manifesto do pacote, abra Package.appxmanifest no Gerenciador de Soluções no Visual Studio.

 

Inicializando dispositivos de entrada

O controlador de jogo não requer inicialização. Para inicializar o toque, é necessário registrar eventos de janelas, como quando o ponteiro é ativado (por exemplo, o jogador pressiona o botão do mouse ou toca a tela), liberado e movido. Para inicializar o acelerômetro, você precisa criar um objeto Windows::D evices::Sensors::Accelerometer ao inicializar o aplicativo.

O exemplo a seguir mostra como o método App::SetWindow registra os eventos de ponteiro Windows::UI::Core::CoreWindow::PointerPressed, Windows::UI::Core::CoreWindow::PointerReleased e Windows::UI::Core::CoreWindow::PointerMoved do método Windows::UI::CoreWindow::PointerMoved. Esses eventos são registrados durante a inicialização do aplicativo e antes do loop do jogo.

Esses eventos são tratados em um thread separado que invoca os manipuladores de eventos.

Para obter mais informações sobre como o aplicativo é inicializado, consulte Estrutura de aplicação do 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);

A classe MarbleMazeMain também cria um objeto std::map para manter eventos de toque. A chave para esse objeto de mapa é um valor que identifica exclusivamente o ponteiro de entrada. Cada tecla mapeia a distância entre cada ponto de toque e o centro da tela. Posteriormente, o Marble Maze usa esses valores para calcular a quantidade de inclinação do labirinto.

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

A classe MarbleMazeMain também contém um objeto Accelerometer.

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

O objeto Accelerometer é inicializado no construtor MarbleMazeMain, conforme mostrado no exemplo a seguir. O método Windows::Devices::Sensors::Accelerometer::GetDefault retorna uma instância do acelerômetro padrão. Se não houver nenhum acelerômetro padrão, Accelerometer::GetDefault retornará nullptr.

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

Você pode usar o mouse, o toque ou um controlador de jogo para navegar pelos menus da seguinte maneira:

  • Use o painel direcional para alterar o item de menu ativo.
  • Use o toque, o botão A ou o botão Menu para escolher um item de menu ou fechar o menu atual, como a tabela de pontuação alta.
  • Use o botão Menu para pausar ou retomar o jogo.
  • Clique em um item de menu com o mouse para escolher essa ação.

Acompanhamento da entrada do controlador de jogo

Para controlar os gamepads atualmente conectados ao dispositivo, MarbleMazeMain define uma variável membro, m_myGamepads, que é uma coleção de objetos Windows::Gaming::Input::Gamepad. Isso é inicializado no construtor da seguinte forma:

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

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

Além disso, o construtor MarbleMazeMain registra eventos para quando os gamepads são adicionados ou removidos:

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

Quando um gamepad é adicionado, ele é adicionado a m_myGamepads, quando um gamepad é removido, verificamos se o gamepad está em m_myGamepads e, se estiver, o removemos. Em ambos os casos, definimos m_currentGamepadNeedsRefresh como true, indicando que precisamos reatribuir m_gamepad.

Finalmente, atribuímos um gamepad a m_gamepad e definimos m_currentGamepadNeedsRefresh como false:

m_gamepad = GetLastGamepad();
m_currentGamepadNeedsRefresh = false;

No método Update, verificamos se m_gamepad precisa ser reatribuído:

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

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

    m_currentGamepadNeedsRefresh = false;
}

Se m_gamepad precisar ser reatribuído, atribuiremos a ele o gamepad adicionado mais recentemente, usando GetLastGamepad, que é definido da seguinte maneira:

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

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

    return gamepad;
}

Esse método simplesmente retorna o último gamepad em m_myGamepads.

Você pode conectar até quatro controladores de jogo a um dispositivo Windows 10. Para evitar ter que descobrir qual controlador é o ativo, simplesmente controlamos o gamepad adicionado mais recentemente. Se o jogo der suporte a mais de um jogador, você precisará acompanhar a entrada de cada jogador separadamente.

O método MarbleMazeMain::Update sonda o gamepad para entrada:

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

Acompanhamos a leitura de entrada que obtivemos no último quadro com m_oldReadinge a leitura de entrada mais recente com m_newReading, que obtemos chamando Gamepad::GetCurrentReading. Isso retorna um objeto GamepadReading, que contém informações sobre o estado atual do gamepad.

Para verificar se um botão foi pressionado ou liberado, definimos MarbleMazeMain::ButtonJustPressed e MarbleMazeMain::ButtonJustReleased, que comparam as leituras de botão desse quadro e do último quadro. Dessa forma, podemos executar uma ação apenas no momento em que um botão é inicialmente pressionado ou liberado, e não quando ele é mantido:

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

As leituras de GamepadButtons são comparadas usando operações bit a bit— verificamos se um botão é pressionado usando bit a bit e (&). Determinamos se um botão foi pressionado ou liberado comparando a leitura antiga e a nova leitura.

Usando os métodos acima, verificamos se determinados botões foram pressionados e executamos as ações correspondentes que devem acontecer. Por exemplo, quando o botão Menu (GamepadButtons::Menu) é pressionado, o estado do jogo muda de ativo para pausado ou pausado para ativo.

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

Também verificamos se o jogador pressiona o botão Exibir, nesse caso, reiniciamos o jogo ou limpamos a tabela de pontuação alta:

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

Se o menu principal estiver ativo, o item de menu ativo será alterado quando o painel direcional for pressionado para cima ou para baixo. Se o usuário escolher a seleção atual, o elemento de interface do usuário apropriado será marcado como sendo escolhido.

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

Acompanhamento de toque e entrada do mouse

Para entrada por toque e mouse, um item de menu é escolhido quando o usuário toca ou clica nele. O exemplo a seguir mostra como o método MarbleMazeMain::Update processa a entrada de ponteiro para selecionar itens de menu. A variável do membro m_pointQueue rastreia os locais em que o usuário tocou ou clicou na tela. A maneira como o Marble Maze coleta a entrada do ponteiro é descrita com mais detalhes posteriormente neste documento na seção Entrada do ponteiro de processamento.

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

O método UserInterface::HitTest determina se o ponto fornecido está localizado nos limites de qualquer elemento de interface do usuário. Todos os elementos de interface do usuário que passam nesse teste são marcados como sendo tocados. Esse método usa a função auxiliar PointInRect para determinar se o ponto fornecido está localizado nos limites de cada elemento de interface do usuário.

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

Atualizando o estado do jogo

Depois que o método MarbleMazeMain::Update processa o controlador e a entrada por toque, ele atualiza o estado do jogo se algum botão foi pressionado.

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

Controlando o jogo

O loop do jogo e o método MarbleMazeMain::Update funcionam juntos para atualizar o estado dos objetos do jogo. Se o jogo aceitar entrada de vários dispositivos, você poderá acumular a entrada de todos os dispositivos em um conjunto de variáveis para que possa escrever código mais fácil de manter. O método MarbleMazeMain::Update define um conjunto de variáveis que acumula movimentação de todos os dispositivos.

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

O mecanismo de entrada pode variar de um dispositivo de entrada para outro. Por exemplo, a entrada de ponteiro é manipulada usando o modelo de manipulação de eventos do Windows Runtime. Por outro lado, você pesquisa dados de entrada do controlador de jogo quando precisa. Recomendamos que você sempre siga o mecanismo de entrada que é prescrito para um determinado dispositivo. Esta seção descreve como o Marble Maze lê a entrada de cada dispositivo, como atualiza os valores de entrada combinados e como ele usa os valores de entrada combinados para atualizar o estado do jogo.

Entrada do ponteiro de processamento

Quando você trabalha com a entrada de ponteiro, chame o método Windows::UI::Core::CoreDispatcher::ProcessEvents para processar eventos de janela. Chame esse método no loop do jogo antes de atualizar ou renderizar a cena. O Marble Maze chama isso no método 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);
    }
}

Se a janela estiver visível, passaremos CoreProcessEventsOption::ProcessAllIfPresent para ProcessEvents para processar todos os eventos enfileirados e retornaremos imediatamente; caso contrário, passaremos CoreProcessEventsOption::ProcessOneAndAllPending para processar todos os eventos enfileirados e aguardar o próximo novo evento. Depois que os eventos são processados, o Marble Maze renderiza e apresenta o próximo quadro.

O Windows Runtime chama o manipulador registrado para cada evento que ocorreu. O método App::SetWindow registra eventos e encaminha informações de ponteiro para a classe 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);
}

A classe MarbleMazeMain reage a eventos de ponteiro atualizando o objeto de mapa que contém eventos de toque. O método MarbleMazeMain::AddTouch é chamado quando o ponteiro é pressionado pela primeira vez, por exemplo, quando o usuário inicialmente toca na tela em um dispositivo habilitado para toque. O método MarbleMazeMain::UpdateTouch é chamado quando a posição do ponteiro se move. O método MarbleMazeMain::RemoveTouch é chamado quando o ponteiro é liberado, por exemplo, quando o usuário para de tocar na tela.

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

A função PointToTouch converte a posição atual do ponteiro, para que a origem esteja no centro da tela e dimensiona as coordenadas para que variem aproximadamente entre -1,0 e +1,0. Isso facilita o cálculo da inclinação do labirinto de maneira consistente entre diferentes métodos de entrada.

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

O método MarbleMazeMain::Update atualiza os valores de entrada combinados incrementando o fator de inclinação por um valor de dimensionamento constante. Esse valor de dimensionamento foi determinado experimentando vários valores diferentes.

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

Entrada do acelerômetro de processamento

Para processar a entrada do acelerômetro, o método MarbleMazeMain::Update chama o método Windows::Devices::Sensors::Accelerometer::GetCurrentReading. Esse método retorna um objeto Windows::Devices::Sensors::AccelerometerReading, que representa uma leitura de acelerômetro. As propriedades Windows::Devices::Sensors::AccelerometerReading::AccelerationX e Windows::Devices::Sensors::AccelerometerReading::AccelerationY armazenam a aceleração da força-g ao longo dos eixos X e Y, respectivamente.

O exemplo a seguir mostra como o método MarbleMazeMain::Update sonda o acelerômetro e atualiza os valores de entrada combinados. À medida que você inclina o dispositivo, a gravidade faz com que o mármore se mova mais rápido.

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

Como você não pode ter certeza de que um acelerômetro está presente no computador do usuário, sempre verifique se você tem um objeto Accelerometer válido antes de sondar o acelerômetro.

Processamento de entrada do controlador de jogo

No método MarbleMazeMain::Update, usamos m_newReading para processar a entrada do bastão analógico esquerdo:

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

Verificamos se a entrada do bastão analógico esquerdo está fora da zona morta e, se estiver, a adicionamos a combinedTiltX e combinedTiltY (multiplicado por um fator de escala) para inclinar o estágio.

Importante

Quando você trabalha com um controlador de jogo, sempre conta para a zona morta. A zona morta refere-se à variação entre os gamepads em sua sensibilidade ao movimento inicial. Em alguns controladores, um pequeno movimento pode não gerar leitura, mas em outros pode gerar uma leitura mensurável. Para explicar isso em seu jogo, crie uma zona de não movimento para o movimento inicial do polegar. Para obter mais informações sobre a zona morta, consulte Lendo os polegares.

 

Aplicando entrada ao estado do jogo

Os dispositivos relatam valores de entrada de diferentes maneiras. Por exemplo, a entrada do ponteiro pode estar em coordenadas de tela e a entrada do controlador pode estar em um formato completamente diferente. Um desafio com a combinação de entrada de vários dispositivos em um conjunto de valores de entrada é a normalização ou a conversão de valores em um formato comum. O Marble Maze normaliza valores dimensionando-os no intervalo [-1,0, 1,0]. A função PointToTouch, descrita anteriormente nesta seção, converte coordenadas de tela em valores normalizados que variam aproximadamente entre -1.0 e +1.0.

Dica

Mesmo que seu aplicativo use um método de entrada, recomendamos que você sempre normalize os valores de entrada. Isso pode simplificar a maneira como a entrada é interpretada por outros componentes do seu jogo, como a simulação física, e facilita a gravação de jogos que funcionam em diferentes resoluções de tela.

 

Depois que o método MarbleMazeMain::Update processa a entrada, ele cria um vetor que representa o efeito da inclinação do labirinto na bolinha. O exemplo a seguir mostra como o Marble Maze usa a função XMVector3Normalize para criar um vetor de gravidade normalizado. A variável maxTilt restringe a quantidade pela qual o labirinto inclina e impede que o labirinto se incline de lado.

const float maxTilt = 1.0f / 8.0f;

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

gravity = XMVector3Normalize(gravity);

Para concluir a atualização de objetos de cena, o Marble Maze passa o vetor de gravidade atualizado para a simulação física, atualiza a simulação física para o tempo decorrido desde o quadro anterior e atualiza a posição e a orientação do mármore. Se a bolinha caiu pelo labirinto, o método MarbleMazeMain::Update coloca a bolinha de volta no último ponto de verificação em que a bolinha tocou e redefine o estado da simulação física.

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

Esta seção não descreve como a simulação física funciona. Para saber mais sobre esse assunto, consulte os arquivos Physics.h e Physics.cpp nas origens do Marble Maze.

Próximas etapas

Leia Adicionar áudio ao exemplo do Marble Maze para obter informações sobre algumas das principais práticas a serem consideradas ao trabalhar com áudio. O documento discute como o Marble Maze usa o Microsoft Media Foundation e o XAudio2 para carregar, misturar e reproduzir recursos de áudio.