Gamepad e vibração
Esta página descreve os conceitos básicos de programação para game pads usando [Windows.Gaming.Input.Gamepad][game pad] e APIs relacionadas para a Plataforma Universal do Windows (UWP).
Ao ler esta página, você aprenderá:
- como reunir uma lista de game pads conectados e seus usuários
- como detectar que um painel de jogos foi adicionado ou removido
- como ler a entrada de um ou mais game pads
- como enviar comandos de vibração e impulso
- como os game pads se comportam como dispositivos de navegação da interface do usuário
Visão geral do Gamepad
Gamepads como o Xbox Wireless Controller e o Xbox Wireless Controller S são dispositivos de entrada para jogos de uso geral. Eles são o dispositivo de entrada padrão no Xbox One e uma opção comum para os jogadores do Windows quando eles não favorecem um teclado e um mouse. Os gamepads têm suporte nos aplicativos Windows 10 ou Windows 11 e Xbox UWP por meio do namespace Windows.Gaming.Input.
Os gamepads do Xbox One são equipados com um botão direcional (ou D-pad); botões A, B, X, Y, Exibir e Menu; botões esquerdo e direito, botões superiores e gatilhos; e um total de quatro motores de vibração. Ambos os botões fornecem leituras analógicas duplas nos eixos X e Y e também atuam como um botão quando pressionados para dentro. Cada gatilho fornece uma leitura analógica que representa o quão longe ele é puxado para trás.
Observação
Windows.Gaming.Input.Gamepad
também dá suporte a gamepads do Xbox 360, que têm o mesmo layout de controle que os gamepads padrão do Xbox One.
Gatilhos de vibração e impulso
Os gamepads do Xbox One fornecem dois motores independentes para vibração forte e sutil do gamepad, bem como dois motores dedicados para fornecer vibração acentuada para cada gatilho (esse recurso exclusivo é a razão pela qual os gatilhos de gamepad do Xbox One são chamados de gatilhos de impulso).
Observação
Os gamepads do Xbox 360 não são equipados com gatilhos de impulso.
Para obter mais informações, consulte Visão geral dos gatilhos de vibração e impulso.
Zonas mortas de botões
Um polegar em repouso na posição central produziria idealmente a mesma leitura neutra nos eixos X e Y todas as vezes. No entanto, devido às forças mecânicas e à sensibilidade do botão, as leituras reais na posição central só se aproximam do valor neutro ideal e podem variar entre as leituras subsequentes. Por esse motivo, você sempre deve usar uma pequena zona morta – um intervalo de valores próximos à posição ideal central que são ignorados – para compensar diferenças de fabricação, o desgaste mecânico ou outros problemas do gamepad.
Zonas mortas maiores oferecem uma estratégia simples para separar a entrada intencional de entrada não intencional.
Para obter mais informações, consulte Lendo os botões.
Navegação na interface do usuário
A fim de aliviar o fardo de suportar os diferentes dispositivos de entrada para a navegação da interface do usuário e incentivar a consistência entre jogos e dispositivos, a maioria dos dispositivos de entrada físicos atua simultaneamente como um dispositivo de entrada separado lógico chamado controlador de navegação UI. O controle de navegação na interface do usuário fornece um vocabulário comum para comandos de navegação na interface do usuário em dispositivos de entrada.
Como um controlador de navegação da interface do usuário, o gamepad mapeia o conjunto obrigatório de comandos de navegação para o botão esquerdo, direcional, botões Exibir, Menu, A e B.
Comando de navegação | Entrada do gamepad |
---|---|
Operante | Botão esquerdo para cima/D-pad up |
Para baixo | Botão esquerdo para baixo/D-pad para baixo |
Esquerda | Botão esquerdo do botão esquerdo/D-pad à esquerda |
Right | Botão direito do polegar esquerdo/D-pad à direita |
Visualizar | Botão Exibir |
Menu | Botão de menu |
Aceitar | Botão A |
Cancelar | Botão B |
Além disso, os gamepads mapeiam todo o conjunto opcional de comandos de navegação para as entradas restantes.
Comando de navegação | Entrada do gamepad |
---|---|
Page Up | Gatilho esquerdo |
Page Down | Gatilho direito |
Página à esquerda | LB (botão superior esquerdo) |
Página à direita | RB (botão superior direito) |
Rolar para cima | Botão direito para cima |
Rolar para baixo | Botão direito para baixo |
Rolar para a esquerda | Botão direito para a esquerda |
Rolar para a direita | Botão direito do mouse para a direita |
Contexto 1 | Botão X |
Contexto 2 | Botão Y |
Contexto 3 | Pressionar o botão esquerdo |
Contexto 4 | Pressione o botão direito do mouse |
Detectar e acompanhar gamepads
Os gamepads são gerenciados pelo sistema, portanto, você não precisa criá-los ou inicializá-los. O sistema fornece uma lista de gamepads e eventos conectados para notificá-lo quando um gamepad é adicionado ou removido.
A lista de gamepads
A classe Gamepad fornece uma propriedade estática, Gamepads, que é uma lista somente leitura de gamepads conectados no momento. Como você pode estar interessado apenas em alguns dos gamepads conectados, é recomendável manter sua própria coleção em vez de acessá-las por meio da Gamepads
propriedade.
O exemplo a seguir copia todos os gamepads conectados em uma nova coleção. Observe que, como outros threads em segundo plano estarão acessando essa coleção (nos eventos GamepadAdded e GamepadRemoved ), você precisa colocar um bloqueio em torno de qualquer código que leia ou atualize a coleção.
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);
}
}
}
}
Adicionando e removendo gamepads
Quando um gamepad é adicionado ou removido, os eventos GamepadAdded e GamepadRemoved são gerados. Você pode registrar manipuladores para esses eventos para acompanhar os gamepads que estão conectados no momento.
O exemplo a seguir começa a rastrear um gamepad que foi adicionado.
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);
}
}
};
O exemplo a seguir interrompe o acompanhamento de um gamepad que foi removido. Você também precisará lidar com o que acontece com os gamepads que você está acompanhando quando eles são removidos; por exemplo, esse código controla apenas a entrada de um gamepad e simplesmente o define para nullptr
quando ele é removido. Você precisará verificar cada quadro se o gamepad está ativo e atualizar qual gamepad você está coletando entradas de quando os controladores estão conectados e 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);
}
}
};
Consulte as Práticas de entrada para jogos para obter mais informações.
Usuários e headsets
Cada gamepad pode ser associado a uma conta de usuário para vincular sua identidade ao jogo e pode ter um headset anexado para facilitar o chat de voz ou recursos no jogo. Para saber mais sobre como trabalhar com usuários e headsets, consulte Acompanhamento de usuários e seus dispositivos e headset.
Lendo o gamepad
Depois de identificar o gamepad no qual você está interessado, você estará pronto para coletar a entrada dele. No entanto, ao contrário de alguns outros tipos de entrada que você pode estar acostumado, os gamepads não comunicam a alteração de estado aumentando os eventos. Em vez disso, você faz leituras regulares de seus estados atuais sondando-os.
Sondando o gamepad
A sondagem captura um instantâneo do dispositivo de navegação em um ponto preciso no tempo. Essa abordagem para coleta de entrada é uma boa opção para a maioria dos jogos porque sua lógica normalmente é executada em um loop determinístico em vez de ser orientada por eventos. Também é normalmente mais simples interpretar comandos de jogo a partir de entradas coletadas de uma só vez do que de muitas entradas únicas coletadas ao longo do tempo.
Você sonda um gamepad chamando GetCurrentReading; essa função retorna um GamepadReading que contém o estado do gamepad.
O exemplo a seguir sonda um gamepad para seu estado atual.
auto gamepad = myGamepads[0];
GamepadReading reading = gamepad->GetCurrentReading();
Gamepad gamepad = myGamepads[0];
GamepadReading reading = gamepad.GetCurrentReading();
Além do estado do gamepad, cada leitura inclui um carimbo de data/hora que indica precisamente quando o estado foi recuperado. O carimbo de data/hora é útil para se relacionar com o tempo das leituras anteriores ou com o tempo da simulação do jogo.
Lendo os botões
Cada botão fornece uma leitura analógica entre -1.0 e +1.0 nos eixos X e Y. No eixo X, um valor de -1.0 corresponde à posição de botão mais à esquerda; um valor +1.0 corresponde à posição mais à direita. No eixo Y, um valor de -1.0 corresponde à posição do botão inferior mais baixo; um valor +1.0 corresponde à posição mais alta. Em ambos os eixos, o valor é aproximadamente 0,0 quando a vara está na posição central, mas é normal que o valor preciso varie, mesmo entre as leituras subsequentes; as estratégias para atenuar essa variação são discutidas posteriormente nesta seção.
O valor do eixo X do botão esquerdo é lido da propriedade LeftThumbstickX
da estrutura GamepadReading; o valor do eixo Y é lido da propriedade LeftThumbstickY
. O valor do eixo X do botão direito é lido da propriedade RightThumbstickX
; o valor do eixo Y é lido da propriedade RightThumbstickY
.
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
Ao ler os valores do botão, você observará que eles não produzem de forma confiável uma leitura neutra de 0,0 quando o botão estiver em repouso na posição central; Em vez disso, eles produzirão valores diferentes perto de 0,0 cada vez que o botão for movido e retornado para a posição central. Para atenuar essas variações, você pode implementar uma pequena zona morta, que é um intervalo de valores próximos à posição central ideal que são ignorados. Uma maneira de implementar uma zona morta é determinar o quão longe do centro o botão se moveu e ignorando as leituras mais próximas do que alguma distância que você escolher. Você pode calcular a distância aproximadamente — não é exato porque as leituras de botões são essencialmente valores polares, não planar, apenas usando o teorema pitagórico. Isso produz uma zona morta radial.
O exemplo a seguir demonstra uma zona morta radial básica usando o teorema de Pitágoras.
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 botão também atua como um botão quando pressionado para dentro; para obter mais informações sobre como ler essa entrada, consulte Ler os botões.
Lendo os gatilhos
Os gatilhos são representados como valores de ponto flutuante entre 0,0 (totalmente liberado) e 1,0 (totalmente deprimidos). O valor do gatilho esquerdo é lido da LeftTrigger
propriedade da estrutura GamepadReading; o valor do gatilho à direita é lido da RightTrigger
propriedade.
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
Como ler os botões
Cada um dos botões do gamepad — as quatro direções do painel D, para-choques esquerdo e direito, pressionamento do botão direito e esquerdo, A, B, X, Y, Exibiçãoe Menu— fornece uma leitura digital que indica se ele é pressionado (para baixo) ou liberado (para cima). Para garantir a eficiência, as leituras dos botões não são representadas como valores booleanos individuais; elas são reunidas em um único campo de bits que é representado pela enumeração GamepadButtons.
Os valores de botão são lidos da Buttons
propriedade da estrutura GamepadReading. Como essa propriedade é um campo de bits, o mascaramento bit a bit é usado para isolar o valor do botão no qual você está interessado. O botão é pressionado (para baixo) quando o bit correspondente é definido; caso contrário, é liberado (para cima).
O exemplo a seguir determina se o botão A é pressionado.
if (GamepadButtons::A == (reading.Buttons & GamepadButtons::A))
{
// button A is pressed
}
if (GamepadButtons.A == (reading.Buttons & GamepadButtons.A))
{
// button A is pressed
}
O exemplo a seguir determina se o botão A é liberado.
if (GamepadButtons::None == (reading.Buttons & GamepadButtons::A))
{
// button A is released
}
if (GamepadButtons.None == (reading.Buttons & GamepadButtons.A))
{
// button A is released
}
Às vezes, talvez você queira determinar quando um botão faz a transição de pressionado para liberado ou liberado para pressionado, se vários botões são pressionados ou liberados ou se um conjunto de botões é organizado de uma maneira específica— alguns pressionados, outros não. Para obter informações sobre como detectar cada uma dessas condições, consulte Detectando transições de botão e Detectando arranjos de botões complexos.
Executar o exemplo de entrada do gamepad
O exemplo de GamepadUWP (github) demonstra como se conectar a um gamepad e ler seu estado.
Visão geral dos gatilhos de vibração e impulso
Os motores de vibração dentro de um gamepad são para fornecer comentários táteis ao usuário. Os jogos usam essa capacidade de criar uma maior sensação de imersão, para ajudar a comunicar informações de status (como causar danos), sinalizar a proximidade com objetos importantes ou para outros usos criativos.
Os gamepads do Xbox One são equipados com um total de quatro motores de vibração independentes. Dois são motores grandes localizados no corpo do gamepad; o motor esquerdo fornece vibração bruta de alta amplitude, enquanto o motor direito fornece uma vibração mais sutil e suave. Os outros dois são motores pequenos, um dentro de cada gatilho, que fornecem rajadas acentuadas de vibração diretamente para os dedos de gatilho do usuário; essa capacidade exclusiva do gamepad do Xbox One é o motivo pelo qual seus gatilhos são chamados de gatilhos de impulso. Ao orquestrar esses motores juntos, uma ampla gama de sensações táteis pode ser produzida.
Usando vibração e impulso
A vibração do gamepad é controlada por meio da propriedade Vibration da classe Gamepad. Vibration
é uma instância da estrutura GamepadVibration que é composta por quatro valores de ponto flutuante, cada valor representa a intensidade de um dos motores.
Embora os membros da Gamepad.Vibration
propriedade possam ser modificados diretamente, é recomendável que você inicialize uma instância separada GamepadVibration
para os valores desejados e, em seguida, copie-a para a Gamepad.Vibration
propriedade para alterar as intensidades reais do motor de uma só vez.
O exemplo a seguir demonstra como alterar as intensidades motoras de uma só 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;
Usando os motores de vibração
Os motores de vibração esquerda e direita levam valores de ponto flutuante entre 0,0 (sem vibração) e 1,0 (vibração mais intensa). A intensidade do motor esquerdo é definida pela LeftMotor
propriedade da estrutura GamepadVibration; a intensidade do motor direito é definida pela RightMotor
propriedade.
O exemplo a seguir define a intensidade de ambos os motores de vibração e ativa a vibração do gamepad.
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;
Lembre-se de que esses dois motores não são idênticos, portanto, definir essas propriedades com o mesmo valor não produz a mesma vibração em um motor como no outro. Para qualquer valor, o motor esquerdo produz uma vibração mais forte em uma frequência menor do que o motor direito que—para o mesmo valor— produz uma vibração mais suave em uma frequência mais alta. Mesmo com o valor máximo, o motor esquerdo não pode produzir as altas frequências do motor direito, nem o motor direito pode produzir as forças altas do motor esquerdo. Ainda assim, como os motores são rigidamente conectados pelo corpo do gamepad, os jogadores não experimentam as vibrações totalmente independentemente, embora os motores tenham características diferentes e possam vibrar com diferentes intensidades. Esse arranjo permite que uma gama mais ampla e expressiva de sensações seja produzida do que se os motores fossem idênticos.
Usando os gatilhos de impulso
Cada motor de gatilho de impulso usa um valor de ponto flutuante entre 0,0 (sem vibração) e 1,0 (vibração mais intensa). A intensidade do motor do gatilho esquerdo é definida pela LeftTrigger
propriedade da estrutura GamepadVibration; a intensidade do gatilho direito é definida pela RightTrigger
propriedade.
O exemplo a seguir define a intensidade de ambos os gatilhos de impulso e os ativa.
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;
Ao contrário dos outros, os dois motores de vibração dentro dos gatilhos são idênticos para que produzam a mesma vibração em qualquer motor para o mesmo valor. No entanto, como esses motores não estão rigidamente conectados de forma alguma, os jogadores experimentam as vibrações de forma independente. Essa disposição permite que sensações totalmente independentes sejam direcionadas a ambos os gatilhos simultaneamente e as ajuda a transmitir informações mais específicas do que os motores no corpo do gamepad podem.
Executar o exemplo de vibração do gamepad
O exemplo GamepadVibrationUWP (github) demonstra como os motores de vibração do gamepad e os gatilhos de impulso são usados para produzir uma variedade de efeitos.