Introducción a XInput en aplicaciones Windows
XInput permite a las aplicaciones de Windows procesar las interacciones del dispositivo (incluidos los efectos de retumbo del dispositivo y la entrada y salida de voz).
En este tema, se proporciona una breve introducción a las funcionalidades de XInput y cómo configurarla en una aplicación. Esto incluye lo siguiente.
Introducción a XInput
Las aplicaciones pueden usar la API XInput para comunicarse con dispositivos de juegos cuando se conectan a un equipo Windows (se pueden conectar hasta cuatro dispositivos únicos a la vez).
Con esta API, se puede consultar cualquier dispositivo conectado compatible para su estado y se pueden establecer efectos de vibración. Los dispositivos que tienen los auriculares conectados también se pueden consultar para dispositivos de entrada y salida de sonido que se pueden usar con los auriculares para el procesamiento de voz.
Diseño del dispositivo
Los dispositivos compatibles tienen dos sticks direccionales analógicos, cada uno con un botón digital, dos desencadenadores analógicos, una almohadilla direccional digital con cuatro direcciones y ocho botones digitales. Los estados de cada una de estas entradas se devuelven en la estructura XINPUT_GAMEPAD cuando se llama a la función XInputGetState.
El dispositivo también tiene dos motores de vibración para proporcionar efectos hápticos al usuario. Las velocidades de estos motores se especifican en la estructura XINPUT_VIBRATION, que se pasa a la función XInputSetState para establecer efectos de vibración.
De manera opcional, se pueden conectar auriculares al dispositivo. Los auriculares tienen un micrófono para la entrada de voz y unos auriculares para la salida de sonido. Puede llamar a la función XInputGetAudioDeviceIds o XInputGetDSoundAudioDeviceGuids heredada para obtener los identificadores de dispositivo que corresponden a los dispositivos para el micrófono y los auriculares. A continuación, puede usar las API de audio principales para recibir la entrada de voz y enviar la salida de sonido.
Uso de XInput
El uso de XInput es tan sencillo como llamar a las funciones XInput según sea necesario. Con las funciones XInput, puede recuperar el estado del dispositivo, obtener identificadores de audio de auriculares y establecer efectos de retumbo del dispositivo.
Varios dispositivos
La API XInput admite hasta cuatro dispositivos conectados en cualquier momento. Todas las funciones XInput requieren un parámetro dwUserIndex, que se pasa para identificar el dispositivo que se va a establecer o consultar. Este identificador estará en el intervalo de 0 a 3 y XInput lo establecerá de manera automática. El número corresponde al puerto en el que está conectado el dispositivo y no se puede modificar.
Cada dispositivo muestra qué identificador está usando al iluminar un cuadrante en el "anillo de luz" en el centro del dispositivo. Un valor dwUserIndex de 0 corresponde al cuadrante superior izquierdo; la numeración continúa alrededor del anillo en orden de las agujas del reloj.
Las aplicaciones deben admitir varios dispositivos.
Obtención del estado del dispositivo
A lo largo de la duración de una aplicación, la obtención del estado de un dispositivo probablemente se realizará con más frecuencia. De marco a fotograma en una aplicación de juego, el estado debe recuperarse y actualizar la información del juego para reflejar los cambios del dispositivo.
Para recuperar el estado, use la función 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
}
}
Tenga en cuenta que el valor devuelto de XInputGetState se puede usar para determinar si el dispositivo está conectado. Las aplicaciones deben definir una estructura para contener información interna del dispositivo; esta información debe compararse con los resultados de XInputGetState para determinar qué cambios, como pulsaciones de botón o deltas del dispositivo analógico, se realizaron durante ese fotograma. En el ejemplo anterior, g_Controllers representa dicha estructura.
Una vez recuperado el estado en una estructura de XINPUT_STATE, puede comprobar si hay cambios y obtener información específica sobre el estado del dispositivo.
El miembro dwPacketNumber de la estructura XINPUT_STATE se puede usar para comprobar si el estado del dispositivo ha cambiado desde la última llamada a XInputGetState. Si dwPacketNumber no cambia entre dos llamadas secuenciales a XInputGetState, no se ha producido ningún cambio en el estado. Si difiere, la aplicación debe comprobar el miembro Gamepad de la estructura XINPUT_STATE para obtener información de estado más detallada.
Por motivos de rendimiento, no llame a XInputGetState para una ranura de usuario "vacía" cada fotograma. En su lugar, se recomienda espaciar las comprobaciones de los nuevos dispositivos cada pocos segundos.
Zona muerta
Para que los usuarios tengan una experiencia de juego coherente, el juego debe implementar correctamente la zona muerta. La zona muerta hace referencia a los valores de "movimiento" notificados por el dispositivo, incluso cuando los sticks analógicos están intactos y centrados. También hay una zona muerta para los 2 desencadenadores analógicos.
Nota:
Los juegos que usan XInput que no filtran la zona muerta en absoluto experimentarán un juego deficiente. Tenga en cuenta que algunos dispositivos son más sensibles que otros, por lo que la zona muerta puede variar de unidad a unidad. Se recomienda que pruebe sus juegos con varios controladores diferentes en sistemas distintos.
Las aplicaciones deben usar "zonas muertas" en entradas analógicas (desencadenadores, sticks) para indicar cuándo se ha realizado un movimiento lo suficientemente en el stick o desencadenador para que se considere válido.
La aplicación debe comprobar si hay zonas muertas y responder de forma oportuna, como en este ejemplo:
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
En este ejemplo, se calcula el vector de dirección del dispositivo y la distancia a lo largo del vector que se ha insertado. Esto permite aplicar una zona muerta circular simplemente comprobando si la magnitud del dispositivo es mayor que el valor de la zona muerta. Además, el código normaliza la magnitud del dispositivo, que luego se puede multiplicar por un factor específico del juego para convertir la posición del controlador en unidades relevantes para el juego.
Tenga en cuenta que puede definir sus propias zonas muertas para los sticks y desencadenadores (desde 0 a 65534), o puede usar las zonas muertas proporcionadas definidas como XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE y XINPUT_GAMEPAD_TRIGGER_THRESHOLD en XInput.h:
#define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE 7849
#define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689
#define XINPUT_GAMEPAD_TRIGGER_THRESHOLD 30
Una vez aplicada la zona muerta, puede resultar útil escalar el punto flotante del intervalo resultante [0.0..1.0] (como en el ejemplo anterior) y, de manera opcional, aplicar una transformación no lineal.
Por ejemplo, con los juegos de conducción, puede ser útil calcular el resultado para proporcionar una mejor sensación de conducir los coches con un controlador para juegos, ya que el resultado le da más precisión en los rangos inferiores, lo que es deseable, ya que los jugadores suelen aplicar una fuerza tenue para obtener movimiento sutil o aplicar una fuerza mayor todo el camino en una dirección para obtener una respuesta realista.
Establecer efectos de vibración
Además de obtener el estado del dispositivo, también puede enviar datos de vibración al dispositivo para modificar los comentarios proporcionados al usuario del dispositivo. El dispositivo contiene dos motores de retumbo que se pueden controlar de forma independiente pasando valores a la función XInputSetState.
La velocidad de cada motor se puede especificar mediante un valor WORD en la estructura XINPUT_VIBRATION que se pasa a la función XInputSetState de la siguiente manera:
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 );
Tenga en cuenta que el motor derecho es el motor de alta frecuencia; el motor izquierdo es el motor de baja frecuencia. No siempre necesitan establecerse en la misma cantidad, ya que proporcionan efectos diferentes.
Obtención de identificadores de dispositivo de audio
Los auriculares de un dispositivo tienen estas funciones:
- Grabar sonido mediante un micrófono
- Reproducir sonido con auriculares
Use este código para obtener los identificadores de dispositivo para los auriculares:
WCHAR renderId[ 256 ] = {0};
WCHAR captureId[ 256 ] = {0};
UINT rcount = 256;
UINT ccount = 256;
XInputGetAudioDeviceIds( i, renderId, &rcount, captureId, &ccount );
Después de obtener los identificadores de dispositivo, puede crear las interfaces adecuadas. Por ejemplo, si usa XAudio 2.8, use este código para crear una voz maestra 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 obtener información sobre cómo usar el identificador de dispositivo captureId, consulte Capturar una secuencia.
Obtención de GUID de DirectSound (solo SDK de DirectX heredado)
Los auriculares que se pueden conectar a un dispositivo tienen dos funciones: pueden grabar sonido mediante un micrófono y reproducir el sonido mediante los auriculares. En la API XInput, estas funciones se realizan a través de DirectSound, mediante las interfaces IDirectSound8 e IDirectSoundCapture8.
Para asociar el micrófono y los auriculares con sus interfaces de DirectSound adecuadas, debe obtener los DirectSoundGUID para la captura y representación de dispositivos llamando a XInputGetDSoundAudioDeviceGuids.
Nota:
No se recomienda usar DirectSound heredado y no está disponible en las aplicaciones de la Tienda Windows. La información de esta sección solo se aplica a la versión del SDK de DirectX de XInput (XInput 1.3). La versión de Windows 8 de XInput (XInput 1.4) usa exclusivamente identificadores de dispositivo de la API de sesión de audio de Windows (WASAPI) que se obtienen a través de XInputGetAudioDeviceIds.
XInputGetDSoundAudioDeviceGuids( i, &dsRenderGuid, &dsCaptureGuid );
Una vez que haya recuperado los GUID, puede crear las interfaces adecuadas llamando a DirectSoundCreate8 y DirectSoundCaptureCreate8 de esta manera:
// 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;