Controles de movimento e aparência para jogos
Saiba como adicionar controles tradicionais de movimento e aparência do mouse e do teclado (também conhecidos como controles de aparência do mouse) ao seu jogo DirectX.
Também discutimos o suporte move-look para dispositivos de toque, com o controlador de movimento definido como a seção inferior esquerda da tela que se comporta como uma entrada direcional e o controlador de olhar definido para o restante da tela, com a câmera centralizada no último lugar que o jogador tocou nessa área.
Se este é um conceito de controle desconhecido para você, pense desta forma: o teclado (ou a caixa de entrada direcional baseada em toque) controla suas pernas neste espaço 3D e se comporta como se suas pernas só fossem capazes de se mover para frente ou para trás, ou metralhar para a esquerda e para a direita. O mouse (ou ponteiro de toque) controla sua cabeça. Você usa sua cabeça para olhar em uma direção - esquerda ou direita, para cima ou para baixo, ou em algum lugar nesse plano. Se houver um alvo em sua exibição, você deve usar o mouse para centralizar a visão da câmera nesse alvo e, em seguida, pressionar a tecla avançar para se mover em direção a ele ou voltar para se afastar dele. Para circundar o alvo, você deve manter a visão da câmera centralizada no alvo e mover para a esquerda ou para a direita ao mesmo tempo. Você pode ver como este é um método de controle muito eficaz para navegar em ambientes 3D!
Esses controles são comumente conhecidos como controles WASD em jogos, onde as teclas W, A, S e D são usadas para o movimento fixo da câmera no plano x-z, e o mouse é usado para controlar a rotação da câmera em torno dos eixos x e y.
Objetivos
- Adicione controles básicos de movimento e aparência ao seu jogo DirectX para mouse e teclado e telas sensíveis ao toque.
- Implemente uma câmera em primeira pessoa usada para navegar em um ambiente 3D.
Uma observação sobre implementações de controle de toque
Para controles de toque, implementamos dois controladores: o controlador de movimento, que lida com o movimento no plano x-z em relação ao ponto de visão da câmera; e o controlador de visão, que aponta o ponto de visão da câmera. Nosso controlador de movimento é mapeado para os botões WASD do teclado e o controlador de aparência é mapeado para o mouse. Mas para controles de toque, precisamos definir uma região da tela que serve como entradas direcionais ou os botões WASD virtuais, com o restante da tela servindo como espaço de entrada para os controles de aparência.
Nossa tela é assim.
Quando você move o ponteiro de toque (não o mouse!) no canto inferior esquerdo da tela, qualquer movimento para cima fará com que a câmera avance. Qualquer movimento para baixo fará com que a câmera se mova para trás. O mesmo vale para o movimento esquerdo e direito dentro do espaço do ponteiro do controlador de movimento. Fora desse espaço, e ele se torna um controlador de olhar - você apenas toca ou arrasta a câmera para onde você gostaria que ela ficasse de frente.
Configurar a infraestrutura básica de eventos de entrada
Primeiro, devemos criar nossa classe de controle que usamos para lidar com eventos de entrada do mouse e do teclado e atualizar a perspectiva da câmera com base nessa entrada. Como estamos implementando controles move-look, nós o chamamos de MoveLookController.
using namespace Windows::UI::Core;
using namespace Windows::System;
using namespace Windows::Foundation;
using namespace Windows::Devices::Input;
#include <DirectXMath.h>
// Methods to get input from the UI pointers
ref class MoveLookController
{
}; // class MoveLookController
Agora, vamos criar um cabeçalho que define o estado do controlador move-look e sua câmera em primeira pessoa, além dos métodos básicos e manipuladores de eventos que implementam os controles e que atualizam o estado da câmera.
#define ROTATION_GAIN 0.004f // Sensitivity adjustment for the look controller
#define MOVEMENT_GAIN 0.1f // Sensitivity adjustment for the move controller
ref class MoveLookController
{
private:
// Properties of the controller object
DirectX::XMFLOAT3 m_position; // The position of the controller
float m_pitch, m_yaw; // Orientation euler angles in radians
// Properties of the Move control
bool m_moveInUse; // Specifies whether the move control is in use
uint32 m_movePointerID; // Id of the pointer in this control
DirectX::XMFLOAT2 m_moveFirstDown; // Point where initial contact occurred
DirectX::XMFLOAT2 m_movePointerPosition; // Point where the move pointer is currently located
DirectX::XMFLOAT3 m_moveCommand; // The net command from the move control
// Properties of the Look control
bool m_lookInUse; // Specifies whether the look control is in use
uint32 m_lookPointerID; // Id of the pointer in this control
DirectX::XMFLOAT2 m_lookLastPoint; // Last point (from last frame)
DirectX::XMFLOAT2 m_lookLastDelta; // For smoothing
bool m_forward, m_back; // States for movement
bool m_left, m_right;
bool m_up, m_down;
public:
// Methods to get input from the UI pointers
void OnPointerPressed(
_In_ Windows::UI::Core::CoreWindow^ sender,
_In_ Windows::UI::Core::PointerEventArgs^ args
);
void OnPointerMoved(
_In_ Windows::UI::Core::CoreWindow^ sender,
_In_ Windows::UI::Core::PointerEventArgs^ args
);
void OnPointerReleased(
_In_ Windows::UI::Core::CoreWindow^ sender,
_In_ Windows::UI::Core::PointerEventArgs^ args
);
void OnKeyDown(
_In_ Windows::UI::Core::CoreWindow^ sender,
_In_ Windows::UI::Core::KeyEventArgs^ args
);
void OnKeyUp(
_In_ Windows::UI::Core::CoreWindow^ sender,
_In_ Windows::UI::Core::KeyEventArgs^ args
);
// Set up the Controls that this controller supports
void Initialize( _In_ Windows::UI::Core::CoreWindow^ window );
void Update( Windows::UI::Core::CoreWindow ^window );
internal:
// Accessor to set position of controller
void SetPosition( _In_ DirectX::XMFLOAT3 pos );
// Accessor to set position of controller
void SetOrientation( _In_ float pitch, _In_ float yaw );
// Returns the position of the controller object
DirectX::XMFLOAT3 get_Position();
// Returns the point which the controller is facing
DirectX::XMFLOAT3 get_LookPoint();
}; // class MoveLookController
Nosso código contém 4 grupos de campos privados. Vamos rever o propósito de cada um.
Primeiro, definimos alguns campos úteis que contêm nossas informações atualizadas sobre a visualização da câmera.
- m_position é a posição da câmera (e, portanto, do plano de visualização) na cena 3D, usando coordenadas de cena.
- m_pitch é a inclinação da câmera, ou sua rotação para cima e para baixo em torno do eixo x do plano de visualização, em radianos.
- m_yaw é a guinada da câmera, ou sua rotação esquerda-direita em torno do eixo y do plano de visão, em radianos.
Agora, vamos definir os campos que usamos para armazenar informações sobre o status e a posição de nossos controladores. Primeiro, definiremos os campos necessários para nosso controlador de movimento baseado em toque. (Não há nada de especial necessário para a implementação do teclado do controlador de movimento. Acabamos de ler eventos de teclado com manipuladores específicos.)
- m_moveInUse indica se o controlador de movimento está em uso.
- m_movePointerID é a ID exclusiva do ponteiro de movimento atual. Nós o usamos para diferenciar entre o ponteiro de aparência e o ponteiro de movimento quando verificamos o valor da ID do ponteiro.
- m_moveFirstDown é o ponto na tela onde o jogador tocou pela primeira vez a área do ponteiro do controlador de movimento. Usamos esse valor posteriormente para definir uma zona morta para impedir que pequenos movimentos tremam na exibição.
- m_movePointerPosition é o ponto na tela para o qual o jogador moveu o ponteiro. Nós o usamos para determinar em que direção o jogador queria se mover, examinando-o em relação a m_moveFirstDown.
- m_moveCommand é o comando calculado final para o controlador de movimento: para cima (para frente), para baixo (para trás), para a esquerda ou para a direita.
Agora, definimos os campos que usamos para nosso controlador de aparência, tanto o mouse quanto as implementações de toque.
- m_lookInUse indica se o controle de aparência está em uso.
- m_lookPointerID é a ID exclusiva do ponteiro de aparência atual. Nós o usamos para diferenciar entre o ponteiro de aparência e o ponteiro de movimento quando verificamos o valor da ID do ponteiro.
- m_lookLastPoint é o último ponto, nas coordenadas da cena, que foi capturado no quadro anterior.
- m_lookLastDelta é a diferença calculada entre o m_position atual e o m_lookLastPoint.
Finalmente, definimos 6 valores booleanos para os 6 graus de movimento, que usamos para indicar o estado atual de cada ação de movimento direcional (ligado ou desligado):
- m_forward, m_back, m_left, m_right, m_up e m_down.
Usamos os 6 manipuladores de eventos para capturar os dados de entrada que usamos para atualizar o estado de nossos controladores:
- OnPointerPressed. O jogador pressionou o botão esquerdo do mouse com o ponteiro na tela do jogo ou tocou na tela.
- OnPointerMoved. O jogador moveu o mouse com o ponteiro na tela do jogo ou arrastou o ponteiro de toque na tela.
- OnPointerReleased. O jogador soltou o botão esquerdo do mouse com o ponteiro na tela do jogo ou parou de tocar na tela.
- OnKeyDown. O jogador pressionou uma tecla.
- OnKeyUp. O jogador soltou uma chave.
E, finalmente, usamos esses métodos e propriedades para inicializar, acessar e atualizar as informações de estado dos controladores.
- Inicializar. Nosso aplicativo chama esse manipulador de eventos para inicializar os controles e anexá-los ao objeto CoreWindow que descreve nossa janela de exibição.
- SetPosition. Nosso aplicativo chama esse método para definir as coordenadas (x, y e z) de nossos controles no espaço da cena.
- SetOrientation. Nosso aplicativo chama esse método para definir a inclinação e a guinada da câmera.
- get_Position. Nosso aplicativo acessa essa propriedade para obter a posição atual da câmera no espaço da cena. Você usa essa propriedade como o método de comunicar a posição atual da câmera ao aplicativo.
- get_LookPoint. Nosso aplicativo acessa essa propriedade para obter o ponto atual para o qual a câmera do controlador está voltada.
- Atualizar. Lê o estado dos controladores de movimento e aparência e atualiza a posição da câmera. Você chama continuamente esse método do loop principal do aplicativo para atualizar os dados do controlador da câmera e a posição da câmera no espaço da cena.
Agora, você tem aqui todos os componentes necessários para implementar seus controles move-look. Então, vamos conectar essas peças.
Criar os eventos de entrada básicos
O dispatcher de eventos do Tempo de Execução do Windows fornece 5 eventos que queremos que as instâncias da classe MoveLookController manipulem:
Esses eventos são implementados no tipo CoreWindow . Supomos que você tenha um objeto CoreWindow para trabalhar. Se você não souber como obter um, consulte Como configurar seu aplicativo C++ da Plataforma Universal do Windows (UWP) para exibir um modo de exibição DirectX.
À medida que esses eventos são acionados enquanto nosso aplicativo está em execução, os manipuladores atualizam as informações de estado dos controladores definidas em nossos campos privados.
Primeiro, vamos preencher os manipuladores de eventos do mouse e do ponteiro de toque. No primeiro manipulador de eventos, OnPointerPressed(), obtemos as coordenadas x-y do ponteiro do CoreWindow que gerencia nossa exibição quando o usuário clica com o mouse ou toca na tela na região do controlador de aparência.
OnPointerPressed
void MoveLookController::OnPointerPressed(
_In_ CoreWindow^ sender,
_In_ PointerEventArgs^ args)
{
// Get the current pointer position.
uint32 pointerID = args->CurrentPoint->PointerId;
DirectX::XMFLOAT2 position = DirectX::XMFLOAT2( args->CurrentPoint->Position.X, args->CurrentPoint->Position.Y );
auto device = args->CurrentPoint->PointerDevice;
auto deviceType = device->PointerDeviceType;
if ( deviceType == PointerDeviceType::Mouse )
{
// Action, Jump, or Fire
}
// Check if this pointer is in the move control.
// Change the values to percentages of the preferred screen resolution.
// You can set the x value to <preferred resolution> * <percentage of width>
// for example, ( position.x < (screenResolution.x * 0.15) ).
if (( position.x < 300 && position.y > 380 ) && ( deviceType != PointerDeviceType::Mouse ))
{
if ( !m_moveInUse ) // if no pointer is in this control yet
{
// Process a DPad touch down event.
m_moveFirstDown = position; // Save the location of the initial contact.
m_movePointerPosition = position;
m_movePointerID = pointerID; // Store the id of the pointer using this control.
m_moveInUse = TRUE;
}
}
else // This pointer must be in the look control.
{
if ( !m_lookInUse ) // If no pointer is in this control yet...
{
m_lookLastPoint = position; // save the point for later move
m_lookPointerID = args->CurrentPoint->PointerId; // store the id of pointer using this control
m_lookLastDelta.x = m_lookLastDelta.y = 0; // these are for smoothing
m_lookInUse = TRUE;
}
}
}
Esse manipulador de eventos verifica se o ponteiro não é o mouse (para os fins deste exemplo, que dá suporte ao mouse e ao toque) e se ele está na área do controlador de movimentação. Se ambos os critérios forem verdadeiros, ele verificará se o ponteiro foi pressionado, especificamente, se esse clique não está relacionado a um movimento anterior ou entrada de aparência, testando se m_moveInUse é falso. Nesse caso, o manipulador captura o ponto na área do controlador de movimento em que o pressionamento ocorreu e define m_moveInUse como true, para que, quando esse manipulador for chamado novamente, ele não substitua a posição inicial da interação de entrada do controlador de movimento. Ele também atualiza a ID do ponteiro do controlador de movimento para a ID do ponteiro atual.
Se o ponteiro for o mouse ou se o ponteiro de toque não estiver na área do controlador de movimento, ele deverá estar na área do controlador de aparência. Ele define m_lookLastPoint para a posição atual em que o usuário pressionou o botão do mouse ou tocou e pressionou, redefine o delta e atualiza a ID do ponteiro do controlador de visão para a ID do ponteiro atual. Ele também define o estado do controlador de aparência como ativo.
OnPointerMoved
void MoveLookController::OnPointerMoved(
_In_ CoreWindow ^sender,
_In_ PointerEventArgs ^args)
{
uint32 pointerID = args->CurrentPoint->PointerId;
DirectX::XMFLOAT2 position = DirectX::XMFLOAT2(args->CurrentPoint->Position.X, args->CurrentPoint->Position.Y);
// Decide which control this pointer is operating.
if (pointerID == m_movePointerID) // This is the move pointer.
{
// Move control
m_movePointerPosition = position; // Save the current position.
}
else if (pointerID == m_lookPointerID) // This is the look pointer.
{
// Look control
DirectX::XMFLOAT2 pointerDelta;
pointerDelta.x = position.x - m_lookLastPoint.x; // How far did pointer move
pointerDelta.y = position.y - m_lookLastPoint.y;
DirectX::XMFLOAT2 rotationDelta;
rotationDelta.x = pointerDelta.x * ROTATION_GAIN; // Scale for control sensitivity.
rotationDelta.y = pointerDelta.y * ROTATION_GAIN;
m_lookLastPoint = position; // Save for the next time through.
// Update our orientation based on the command.
m_pitch -= rotationDelta.y; // Mouse y increases down, but pitch increases up.
m_yaw -= rotationDelta.x; // Yaw is defined as CCW around the y-axis.
// Limit the pitch to straight up or straight down.
m_pitch = (float)__max(-DirectX::XM_PI / 2.0f, m_pitch);
m_pitch = (float)__min(+DirectX::XM_PI / 2.0f, m_pitch);
}
}
O manipulador de eventos OnPointerMoved é acionado sempre que o ponteiro se move (nesse caso, se um ponteiro da tela sensível ao toque estiver sendo arrastado ou se o ponteiro do mouse estiver sendo movido enquanto o botão esquerdo é pressionado). Se a ID do ponteiro for igual à ID do ponteiro do controlador de movimento, será o ponteiro de movimento; Caso contrário, verificamos se é o controlador de aparência que é o ponteiro ativo.
Se for o controlador de movimento, apenas atualizamos a posição do ponteiro. Continuamos atualizando-o enquanto o evento PointerMoved continua sendo disparado, porque queremos comparar a posição final com a primeira que capturamos com o manipulador de eventos OnPointerPressed.
Se for o controlador de aparência, as coisas são um pouco mais complicadas. Precisamos calcular um novo ponto de visão e centralizar a câmera nele, então calculamos o delta entre o último ponto de visão e a posição atual da tela e, em seguida, multiplicamos pelo nosso fator de escala, que podemos ajustar para tornar os movimentos de visão menores ou maiores em relação à distância do movimento da tela. Usando esse valor, calculamos a inclinação e a guinada.
Por fim, precisamos desativar os comportamentos do controlador de movimento ou aparência quando o jogador para de mover o mouse ou tocar na tela. Usamos OnPointerReleased, que chamamos quando PointerReleased é disparado, para definir m_moveInUse ou m_lookInUse como FALSE e desativar o movimento panorâmico da câmera e zerar a ID do ponteiro.
OnPointerReleased
void MoveLookController::OnPointerReleased(
_In_ CoreWindow ^sender,
_In_ PointerEventArgs ^args)
{
uint32 pointerID = args->CurrentPoint->PointerId;
DirectX::XMFLOAT2 position = DirectX::XMFLOAT2( args->CurrentPoint->Position.X, args->CurrentPoint->Position.Y );
if ( pointerID == m_movePointerID ) // This was the move pointer.
{
m_moveInUse = FALSE;
m_movePointerID = 0;
}
else if (pointerID == m_lookPointerID ) // This was the look pointer.
{
m_lookInUse = FALSE;
m_lookPointerID = 0;
}
}
Até agora, lidamos com todos os eventos da tela sensível ao toque. Agora, vamos lidar com os eventos de entrada de teclas para um controlador de movimento baseado em teclado.
OnKeyDown
void MoveLookController::OnKeyDown(
__in CoreWindow^ sender,
__in KeyEventArgs^ args )
{
Windows::System::VirtualKey Key;
Key = args->VirtualKey;
// Figure out the command from the keyboard.
if ( Key == VirtualKey::W ) // Forward
m_forward = true;
if ( Key == VirtualKey::S ) // Back
m_back = true;
if ( Key == VirtualKey::A ) // Left
m_left = true;
if ( Key == VirtualKey::D ) // Right
m_right = true;
}
Desde que uma dessas teclas seja pressionada, esse manipulador de eventos define o estado de movimento direcional correspondente como true.
OnKeyUp
void MoveLookController::OnKeyUp(
__in CoreWindow^ sender,
__in KeyEventArgs^ args)
{
Windows::System::VirtualKey Key;
Key = args->VirtualKey;
// Figure out the command from the keyboard.
if ( Key == VirtualKey::W ) // forward
m_forward = false;
if ( Key == VirtualKey::S ) // back
m_back = false;
if ( Key == VirtualKey::A ) // left
m_left = false;
if ( Key == VirtualKey::D ) // right
m_right = false;
}
E quando a chave é liberada, esse manipulador de eventos a define de volta como false. Quando chamamos Update, ele verifica esses estados de movimento direcional e move a câmera de acordo. Isso é um pouco mais simples do que a implementação de toque!
Inicializar os controles de toque e o estado do controlador
Vamos conectar os eventos agora e inicializar todos os campos de estado do controlador.
Initialize
void MoveLookController::Initialize( _In_ CoreWindow^ window )
{
// Opt in to receive touch/mouse events.
window->PointerPressed +=
ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &MoveLookController::OnPointerPressed);
window->PointerMoved +=
ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &MoveLookController::OnPointerMoved);
window->PointerReleased +=
ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &MoveLookController::OnPointerReleased);
window->CharacterReceived +=
ref new TypedEventHandler<CoreWindow^, CharacterReceivedEventArgs^>(this, &MoveLookController::OnCharacterReceived);
window->KeyDown +=
ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(this, &MoveLookController::OnKeyDown);
window->KeyUp +=
ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(this, &MoveLookController::OnKeyUp);
// Initialize the state of the controller.
m_moveInUse = FALSE; // No pointer is in the Move control.
m_movePointerID = 0;
m_lookInUse = FALSE; // No pointer is in the Look control.
m_lookPointerID = 0;
// Need to init this as it is reset every frame.
m_moveCommand = DirectX::XMFLOAT3( 0.0f, 0.0f, 0.0f );
SetOrientation( 0, 0 ); // Look straight ahead when the app starts.
}
Initialize usa uma referência à instância CoreWindow do aplicativo como um parâmetro e registra os manipuladores de eventos que desenvolvemos para os eventos apropriados nesse CoreWindow. Ele inicializa as IDs do ponteiro de movimento e aparência, define o vetor de comando para a implementação do controlador de movimento da tela sensível ao toque como zero e define a câmera olhando para a frente quando o aplicativo é iniciado.
Obtendo e definindo a posição e a orientação da câmera
Vamos definir alguns métodos para obter e definir a posição da câmera em relação à janela de visualização.
void MoveLookController::SetPosition( _In_ DirectX::XMFLOAT3 pos )
{
m_position = pos;
}
// Accessor to set the position of the controller.
void MoveLookController::SetOrientation( _In_ float pitch, _In_ float yaw )
{
m_pitch = pitch;
m_yaw = yaw;
}
// Returns the position of the controller object.
DirectX::XMFLOAT3 MoveLookController::get_Position()
{
return m_position;
}
// Returns the point at which the camera controller is facing.
DirectX::XMFLOAT3 MoveLookController::get_LookPoint()
{
float y = sinf(m_pitch); // Vertical
float r = cosf(m_pitch); // In the plane
float z = r*cosf(m_yaw); // Fwd-back
float x = r*sinf(m_yaw); // Left-right
DirectX::XMFLOAT3 result(x,y,z);
result.x += m_position.x;
result.y += m_position.y;
result.z += m_position.z;
// Return m_position + DirectX::XMFLOAT3(x, y, z);
return result;
}
Atualizando as informações de estado do controlador
Agora, realizamos nossos cálculos que convertem as informações de coordenadas do ponteiro rastreadas em m_movePointerPosition em novas informações de coordenadas respectivas do nosso sistema de coordenadas mundiais. Nosso aplicativo chama esse método toda vez que atualizamos o loop do aplicativo principal. Portanto, é aqui que calculamos as novas informações de posição do ponto de visão que queremos passar para o aplicativo para atualizar a matriz de exibição antes da projeção na janela de exibição.
void MoveLookController::Update(CoreWindow ^window)
{
// Check for input from the Move control.
if (m_moveInUse)
{
DirectX::XMFLOAT2 pointerDelta(m_movePointerPosition);
pointerDelta.x -= m_moveFirstDown.x;
pointerDelta.y -= m_moveFirstDown.y;
// Figure out the command from the touch-based virtual joystick.
if (pointerDelta.x > 16.0f) // Leave 32 pixel-wide dead spot for being still.
m_moveCommand.x = 1.0f;
else
if (pointerDelta.x < -16.0f)
m_moveCommand.x = -1.0f;
if (pointerDelta.y > 16.0f) // Joystick y is up, so change sign.
m_moveCommand.y = -1.0f;
else
if (pointerDelta.y < -16.0f)
m_moveCommand.y = 1.0f;
}
// Poll our state bits that are 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 45 degree cases are not faster.
DirectX::XMFLOAT3 command = m_moveCommand;
DirectX::XMVECTOR vector;
vector = DirectX::XMLoadFloat3(&command);
if (fabsf(command.x) > 0.1f || fabsf(command.y) > 0.1f || fabsf(command.z) > 0.1f)
{
vector = DirectX::XMVector3Normalize(vector);
DirectX::XMStoreFloat3(&command, vector);
}
// Rotate command to align with our direction (world coordinates).
DirectX::XMFLOAT3 wCommand;
wCommand.x = command.x*cosf(m_yaw) - command.y*sinf(m_yaw);
wCommand.y = command.x*sinf(m_yaw) + command.y*cosf(m_yaw);
wCommand.z = command.z;
// Scale for sensitivity adjustment.
wCommand.x = wCommand.x * MOVEMENT_GAIN;
wCommand.y = wCommand.y * MOVEMENT_GAIN;
wCommand.z = wCommand.z * MOVEMENT_GAIN;
// Our velocity is based on the command.
// Also note that y is the up-down axis.
DirectX::XMFLOAT3 Velocity;
Velocity.x = -wCommand.x;
Velocity.z = wCommand.y;
Velocity.y = wCommand.z;
// Integrate
m_position.x += Velocity.x;
m_position.y += Velocity.y;
m_position.z += Velocity.z;
// Clear movement input accumulator for use during the next frame.
m_moveCommand = DirectX::XMFLOAT3(0.0f, 0.0f, 0.0f);
}
Como não queremos movimentos instáveis quando o jogador usa nosso controlador de movimento baseado em toque, definimos uma zona morta virtual ao redor do ponteiro com um diâmetro de 32 pixels. Também adicionamos velocidade, que é o valor do comando mais uma taxa de ganho de movimento. (Você pode ajustar esse comportamento ao seu gosto, para desacelerar ou acelerar a taxa de movimento com base na distância que o ponteiro se move na área do controlador de movimento.)
Quando calculamos a velocidade, também traduzimos as coordenadas recebidas dos controladores de movimento e visão no movimento do ponto de visão real que enviamos para o método que calcula nossa matriz de visualização para a cena. Primeiro, invertemos a coordenada x, porque se clicarmos e movermos ou arrastarmos para a esquerda ou para a direita com o controlador de visão, o ponto de visão gira na direção oposta na cena, pois uma câmera pode girar em torno de seu eixo central. Em seguida, trocamos os eixos y e z, porque um pressionamento de tecla para cima/para baixo ou um movimento de arrastar por toque (lido como um comportamento do eixo y) no controlador de movimento deve se traduzir em uma ação de câmera que move o ponto de visão para dentro ou para fora da tela (o eixo z).
A posição final do ponto de visão para o jogador é a última posição mais a velocidade calculada, e isso é o que é lido pelo renderizador quando ele chama o método get_Position (provavelmente durante a configuração de cada quadro). Depois disso, redefinimos o comando de movimento para zero.
Atualizando a matriz de exibição com a nova posição da câmera
Podemos obter uma coordenada de espaço de cena na qual nossa câmera está focada e que é atualizada sempre que você diz ao seu aplicativo para fazer isso (a cada 60 segundos no loop principal do aplicativo, por exemplo). Esse pseudocódigo sugere o comportamento de chamada que você pode implementar:
myMoveLookController->Update( m_window );
// Update the view matrix based on the camera position.
myFirstPersonCamera->SetViewParameters(
myMoveLookController->get_Position(), // Point we are at
myMoveLookController->get_LookPoint(), // Point to look towards
DirectX::XMFLOAT3( 0, 1, 0 ) // Up-vector
);
Parabéns! Você implementou controles básicos de movimento e aparência para telas sensíveis ao toque e controles de toque de entrada de teclado/mouse em seu jogo!