Compartilhar via


Introdução ao XInput em aplicativos do Windows

O XInput permite que aplicativos do Windows processem interações do controlador (incluindo efeitos de ruído do controlador e entrada e saída de voz).

Este tópico fornece uma breve visão geral dos recursos do XInput e como configurá-lo em um aplicativo. Ele inclui o seguinte:

Introdução ao XInput

Os aplicativos podem usar a API XInput para se comunicar com controladores de jogos quando eles são conectados a um computador Windows (até quatro controladores exclusivos podem ser conectados por vez).

Usando essa API, qualquer controlador conectado compatível pode ser consultado para seu estado e efeitos de vibração podem ser definidos. Os controladores 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 headset para processamento de voz.

Layout do controlador

Os controladores compatíveis têm duas varas direcionais analógicas, cada uma 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 controlador 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 de XINPUT_VIBRATION que é passada para a função XInputSetState para definir efeitos de vibração.

Opcionalmente, um headset pode ser conectado ao controlador. 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ção XInputGetAudioDeviceIds 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.

Usando XInput

O uso do XInput é tão simples quanto chamar as funções XInput conforme necessário. Usando as funções XInput, você pode recuperar o estado do controlador, obter IDs de áudio do headset e definir efeitos de ruído do controlador.

Vários controladores

A API XInput dá suporte a até quatro controladores conectados a qualquer momento. Todas as funções XInput exigem um parâmetro dwUserIndex que é passado para identificar o controlador 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 controlador está conectado e não é modificável.

Cada controlador exibe qual ID ele está usando iluminando um quadrante no "anel de luz" no centro do controlador. Um valor dwUserIndex de 0 corresponde ao quadrante superior esquerdo; a numeração prossegue ao redor do anel em ordem no sentido horário.

Os aplicativos devem dar suporte a vários controladores.

Obtendo o estado do controlador

Durante toda a duração de um aplicativo, obter o estado de um controlador 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 controlador.

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 controlador está conectado. Os aplicativos devem definir uma estrutura para armazenar informações internas do controlador; essas informações devem ser comparadas com os resultados de XInputGetState para determinar quais alterações, como pressionamentos de botão ou deltas do controlador analógico, foram feitas nesse quadro. No exemplo acima, g_Controllers representa essa estrutura.

Depois que o estado tiver sido recuperado em uma estrutura de XINPUT_STATE , você poderá verificar se há alterações e obter informações específicas sobre o estado do controlador.

O membro dwPacketNumber da estrutura XINPUT_STATE pode ser usado para verificar se o estado do controlador foi alterado desde a última chamada para 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 frame. Recomendamos que você espace as verificações de novos controladores a cada alguns 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 refere-se aos valores de "movimento" relatados pelo controlador mesmo quando os botões de controle analógicos estão intocados e centralizados. Há também uma zona morta para os dois gatilhos analógicos.

Observação

Jogos que usam XInput e não filtram a zona morta terão uma jogabilidade ruim. Observe que alguns controladores 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, paus) para indicar quando um movimento foi feito o suficiente no stick ou gatilho para ser considerado válido.

Seu aplicativo deve verificar se há zonas mortas e responder adequadamente, como neste 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

Este exemplo calcula o vetor de direção do controlador e o quão longe ao longo do vetor o controlador foi empurrado. Isso permite a imposição de uma 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 controlador em unidades relevantes para o jogo.

Observe que você pode definir suas próprias zonas mortas para os bastões e gatilhos (de 0 a 65534) ou pode usar os deadzones fornecidos definidos 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 imposta, você poderá achar útil dimensionar o intervalo resultante [0.0..1.0] ponto flutuante (como no exemplo acima) e, opcionalmente, aplicar uma transformação não linear.

Por exemplo, com jogos de condução, pode ser útil elevar o resultado ao cubo para fornecer uma melhor sensação ao dirigir os carros usando um gamepad, pois elevar o resultado ao cubo lhe dá mais precisão nas faixas inferiores, o que é desejável, já que os jogadores costumam aplicar força leve para obter movimentos sutis ou força forte em uma direção para obter uma resposta rápida.

Definindo efeitos de vibração

Além de obter o estado do controlador, você também pode enviar dados de vibração para o controlador para alterar os comentários fornecidos ao usuário do controlador. O controlador 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 XInputSetState da 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.

Obtendo identificadores de dispositivo de áudio

O headset de um controlador tem estas funções:

  • 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 headset:

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 este código para criar uma voz de masterização 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 Capturando um Fluxo.

Obtendo GUIDs do DirectSound (somente o SDK do DirectX legado)

O fone de ouvido que pode ser conectado a um controlador 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 headset e o fone de ouvido às interfaces adequadas do DirectSound, você deve obter os DirectSoundGUIDs para os dispositivos de captura e reproduçã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 nesta 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;

referência de programação