Controlador para juegos y vibración
En esta página se describen los conceptos básicos de la programación para el controlador para juegos mediante [Windows.Gaming.Input.Gamepad][controlador para juegos] y las API relacionadas para la Plataforma universal de Windows (UWP).
Al leer esta página, aprenderá lo siguiente:
- cómo recopilar una lista de paneles de juegos conectados y sus usuarios
- cómo detectar que se ha agregado o quitado un controlador para juegos
- cómo leer la entrada de uno o varios controladores para juego
- cómo enviar comandos de vibración e impulso
- cómo se comportan los paneles de juego como dispositivos de navegación de la interfaz de usuario
Introducción al controlador para juegos
Los controladores para juegos como el mando inalámbrico Xbox y el mando inalámbrico de Xbox S son dispositivos de entrada de juegos de uso general. Son el dispositivo de entrada estándar en Xbox One y una opción común para los jugadores de Windows cuando no favorecen un teclado y un mouse. Los controladores para juegos se admiten en aplicaciones para UWP de Windows 10 o Windows 11 y Xbox a través del espacio de nombres Windows.Gaming.Input.
Los controladores para juegos de Xbox One están equipados con una almohadilla direccional (o D-pad); A, B, X, Y, Very botones Menú; sticks de pulgar izquierdo y derecho, parachoques y desencadenadores; y un total de cuatro motores de vibración. Ambos sticks de pulgar proporcionan lecturas analógicas duales en los ejes X e Y, además, actúan como un botón cuando se presionan hacia adentro. Cada desencadenador proporciona una lectura analógica que representa la distancia a la que se retira.
Nota:
Windows.Gaming.Input.Gamepad
también admite controladores para juegos de Xbox 360, que tienen el mismo diseño de control que los controladores para juegos de Xbox One estándar.
Desencadenadores de vibración e impulso
Los controladores para juegos de Xbox One proporcionan dos motores independientes para vibración fuerte y sutil del controlador para juegos, así como dos motores dedicados para proporcionar vibración nítida a cada desencadenador (esta característica única es la razón por la que los desencadenadores del controlador para juegos de Xbox One se conocen como desencadenadores de impulsos).
Nota:
Los controladores para juegos de Xbox 360 no están equipados con desencadenadores de impulso.
Para obtener más información, ver introducción a los desencadenadores de vibración e impulso.
Zonas muertas del stick de pulgar
Un stick de pulgar en reposo en la posición central produciría idealmente la misma lectura neutra en los ejes X e Y cada vez. Sin embargo, debido a las fuerzas mecánicas y la sensibilidad del stick pulgar, las lecturas reales en la posición central solo se aproximan al valor neutro ideal y pueden variar entre las lecturas posteriores. Por esta razón, siempre debes usar una pequeña zona muerta (un rango de valores cerca de la posición central ideal que se ignora) para compensar las diferencias de fabricación, el desgaste mecánico u otros problemas del controlador para juegos.
Las zonas muertas más grandes ofrecen una estrategia sencilla para separar la entrada intencionada de la entrada involuntaria.
Para obtener más información, ver Lectura de los sticks digitales.
Navegación de la interfaz de usuario
Para aliviar la carga de la compatibilidad con los diferentes dispositivos de entrada para la navegación de la interfaz de usuario y fomentar la coherencia entre dispositivos y juegos, la mayoría de los dispositivos de entrada física actúan simultáneamente como un dispositivo independiente de entrada lógica denominado controlador de navegación de la interfaz de usuario. El controlador de navegación de la interfaz de usuario proporciona un vocabulario común para los comandos de navegación de la interfaz de usuario en todos los dispositivos de entrada.
Como controlador de navegación de la interfaz de usuario, los controladores para juegos asignan el conjunto necesario de comandos de navegación a los botones de navegación izquierdo, panel D, Vista, Menú, Ay B.
Comando de navegación | Entrada del controlador para juegos |
---|---|
Up (Arriba) | Stick de la izquierda hacia arriba / D-pad arriba |
Bajar | Stick de pulgar izquierdo hacia abajo /D-pad abajo |
Izquierda | Stick de pulgar izquierdo a la izquierda/ D-pad a la izquierda |
Right | Stick de pulgar izquierdo a la derecha /D-pad a la derecha |
Ver | Botón Ver |
Menú | Botón Menú |
Aceptar | Botón A |
Cancelar | Botón B |
Además, los controladores para juegos asignan todos los conjunto opcional de comandos de navegación a las entradas restantes.
Comando de navegación | Entrada del controlador para juegos |
---|---|
Re. pág. | Desencadenador izquierdo |
Av. Pág. | Desencadenador derecho |
Página izquierda | Parachoques izquierdo |
Página a la derecha | Parachoques derecho |
Desplazarse hacia arriba | Stick pulgar derecho hacia arriba |
Desplazarse hacia abajo | Stick pulgar derecho hacia abajo |
Desplazarse a la izquierda | Stick pulgar derecho a la izquierda |
Desplazarse hacia la derecha | Stick pulgar derecho a la derecha |
Contexto 1 | Botón X |
Contexto 2 | Botón Y |
Contexto 3 | Pulsación de palanca de pulgar izquierda |
Contexto 4 | Pulsación de stick pulgar derecho |
Detección y seguimiento de controladores para juegos
Los controladores para juegos se administran mediante el sistema, por lo que no es necesario crearlos ni inicializarlos. El sistema proporciona una lista de controladores para juegos conectados y eventos para notificarle cuando se agrega o quita un controlador para juegos.
Lista de controladores para juegos
La clase Gamepad proporciona una propiedad estática, Gamepads, que es una lista de solo lectura de los controladores para juegos que están conectados actualmente. Dado que es posible que solo te interesen algunos de los controladores para juegos conectados, te recomendamos que mantengas tu propia colección en lugar de acceder a ellos a través de la propiedad Gamepads
.
En el ejemplo siguiente se copian todos los controladores para juegos conectados en una nueva colección. Ten en cuenta que, dado que otros subprocesos en segundo plano tendrán acceso a esta colección (en el GamepadAdded y eventos GamepadRemoved), debe colocar un bloqueo alrededor de cualquier código que lea o actualice la colección.
auto myGamepads = ref new Vector<Gamepad^>();
critical_section myLock{};
for (auto gamepad : Gamepad::Gamepads)
{
// Check if the gamepad is already in myGamepads; if it isn't, add it.
critical_section::scoped_lock lock{ myLock };
auto it = std::find(begin(myGamepads), end(myGamepads), gamepad);
if (it == end(myGamepads))
{
// This code assumes that you're interested in all gamepads.
myGamepads->Append(gamepad);
}
}
private readonly object myLock = new object();
private List<Gamepad> myGamepads = new List<Gamepad>();
private Gamepad mainGamepad;
private void GetGamepads()
{
lock (myLock)
{
foreach (var gamepad in Gamepad.Gamepads)
{
// Check if the gamepad is already in myGamepads; if it isn't, add it.
bool gamepadInList = myGamepads.Contains(gamepad);
if (!gamepadInList)
{
// This code assumes that you're interested in all gamepads.
myGamepads.Add(gamepad);
}
}
}
}
Agregar y quitar controladores para juegos
Cuando se agrega o quita un controlador para juegos, se generan los eventos GamepadAdded Y GamepadRemoved. Puedes registrar controladores para estos eventos para realizar un seguimiento de los controladores de juegos que están conectados actualmente.
En el ejemplo siguiente se inicia el seguimiento de un controlador para juegos que se ha agregado.
Gamepad::GamepadAdded += ref new EventHandler<Gamepad^>(Platform::Object^, Gamepad^ args)
{
// Check if the just-added gamepad is already in myGamepads; if it isn't, add
// it.
critical_section::scoped_lock lock{ myLock };
auto it = std::find(begin(myGamepads), end(myGamepads), args);
if (it == end(myGamepads))
{
// This code assumes that you're interested in all new gamepads.
myGamepads->Append(args);
}
}
Gamepad.GamepadAdded += (object sender, Gamepad e) =>
{
// Check if the just-added gamepad is already in myGamepads; if it isn't, add
// it.
lock (myLock)
{
bool gamepadInList = myGamepads.Contains(e);
if (!gamepadInList)
{
myGamepads.Add(e);
}
}
};
En el ejemplo siguiente se detiene el seguimiento de un controlador para juegos que se ha quitado. También tendrás que controlar lo que sucede con los controladores para juegos que estás rastreando cuando se quitan; por ejemplo, este código solo realiza un seguimiento de la entrada de un controlador para juegos y simplemente lo establece en nullptr
cuando se quita. Tendrás que comprobar cada fotograma si el controlador para juegos está activo y actualizar el controlador para juegos desde el que estás recopilando la entrada cuando los controladores están conectados y desconectados.
Gamepad::GamepadRemoved += ref new EventHandler<Gamepad^>(Platform::Object^, Gamepad^ args)
{
unsigned int indexRemoved;
critical_section::scoped_lock lock{ myLock };
if(myGamepads->IndexOf(args, &indexRemoved))
{
if (m_gamepad == myGamepads->GetAt(indexRemoved))
{
m_gamepad = nullptr;
}
myGamepads->RemoveAt(indexRemoved);
}
}
Gamepad.GamepadRemoved += (object sender, Gamepad e) =>
{
lock (myLock)
{
int indexRemoved = myGamepads.IndexOf(e);
if (indexRemoved > -1)
{
if (mainGamepad == myGamepads[indexRemoved])
{
mainGamepad = null;
}
myGamepads.RemoveAt(indexRemoved);
}
}
};
Ver Prácticas de entrada para juegos para obtener más información.
Usuarios y auriculares
Cada controlador para juegos se puede asociar a una cuenta de usuario para vincular su identidad a su juego y puede tener un casco conectado para facilitar el chat de voz o las características del juego. Para obtener más información sobre cómo trabajar con usuarios y auriculares, ver Seguimiento de usuarios y sus dispositivos y Auriculares.
Lectura del controlador para juegos
Después de identificar el controlador para juegos que te interesa, estás listo para recopilar la entrada de él. Sin embargo, a diferencia de otros tipos de entrada que puedes usar para, los controladores para juegos no comunican el cambio de estado mediante la generación de eventos. En su lugar, se toman lecturas regulares de su estado actual sondear.
Sondear el controlador para juegos
El sondeo captura una instantánea del dispositivo de navegación en un momento dado preciso. Este enfoque para la recopilación de entradas es una buena opción para la mayoría de los juegos porque su lógica normalmente se ejecuta en un bucle determinista en lugar de ser controlado por eventos; también suele ser más sencillo interpretar los comandos de juego de la entrada recopiladas a la vez que de muchas entradas únicas recopiladas a lo largo del tiempo.
Puedes sondear un controlador para juegos llamando a GetCurrentReading; esta función devuelve un GamepadReading que contiene el estado del controlador para juegos.
En el ejemplo siguiente se sondea un controlador para juegos por su estado actual.
auto gamepad = myGamepads[0];
GamepadReading reading = gamepad->GetCurrentReading();
Gamepad gamepad = myGamepads[0];
GamepadReading reading = gamepad.GetCurrentReading();
Además del estado del controlador para juegos, cada lectura incluye una marca de tiempo que indica exactamente cuándo se recuperó el estado. La marca de tiempo es útil para relacionarse con el tiempo de lecturas anteriores o con el tiempo de la simulación del juego.
Lectura de los sticks digitales
Cada stick pulgar proporciona una lectura analógica entre -1.0 y +1.0 en los ejes X e Y. En el eje X, un valor de -1,0 corresponde a la posición del stick de control más a la izquierda; Un valor de +1.0 corresponde a la posición más derecha. En el eje Y, un valor de -1,0 corresponde a la posición de la barra de control más abajo; Un valor de +1.0 corresponde a la posición más alta. En ambos ejes, el valor es aproximadamente 0,0 cuando el stick está en la posición central, pero es normal que el valor preciso varíe, incluso entre las lecturas posteriores; las estrategias para mitigar esta variación se describen más adelante en esta sección.
El valor del eje X del stick de la izquierda se lee de la LeftThumbstickX
propiedad de la estructura GamepadReading; el valor del eje Y se lee de la propiedad LeftThumbstickY
. El valor del eje X del stick derecho se lee de la RightThumbstickX
propiedad ; el valor del eje Y se lee de la RightThumbstickY
propiedad.
float leftStickX = reading.LeftThumbstickX; // returns a value between -1.0 and +1.0
float leftStickY = reading.LeftThumbstickY; // returns a value between -1.0 and +1.0
float rightStickX = reading.RightThumbstickX; // returns a value between -1.0 and +1.0
float rightStickY = reading.RightThumbstickY; // returns a value between -1.0 and +1.0
double leftStickX = reading.LeftThumbstickX; // returns a value between -1.0 and +1.0
double leftStickY = reading.LeftThumbstickY; // returns a value between -1.0 and +1.0
double rightStickX = reading.RightThumbstickX; // returns a value between -1.0 and +1.0
double rightStickY = reading.RightThumbstickY; // returns a value between -1.0 and +1.0
Al leer los valores de la barra digital, observará que no producen una lectura neutra de 0,0 cuando el stick pulgar está en reposo en la posición central; en su lugar, generarán valores diferentes cerca de 0,0 cada vez que se mueve el stick pulgar y se devuelven a la posición central. Para mitigar estas variaciones, puede implementar una pequeña zona muerta, que es un intervalo de valores cerca de la posición central ideal que se omite. Una manera de implementar una zona muerta es determinar cuánto lejos del centro se ha movido el stick digital e ignorar las lecturas que están más cercanas que alguna distancia que elija. Puede calcular la distancia aproximadamente, no es exacta porque las lecturas del stick digital son esencialmente polares, no planar, valores, simplemente mediante el teorema de Pythagorean. Esto produce una zona muerta radial.
En el ejemplo siguiente se muestra una zona muerta radial básica mediante el teorema de Pythagorean.
float leftStickX = reading.LeftThumbstickX; // returns a value between -1.0 and +1.0
float leftStickY = reading.LeftThumbstickY; // returns a value between -1.0 and +1.0
// choose a deadzone -- readings inside this radius are ignored.
const float deadzoneRadius = 0.1;
const float deadzoneSquared = deadzoneRadius * deadzoneRadius;
// Pythagorean theorem -- for a right triangle, hypotenuse^2 = (opposite side)^2 + (adjacent side)^2
auto oppositeSquared = leftStickY * leftStickY;
auto adjacentSquared = leftStickX * leftStickX;
// accept and process input if true; otherwise, reject and ignore it.
if ((oppositeSquared + adjacentSquared) > deadzoneSquared)
{
// input accepted, process it
}
double leftStickX = reading.LeftThumbstickX; // returns a value between -1.0 and +1.0
double leftStickY = reading.LeftThumbstickY; // returns a value between -1.0 and +1.0
// choose a deadzone -- readings inside this radius are ignored.
const double deadzoneRadius = 0.1;
const double deadzoneSquared = deadzoneRadius * deadzoneRadius;
// Pythagorean theorem -- for a right triangle, hypotenuse^2 = (opposite side)^2 + (adjacent side)^2
double oppositeSquared = leftStickY * leftStickY;
double adjacentSquared = leftStickX * leftStickX;
// accept and process input if true; otherwise, reject and ignore it.
if ((oppositeSquared + adjacentSquared) > deadzoneSquared)
{
// input accepted, process it
}
Cada stick también actúa como un botón cuando se presiona hacia adentro; para obtener más información sobre cómo leer esta entrada, ver Lectura de los botones.
Lectura de los desencadenadores
Los desencadenadores se representan como valores de punto flotante entre 0,0 (totalmente liberado) y 1,0 (totalmente presionado). El valor del desencadenador izquierdo se lee de la propiedad LeftTrigger
de la estructura GamepadReading; el valor del desencadenador derecho se lee desde la propiedad RightTrigger
.
float leftTrigger = reading.LeftTrigger; // returns a value between 0.0 and 1.0
float rightTrigger = reading.RightTrigger; // returns a value between 0.0 and 1.0
double leftTrigger = reading.LeftTrigger; // returns a value between 0.0 and 1.0
double rightTrigger = reading.RightTrigger; // returns a value between 0.0 and 1.0
Lectura de los botones
Cada uno de los botones—del controlador para juegos las cuatro direcciones del panel D, los parachoques izquierdo y derecho, la pulsación de la izquierda y derecha, A, B, X, Y, Ver, y Menú, proporciona una lectura digital que indica si se presiona (hacia abajo) o se libera (hacia arriba). Para mejorar la eficacia, las lecturas de botón no se representan como valores booleanos individuales; en su lugar, todos se empaquetan en un solo campo de bits representado por la enumeración GamepadButtons.
Los valores del botón se leen de la Buttons
propiedad de la estructura GamepadReading. Dado que esta propiedad es un campo de bits, el enmascaramiento bit a bit se usa para aislar el valor del botón que le interesa. El botón se presiona (abajo) cuando se establece el bit correspondiente; de lo contrario, se libera (arriba).
En el ejemplo siguiente se determina si se presiona el botón A.
if (GamepadButtons::A == (reading.Buttons & GamepadButtons::A))
{
// button A is pressed
}
if (GamepadButtons.A == (reading.Buttons & GamepadButtons.A))
{
// button A is pressed
}
En el ejemplo siguiente se determina si se suelta el botón A.
if (GamepadButtons::None == (reading.Buttons & GamepadButtons::A))
{
// button A is released
}
if (GamepadButtons.None == (reading.Buttons & GamepadButtons.A))
{
// button A is released
}
A veces, es posible que desee determinar cuándo un botón pasa de presionado a liberado o liberado a presionado, si se presionan o sueltan varios botones, o si se organiza un conjunto de botones de una manera determinada, algunos no. Para obtener información sobre cómo detectar cada una de estas condiciones, ver Detección de transiciones de botón y Detección de arreglos de botón complejos.
Ejecutar el ejemplo de entrada del controlador para juegos
El ejemplo GamepadUWP(github)muestra cómo conectarse a un controlador para juegos y leer su estado.
Introducción a los desencadenadores de vibración e impulso
Los motores de vibración dentro de un controlador para juegos son para proporcionar comentarios táctiles al usuario. Los juegos usan esta capacidad para crear una mayor sensación de inmersión, para ayudar a comunicar la información de estado (como tomar daño), para indicar la proximidad a objetos importantes o para otros usos creativos.
Los controladores para juegos de Xbox One están equipados con un total de cuatro motores de vibración independientes. Dos son motores grandes ubicados en el cuerpo del controlador para juegos; el motor izquierdo proporciona vibración rugosa y de alta amplitud, mientras que el motor derecho proporciona vibración más suave y sutil. Los otros dos son motores pequeños, uno dentro de cada desencadenador, que proporcionan ráfagas de vibración nítidas directamente a los dedos del desencadenador del usuario; esta capacidad única del controlador para juegos de Xbox One es la razón por la que sus desencadenadores se conocen como desencadenadores de impulso. Mediante la orquestación de estos motores, se puede producir una amplia gama de sensaciones táctiles.
Uso de la vibración y el impulso
La vibración del controlador para juegos se controla a través de la propiedad Vibration de la clase Gamepad. Vibration
es una instancia de la estructura GamepadVibration que se compone de cuatro valores de punto flotante; cada valor representa la intensidad de uno de los motores.
Aunque los miembros de la propiedad Gamepad.Vibration
se pueden modificar directamente, se recomienda inicializar una instancia de GamepadVibration
independiente a los valores que desee y, a continuación, copiarlos en la propiedad Gamepad.Vibration
para cambiar la densidad del motor real a la vez.
En el ejemplo siguiente se muestra cómo cambiar la densidad del motor a la vez.
// get the first gamepad
Gamepad^ gamepad = Gamepad::Gamepads->GetAt(0);
// create an instance of GamepadVibration
GamepadVibration vibration;
// ... set vibration levels on vibration struct here
// copy the GamepadVibration struct to the gamepad
gamepad.Vibration = vibration;
// get the first gamepad
Gamepad gamepad = Gamepad.Gamepads[0];
// create an instance of GamepadVibration
GamepadVibration vibration = new GamepadVibration();
// ... set vibration levels on vibration struct here
// copy the GamepadVibration struct to the gamepad
gamepad.Vibration = vibration;
Uso de los motores de vibración
Los motores de vibración izquierdo y derecho toman valores de punto flotante entre 0,0 (sin vibración) y 1,0 (vibración más intensa). La intensidad del motor izquierdo se establece mediante la propiedad LeftMotor
de la estructura GamepadVibration; la intensidad del motor derecho se establece mediante la propiedad RightMotor
.
En el ejemplo siguiente se establece la intensidad de los motores de vibración y se activa la vibración del controlador para juegos.
GamepadVibration vibration;
vibration.LeftMotor = 0.80; // sets the intensity of the left motor to 80%
vibration.RightMotor = 0.25; // sets the intensity of the right motor to 25%
gamepad.Vibration = vibration;
GamepadVibration vibration = new GamepadVibration();
vibration.LeftMotor = 0.80; // sets the intensity of the left motor to 80%
vibration.RightMotor = 0.25; // sets the intensity of the right motor to 25%
mainGamepad.Vibration = vibration;
Recuerde que estos dos motores no son idénticos, por lo que establecer estas propiedades en el mismo valor no produce la misma vibración en un motor que en el otro. Para cualquier valor, el motor izquierdo produce una vibración más fuerte con una frecuencia menor que el motor derecho que, para el mismo valor, produce una vibración más suave con una frecuencia mayor. Incluso en el valor máximo, el motor izquierdo no puede producir las altas frecuencias del motor derecho, ni puede el motor derecho producir las altas fuerzas del motor izquierdo. Aun así, dado que los motores están conectados rígidamente por el cuerpo del controlador para juegos, los jugadores no experimentan las vibraciones completamente de forma independiente, aunque los motores tienen características diferentes y pueden vibrar con diferentes intensidades. Esta disposición permite producir una gama más amplia y expresiva de sensaciones que si los motores fueran idénticos.
Uso de los desencadenadores de impulso
Cada motor de desencadenador de impulso toma un valor de punto flotante entre 0,0 (sin vibración) y 1,0 (vibración más intensa). La intensidad del motor del desencadenador izquierdo se establece mediante la LeftTrigger
propiedad de la estructura GamepadVibration; la intensidad del desencadenador derecho se establece mediante la propiedadRightTrigger
.
En el ejemplo siguiente se establece la intensidad de ambos desencadenadores de impulso y se activan.
GamepadVibration vibration;
vibration.LeftTrigger = 0.75; // sets the intensity of the left trigger to 75%
vibration.RightTrigger = 0.50; // sets the intensity of the right trigger to 50%
gamepad.Vibration = vibration;
GamepadVibration vibration = new GamepadVibration();
vibration.LeftTrigger = 0.75; // sets the intensity of the left trigger to 75%
vibration.RightTrigger = 0.50; // sets the intensity of the right trigger to 50%
mainGamepad.Vibration = vibration;
A diferencia de los otros, los dos motores de vibración dentro de los desencadenadores son idénticos, por lo que producen la misma vibración en cualquiera de los motores para el mismo valor. Sin embargo, dado que estos motores no están conectados rígidamente de ninguna manera, los jugadores experimentan las vibraciones de forma independiente. Esta disposición permite dirigir sensaciones completamente independientes a ambos desencadenadores simultáneamente, y les ayuda a transmitir información más específica que los motores del cuerpo del controlador para juegos.
Ejecutar la muestra de vibración del controlador para juegos
El ejemplo GamepadVibrationUWP(github) muestra cómo se usan los motores de vibración del controlador para juegos y los desencadenadores de impulso para producir una variedad de efectos.