Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
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 correspondente a este documento encontra-se no exemplo de jogo Marble Maze do DirectX .
Aqui estão alguns dos principais pontos que este documento discute quando você trabalha com inputs 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 eventos da janela, como quando o ponteiro é ativado, liberado e movido. Para inicializar o acelerômetro, crie um objeto Windows::Devices::Sensors::Accelerometer ao inicializar o aplicativo. Um controlador de jogo não requer inicialização.
Para jogos de um único jogador, considere se a entrada deve ser combinada de todos os controladores 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. No caso de toque, registre eventos de toque nas 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 touch 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 "toque" para se referir tanto à entrada por toque quanto à entrada do mouse, e "ponteiro" para se referir a qualquer dispositivo que utilize 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 especifica Paisagem como a única rotação com suporte para o jogo, a fim de evitar que a orientação seja alterada quando você gira o dispositivo para rolar a esfera. Para exibir o manifesto do pacote, abra o 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, você deve se registrar para eventos de janela, como quando o ponteiro é ativado (por exemplo, o jogador pressiona o botão do mouse ou toca na tela), liberado e movido. Para inicializar o acelerômetro, você precisa criar um objeto Windows::Devices::Sensors::Accelerometer ao inicializar o aplicativo.
O exemplo a seguir mostra como o método App::SetWindow se registra para os eventos de ponteiro Windows::UI::Core::CoreWindow::PointerPressed, Windows::UI::Core::CoreWindow::PointerReleased, e Windows::UI::Core::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 do aplicativo 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 realizar eventos de toque. A chave deste objeto de mapa é um valor que identifica exclusivamente o ponteiro de entrada. Cada chave corresponde à distância de cada ponto de toque até o centro da tela. O Marble Maze posteriormente usa esses valores para calcular a quantidade pela qual o labirinto está inclinado.
typedef std::map<int, XMFLOAT2> TouchMap;
TouchMap m_touches;
A classe MarbleMazeMain também contém um objeto acelerômetro.
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 um acelerômetro padrão, Accelerometer::GetDefault retornará nullptr.
// Returns accelerometer ref if there is one; nullptr otherwise.
m_accelerometer = Windows::Devices::Sensors::Accelerometer::GetDefault();
Navegando nos menus
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 maneira:
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 ao m_myGamepads; quando um gamepad é removido, verificamos se o gamepad está em m_myGamepads e, se for, o removemos. Em ambos os casos, definimos m_currentGamepadNeedsRefresh como verdadeiro, indicando que precisamos reatribuir m_gamepad.
Por fim, atribuímos um gamepad a m_gamepad e definimos m_currentGamepadNeedsRefresh para falso:
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;
}
Este método simplesmente retorna o último gamepad de 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 verifica o gamepad para entrada:
if (m_gamepad != nullptr)
{
m_oldReading = m_newReading;
m_newReading = m_gamepad->GetCurrentReading();
}
Monitoramos a leitura de entrada que obtivemos no último quadro com m_oldReadinge a leitura de entrada mais recente com m_newReading, que é obtida ao chamar 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 somente 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 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 membro m_pointQueue rastreia os locais em que o usuário tocou ou clicou na tela. A forma como o Marble Maze coleta os dados do ponteiro é descrita com mais detalhes posteriormente neste documento na seção Processamento de entrada do ponteiro.
// 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 a entrada de vários dispositivos, você poderá acumular a entrada de todos os dispositivos em um conjunto de variáveis para que você possa escrever um 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ê consulta os dados de entrada do controlador de jogo quando de que 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.
Processamento de entrada de ponteiro
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, passarmos CoreProcessEventsOption::ProcessAllIfPresent para ProcessEvents para processar todos os eventos enfileirados e retornar imediatamente; caso contrário, passarmos 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
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 ajusta a posição do ponteiro atual para que a origem esteja no centro da tela e, em seguida, ajusta 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 contêm a aceleração de 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 a bolinha se mova mais rapidamente.
// 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 da alavanca analógica esquerda está fora da zona morta e, se estiver, adicionamos ela a combinedTiltX e combinedTiltY, multiplicando por um fator de escala, para inclinar a plataforma.
Importante
Quando você trabalha com um controlador de jogo, sempre considere 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 considerar isso em seu jogo, crie uma zona de não movimentação para o movimento inicial do thumbstick. Para obter mais informações sobre a zona morta, consulte Lendo os thumbsticks.
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 os valores escalando-os para o 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 os dados de entrada, ele cria um vetor que representa o efeito da inclinação do labirinto na esfera. 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 bola de gude caiu pelo labirinto, o método MarbleMazeMain::Update coloca a bola de gude de volta no último ponto de verificação que ela 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 de física funciona. Para obter detalhes sobre isso, consulte Physics.h e Physics.cpp nas fontes do Marble Maze.
Próximas etapas
Leia a adição de áudio ao exemplo do Marble Maze para obter informações sobre algumas das principais práticas para ter em mente quando você trabalha 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.
Tópicos relacionados
- Adicionando áudio ao exemplo de Marble Maze
- Adicionando conteúdo visual ao exemplo do Marble Maze
- Desenvolvendo o Marble Maze, um jogo UWP no C++ e no DirectX