Introdução ao XInput em aplicativos Windows
O XInput permite que aplicativos do Windows processem interações do controle (incluindo efeitos de ruído do controle e entrada e saída de voz).
Esse tópico fornece uma breve visão geral dos recursos do XInput e de como configurá-lo em um aplicativo. Inclui o seguinte:
Introdução ao XInput
Os aplicativos podem usar a API XInput para se comunicar com controles de jogos quando eles são conectados a um computador Windows (até quatro controles exclusivos podem ser conectados por vez).
Ao usar essa API, qualquer controle conectado compatível pode ser consultado para seu estado e efeitos de vibração podem ser definidos. Os controles que têm o fone de ouvido anexado também podem ser consultados para dispositivos de entrada e saída de som que podem ser usados com o fone de ouvido para processamento de voz.
Layout do controle
Os controles compatíveis têm dois direcionais analógicos, cada um com um botão digital, dois gatilhos analógicos, um painel direcional digital com quatro direções e oito botões digitais. Os estados de cada uma dessas entradas são retornados na estrutura XINPUT_GAMEPAD quando a função XInputGetState é chamada.
O controle também tem dois motores de vibração para fornecer efeitos de comentários forçados ao usuário. As velocidades desses motores são especificadas na estrutura XINPUT_VIBRATION que é passada para a função XInputSetState para definir efeitos de vibração.
Opcionalmente, um fone de ouvido pode ser conectado ao controle. O fone de ouvido tem um microfone para entrada de voz e um fone de ouvido para saída de som. Você pode chamar a funçãoXInputGetAudioDeviceIds ou XInputGetDSoundAudioDeviceGuids herdada para obter os identificadores de dispositivo que correspondem aos dispositivos do microfone e do fone de ouvido. Em seguida, você pode usar as APIs de Áudio Principal para receber entrada de voz e enviar saída de som.
Como usar o XInput
O uso do XInput é tão simples quanto chamar as funções do XInput conforme necessário. Ao usar as funções do XInput, você pode recuperar o estado do controle, obter IDs de áudio do fone de ouvido e definir efeitos de ruído do controlador.
Vários controles
A API XInput dá suporte a até quatro controles conectados a qualquer momento. Todas as funções XInput exigem um parâmetro dwUserIndex que é passado para identificar o controle que está sendo definido ou consultado. Essa ID estará no intervalo de 0 a 3 e será definida automaticamente pelo XInput. O número corresponde à porta na qual o controle está conectado e não é modificável.
Cada controle exibe qual ID ele está usando iluminando um quadrante no "anel de luz" no centro do controle. Um valor dwUserIndex de 0 corresponde ao quadrante superior esquerdo. A numeração prossegue em torno do anel, no sentido horário.
Os aplicativos devem dar suporte a vários controles.
Como obter o estado do controlador
Durante toda a duração de um aplicativo, obter o estado de um controle provavelmente será feito com mais frequência. Do quadro ao quadro em um aplicativo de jogo, o estado deve ser recuperado e as informações do jogo são atualizadas para refletir as alterações do controle.
Para recuperar o estado, use a função XInputGetState:
DWORD dwResult;
for (DWORD i=0; i< XUSER_MAX_COUNT; i++ )
{
XINPUT_STATE state;
ZeroMemory( &state, sizeof(XINPUT_STATE) );
// Simply get the state of the controller from XInput.
dwResult = XInputGetState( i, &state );
if( dwResult == ERROR_SUCCESS )
{
// Controller is connected
}
else
{
// Controller is not connected
}
}
Observe que o valor retornado de XInputGetState pode ser usado para determinar se o controle está conectado. Os aplicativos devem definir uma estrutura para armazenar informações internas do controle. Essas informações devem ser comparadas com os resultados de XInputGetState para determinar quais alterações, como pressionamentos de botão ou deltas do controle analógico, foram feitas nesse quadro. No exemplo acima, g_Controllers representa essa estrutura.
Depois que o estado tiver sido recuperado em uma estrutura XINPUT_STATE, você poderá verificar se há alterações e obter informações específicas sobre o estado do controlade.
O membro dwPacketNumber da estrutura XINPUT_STATE pode ser usado para verificar se o estado do controle foi alterado desde a última chamada a XInputGetState. Se dwPacketNumber não mudar entre duas chamadas sequenciais para XInputGetState, não haverá nenhuma alteração no estado. Se for diferente, o aplicativo deverá verificar o membro do Gamepad da estrutura XINPUT_STATE para obter informações de estado mais detalhadas.
Por motivos de desempenho, não chame XInputGetState para um slot de usuário "vazio" a cada quadro. Recomendamos que você espace as verificações de novos controladores a cada poucos segundos.
Zona Morta
Para que os usuários tenham uma experiência de jogo consistente, seu jogo deve implementar a zona morta corretamente. A zona morta são valores de "movimento" relatados pelo controle mesmo quando os direcionais analógicos estão intocados e centralizados. Há também uma zona morta para os dois gatilhos analógicos.
Observação
Os jogos que usam XInput e que não filtram a zona morta terão uma jogabilidade ruim. Observe que alguns controles são mais sensíveis do que outros, portanto, a zona morta pode variar de unidade para unidade. É recomendável que você teste seus jogos com vários controladores diferentes em sistemas diferentes.
Os aplicativos devem usar "zonas mortas" em entradas analógicas (gatilhos, direcionais) para indicar quando foi feito um movimento o suficiente no direcional ou gatilho para ser considerado válido.
Seu aplicativo deve verificar se há zonas mortas e responder de forma adequada, como nesse exemplo:
XINPUT_STATE state = g_Controllers[i].state;
float LX = state.Gamepad.sThumbLX;
float LY = state.Gamepad.sThumbLY;
//determine how far the controller is pushed
float magnitude = sqrt(LX*LX + LY*LY);
//determine the direction the controller is pushed
float normalizedLX = LX / magnitude;
float normalizedLY = LY / magnitude;
float normalizedMagnitude = 0;
//check if the controller is outside a circular dead zone
if (magnitude > INPUT_DEADZONE)
{
//clip the magnitude at its expected maximum value
if (magnitude > 32767) magnitude = 32767;
//adjust magnitude relative to the end of the dead zone
magnitude -= INPUT_DEADZONE;
//optionally normalize the magnitude with respect to its expected range
//giving a magnitude value of 0.0 to 1.0
normalizedMagnitude = magnitude / (32767 - INPUT_DEADZONE);
}
else //if the controller is in the deadzone zero out the magnitude
{
magnitude = 0.0;
normalizedMagnitude = 0.0;
}
//repeat for right thumb stick
Esse exemplo calcula o vetor de direção do controle e o quão longe ao longo do vetor o controle foi pressionado. Isso permite a imposição de um zona morta circular simplesmente verificando se a magnitude do controle é maior que o valor da zona morta. Além disso, o código normaliza a magnitude do controlador, que pode ser multiplicada por um fator específico do jogo para converter a posição do controle em unidades relevantes para o jogo.
Observe que você pode definir suas próprias zonas mortas para os direcionais e gatilhos (de 0 a 65534) ou pode usar as zonas mortas fornecidas definidas como XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE e XINPUT_GAMEPAD_TRIGGER_THRESHOLD em XInput.h:
#define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE 7849
#define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689
#define XINPUT_GAMEPAD_TRIGGER_THRESHOLD 30
Depois que a zona morta for aplicada, poderá ser útil dimensionar o intervalo resultante [0.0..1.0] de ponto flutuante (como no exemplo acima) e, opcionalmente, aplicar uma transformação não linear.
Por exemplo, em jogos de corrida, pode ser útil cubar o resultado para proporcionar uma melhor sensação de dirigir os carros usando um gamepad, já que cubar o resultado proporciona mais precisão nas faixas mais baixas, o que é desejável, já que os jogadores normalmente aplicam força suave para obter movimentos sutis ou aplicar muita força em uma direção para obter uma resposta secundária.
Como definir efeitos de vibração
Além de obter o estado do controle, você também pode enviar dados de vibração para o controle para alterar os comentários fornecidos ao usuário do controle. O controle contém dois motores de ruído que podem ser controlados independentemente passando valores para a função XInputSetState.
A velocidade de cada motor pode ser especificada usando um valor WORD na estrutura XINPUT_VIBRATION que é passada para a função XInputSetStateda seguinte maneira:
XINPUT_VIBRATION vibration;
ZeroMemory( &vibration, sizeof(XINPUT_VIBRATION) );
vibration.wLeftMotorSpeed = 32000; // use any value between 0-65535 here
vibration.wRightMotorSpeed = 16000; // use any value between 0-65535 here
XInputSetState( i, &vibration );
Observe que o motor direito é o motor de alta frequência, o motor esquerdo é o motor de baixa frequência. Nem sempre eles precisam ser definidos com a mesma quantidade, pois fornecem efeitos diferentes.
Como obter identificadores de dispositivo de áudio
O fone de ouvido de um controle tem as funções a seguir:
- Gravar som usando um microfone
- Reproduzir o som usando um fone de ouvido
Use este código para obter os identificadores de dispositivo para o fone de ouvido:
WCHAR renderId[ 256 ] = {0};
WCHAR captureId[ 256 ] = {0};
UINT rcount = 256;
UINT ccount = 256;
XInputGetAudioDeviceIds( i, renderId, &rcount, captureId, &ccount );
Depois de obter os identificadores do dispositivo, você pode criar as interfaces apropriadas. Por exemplo, se você usar o XAudio 2.8, use o código a seguir para criar uma voz de domínio para este dispositivo:
IXAudio2* pXAudio2 = NULL;
HRESULT hr;
if ( FAILED(hr = XAudio2Create( &pXAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR ) ) )
return hr;
IXAudio2MasteringVoice* pMasterVoice = NULL;
if ( FAILED(hr = pXAudio2->CreateMasteringVoice( &pMasterVoice, XAUDIO2_DEFAULT_CHANNELS, XAUDIO2_DEFAULT_SAMPLERATE, 0, renderId, NULL, AudioCategory_Communications ) ) )
return hr;
Para obter informações sobre como usar o identificador de dispositivo captureId, consulte Como Capturando um Fluxo.
Obtendo GUIDs do DirectSound (somente para SDK do DirectX herdado)
O fone de ouvido que pode ser conectado a um controle tem duas funções: ele pode gravar som usando um microfone e pode reproduzir o som usando um fone de ouvido. Na API XInput, essas funções são realizadas por meio do DirectSound, usando as interfaces IDirectSound8 e IDirectSoundCapture8.
Para associar o microfone do fone de ouvido e o fone de ouvido às interfaces apropriadas do DirectSound, você deve obter os DirectSoundGUIDs para os dispositivos de captura e renderização chamando XInputGetDSoundAudioDeviceGuids.
Observação
O uso do DirectSound herdado não é recomendado e não está disponível em aplicativos da Windows Store. As informações nessa seção se aplicam apenas à versão do SDK do DirectX do XInput (XInput 1.3). A versão do Windows 8 do XInput (XInput 1.4) usa exclusivamente identificadores de dispositivo WASAPI (API de Sessão de Áudio) do Windows obtidos por meio de XInputGetAudioDeviceIds.
XInputGetDSoundAudioDeviceGuids( i, &dsRenderGuid, &dsCaptureGuid );
Depois de recuperar os GUIDs, você poderá criar as interfaces apropriadas chamando DirectSoundCreate8 e DirectSoundCaptureCreate8 da seguinte maneira:
// Create IDirectSound8 using the controller's render device
if( FAILED( hr = DirectSoundCreate8( &dsRenderGuid, &pDS, NULL ) ) )
return hr;
// Set coop level to DSSCL_PRIORITY
if( FAILED( hr = pDS->SetCooperativeLevel( hWnd, DSSCL_NORMAL ) ) )
return hr;
// Create IDirectSoundCapture using the controller's capture device
if( FAILED( hr = DirectSoundCaptureCreate8( &dsCaptureGuid, &pDSCapture, NULL ) ) )
return hr;