Volante de corrida e force feedback

Esta página descreve as noções básicas de programação para volantes de corrida no Xbox One usando Windows.Gaming.Input.RacingWheel e APIs relacionadas para a Plataforma Universal do Windows (UWP).

Ao ler esta página, você aprenderá:

  • Como reunir uma lista de volantes de corrida conectados e seus usuários
  • Como detectar que um volante de corrida foi adicionado ou removido
  • Como ler a entrada de um ou mais volantes de corrida
  • Como enviar comandos force feedback
  • Como os volantes de corrida se comportam como dispositivos de navegação da interface do usuário

Visão geral do volante de corrida

Os volantes de corrida são dispositivos de entrada que se assemelham à sensação de um cockpit de carro de corrida real. Os volantes de corrida são o dispositivo de entrada perfeito para jogos de corrida no estilo arcade e simulação que apresentam carros ou caminhões. Os volantes de corrida são suportados nos aplicativos UWP do Windows 10 ou Windows 11 e Xbox One pelo namespace Windows.Gaming.Input.

Os volantes de corrida são oferecidos em uma variedade de preços, geralmente tendo mais e melhores recursos de entrada e force feedback à medida que seus pontos de preço aumentam. Todos os volantes de corrida são equipados com volante analógico, controles analógicos de aceleração e freio, e alguns botões no volante. Alguns volantes de corrida são adicionalmente equipados com controles analógicos de embreagem e freio de mão, trocas de marchas padrão e recursos de force feedback. Nem todos os volantes de corrida são equipados com os mesmos conjuntos de recursos, e também podem variar em seu suporte para determinados recursos - por exemplo, os volantes podem suportar diferentes faixas de rotação e os shifters padrão podem suportar diferentes números de marchas.

Funcionalidades de dispositivo

Diferentes volantes de corrida oferecem diferentes conjuntos de capacidades de dispositivo opcionais e diferentes níveis de suporte para essas capacidades; esse nível de variação entre um único tipo de dispositivo de entrada é exclusivo entre os dispositivos suportados pela API Windows.Gaming.Input. Além disso, a maioria dos dispositivos que você encontrará suportará pelo menos alguns recursos opcionais ou outras variações. Por isso, é importante determinar as capacidades de cada volante de corrida conectado individualmente e suportar toda a variação de recursos que faz sentido para o seu jogo.

Para mais informações, consulte Determinando os recursos do volante de corrida.

Force feedback

Alguns volantes de corrida oferecem um verdadeiro force feedback, ou seja, podem aplicar forças reais em um eixo de controle, como o volante, e não apenas uma simples vibração. Os jogos usam essa capacidade para criar uma maior sensação de imersão (dano de colisão simulado, "sensação de estrada") e para aumentar o desafio de dirigir bem.

Para obter mais informações, consulte Visão geral de force feedback.

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 controlador de navegação da interface do usuário fornece um vocabulário comum para comandos de navegação da interface do usuário em dispositivos de entrada.

Devido ao seu foco exclusivo em controles analógicos e ao grau de variação entre diferentes volantes de corrida, eles são normalmente equipados com um D-pad digital, botões Exibir, Menu, A, B, X e Y que se assemelham aos de um gamepad; Esses botões não se destinam a suportar comandos de jogabilidade e não podem ser facilmente acessados como botões de volante de corrida.

Como um controlador de navegação da interface do usuário, os volantes de corrida mapeiam o conjunto necessário de comandos de navegação para os botões polegar esquerdo, D-pad, Visualizar, Menu, A e B.

Comando de navegação Entrada de volante de corrida
Operante D-pad para cima
Para baixo D-pad para baixo
Esquerda D-pad esquerdo
Right D-pad direito
Visualizar Botão exibir
Menu Botão de menu
Aceitar Botão A
Cancelar Botão B

Além disso, alguns volantes de corrida podem mapear alguns dos conjunto opcional de comandos de navegação para outras entradas que suportam, mas os mapeamentos de comandos podem variar de dispositivo para dispositivo. Considere também oferecer suporte a esses comandos, mas certifique-se de que esses comandos não sejam essenciais para navegar na interface do jogo.

Comando de navegação Entrada de volante de corrida
Page Up varia
Page Down varia
Página à esquerda varia
Página à direita varia
Rolar para cima varia
Rolar para baixo varia
Rolar para a esquerda varia
Rolar para a direita varia
Contexto 1 Botão X (comumente)
Contexto 2 Botão Y (comumente)
Contexto 3 varia
Contexto 4 varia

Detectar e rastrear volantes de corrida

Detectar e rastrear volantes de corrida funciona exatamente da mesma maneira que funciona para gamepads, exceto com a classe RacingWheel em vez da classe Gamepad. Consulte Gamepad e vibração para obter mais informações.

Lendo o volante de corrida

Depois de identificar os volantes de corrida nos quais você está interessado, você está pronto para coletar informações deles. No entanto, ao contrário de alguns outros tipos de entrada com os quais você pode estar acostumado, os volantes de corrida não comunicam a mudança de estado ao gerar eventos. Em vez disso, você faz leituras regulares de seus estados atuais pesquisando eles.

Sondagem do volante

A sondagem captura um instantâneo do volante de corrida 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 volante de corrida chamando GetCurrentReading; essa função retorna um RacingWheelReading que contém o estado do volante de corrida.

O exemplo a seguir sonda um volante de corrida para seu estado atual.

auto racingwheel = myRacingWheels[0];

RacingWheelReading reading = racingwheel->GetCurrentReading();

Além do estado do volante de corrida, 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.

Determinando as capacidades do volante de corrida

Muitos dos controles do volante de corrida são opcionais ou suportam diferentes variações, mesmo nos controles necessários, então você tem que determinar as capacidades de cada volante de corrida individualmente antes de poder processar a entrada coletada em cada leitura do volante de corrida.

Os controles opcionais são freio de mão, embreagem e câmbio padrão; você pode determinar se um volante de corrida conectado suporta esses controles lendo as propriedades HasHandbrake, HasClutch e HasPatternShifter do volante de corrida, respectivamente. O controle terá suporte se o valor da propriedade for true; caso contrário, não é suportado.

if (racingwheel->HasHandbrake)
{
    // the handbrake is supported
}

if (racingwheel->HasClutch)
{
    // the clutch is supported
}

if (racingwheel->HasPatternShifter)
{
    // the pattern shifter is supported
}

Além disso, os controles que podem variar são o volante e o câmbio padrão. O volante pode variar pelo grau de rotação física que o volante real pode suportar, enquanto o câmbio padrão pode variar pelo número de marchas distintas que ele suporta. Você pode determinar o maior ângulo de rotação que a roda real suporta lendo a propriedade MaxWheelAngle do volante de corrida; seu valor é o ângulo físico máximo suportado em graus no sentido horário (positivo) que também é suportado no sentido anti-horário (graus negativos). Você pode determinar a maior marcha para frente que o câmbio padrão suporta lendo a propriedade MaxPatternShifterGear do volante de corrida; Seu valor é a marcha mais alta suportada, inclusive, ou seja, se seu valor for 4, o câmbio padrão suporta marchas à ré, neutra, primeira, segunda, terceira e quarta.

auto maxWheelDegrees = racingwheel->MaxWheelAngle;
auto maxShifterGears = racingwheel->MaxPatternShifterGear;

Finalmente, alguns volantes de corrida suportam oforce feedback através do volante. Você pode determinar se um volante de corrida conectado suporta force feedback lendo a propriedade WheelMotor do volante de corrida. Há suporte para force feedback se WheelMotor não for nulo; caso contrário, não é suportado.

if (racingwheel->WheelMotor != nullptr)
{
    // force feedback is supported
}

Para obter informações sobre como usar o recurso force feedback dos volantes de corrida que o suportam, consulte Visão geral do force feedback.

Lendo os botões

Cada um dos botões do volante de corrida — as quatro direções do D-pad, os botões Marcha anterior e Próxima marcha e 16 botões adicionais — fornece uma leitura digital que indica se ele está pressionado (para baixo) ou liberado (para cima). Para eficiência, as leituras de botões não são representadas como valores booleanos individuais; em vez disso, todos eles são empacotados em um único campo de bits que é representado pela enumeração RacingWheelButtons.

Observação

Os volantes de corrida são equipados com botões adicionais usados para navegação na interface do usuário, como os botões Exibir e Menu. Esses botões não fazem parte da enumeração RacingWheelButtons e só podem ser lidos acessando o volante de corrida como um dispositivo de navegação da interface do usuário. Para obter mais informações, consulte Dispositivo de navegação da interface do usuário.

Os valores de botão são lidos a partir da propriedade Buttons da estrutura RacingWheelReading. 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 Próxima marcha é pressionado.

if (RacingWheelButtons::NextGear == (reading.Buttons & RacingWheelButtons::NextGear))
{
    // Next Gear is pressed
}

O exemplo a seguir determina se o botão Próxima marcha é liberado.

if (RacingWheelButtons::None == (reading.Buttons & RacingWheelButtons::NextGear))
{
    // Next Gear is released (not pressed)
}

Às vezes, você pode querer 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 está organizado de uma maneira específica — alguns pressionados, outros não. Para obter informações sobre como detectar essas condições, consulte Detectando transições de botão e Detectando arranjos complexos de botões.

Lendo o volante

O volante é um comando obrigatório que fornece uma leitura analógica entre -1.0 e +1.0. Um valor de -1,0 corresponde à posição do volante mais à esquerda; um valor de +1,0 corresponde à posição mais à direita. O valor do volante é lido a partir da propriedade Wheel da estrutura RacingWheelReading.

float wheel = reading.Wheel;  // returns a value between -1.0 and +1.0.

Embora as leituras das rodas correspondam a diferentes graus de rotação física na roda real, dependendo da faixa de rotação suportada pelo controle de corrida física, você geralmente não deseja dimensionar as leituras do controle; controles que suportam maiores graus de rotação apenas proporcionam maior precisão.

Leitura do acelerador e freio

O acelerador e o freio são controles obrigatórios que fornecem leituras analógicas entre 0,0 (totalmente liberado) e 1,0 (totalmente pressionado) representados como valores de ponto flutuante. O valor do controle do acelerador é lido a partir da propriedade Throttle da estrutura RacingWheelReading; o valor do controle de freio é lido a partir da propriedade Brake.

float throttle = reading.Throttle;  // returns a value between 0.0 and 1.0
float brake    = reading.Brake;     // returns a value between 0.0 and 1.0

Leitura do freio de mão e da embreagem

O freio de mão e a embreagem são controles opcionais que fornecem leituras analógicas entre 0,0 (totalmente liberado) e 1,0 (totalmente engatado) representados como valores de ponto flutuante. O valor do controle do freio de mão é lido a partir da propriedade Handbrake da estrutura RacingWheelReading; o valor do controle de embreagem é lido a partir da propriedade Clutch.

float handbrake = 0.0;
float clutch = 0.0;

if(racingwheel->HasHandbrake)
{
    handbrake = reading.Handbrake;  // returns a value between 0.0 and 1.0
}

if(racingwheel->HasClutch)
{
    clutch = reading.Clutch;        // returns a value between 0.0 and 1.0
}

Lendo o câmbio padrão

O câmbio padrão é um controle opcional que fornece uma leitura digital entre -1 e MaxPatternShifterGear representado como um valor inteiro assinado. Um valor de -1 ou 0 corresponde às marchas reversa e neutra, respectivamente; valores cada vez mais positivos correspondem a maiores marchas para frente até MaxPatternShifterGear, inclusive. O valor do câmbio padrão é lido a partir da propriedade PatternShifterGear da estrutura RacingWheelReading.

if (racingwheel->HasPatternShifter)
{
    gear = reading.PatternShifterGear;
}

Observação

O câmbio padrão, quando suportado, existe ao lado dos botões necessários Marcha anterior e Próxima marcha que também afetam a marcha atual do carro do jogador. Uma estratégia simples para unificar essas entradas onde ambas estão presentes é ignorar o câmbio padrão (e embreagem) quando um jogador escolhe uma transmissão automática para seu carro, e ignorar os botões Marcha anterior e Próxima marcha quando um jogador escolhe uma transmissão manual para seu carro apenas se seu volante de corrida estiver equipado com um controle de câmbio padrão. Você pode implementar uma estratégia de unificação diferente se isso não for adequado para o seu jogo.

Executar o exemplo InputInterfacing

O aplicativo de exemplo InputInterfacingUWP no GitHub demonstra como usar volantes de corrida e diferentes tipos de dispositivos de entrada em conjunto; bem como esses dispositivos de entrada se comportam como controladores de navegação da interface do usuário.

Visão geral do force feedback

Muitos volantes de corrida têm capacidade de force feedback para proporcionar uma experiência de condução mais imersiva e desafiadora. As rodas de corrida que suportam o force feedback são normalmente equipadas com um único motor que aplica força ao volante ao longo de um único eixo, o eixo de rotação do volante. Há suporte para comentários forçados nos aplicativos UWP do Windows 10 ou Windows 11 e Xbox One por meio do namespace Windows.Gaming.Input.ForceFeedback

Observação

As APIs de force feedback são capazes de suportar vários eixos de força, mas nenhum volante de corrida atualmente suporta qualquer eixo de feedback além do de rotação do volante.

Usando o force feedback

Estas seções descrevem os conceitos básicos de programação de efeitos de force feedback para volantes de corrida. O feedback é aplicado usando efeitos, que são primeiro carregados no dispositivo de force feedback e, em seguida, podem ser iniciados, pausados, retomados e parados de maneira semelhante aos efeitos sonoros; no entanto, você deve primeiro determinar os recursos de feedback do volante de corrida.

Determinando os recursos de force feedback

Você pode determinar se um volante de corrida conectado suporta force feedback lendo a propriedade WheelMotor do volante de corrida. Não há suporte para comentários forçados se WheelMotor for nulo; caso contrário, o force feedback é suportado e você pode continuar a determinar os recursos de feedback específicos do motor, como os eixos que ele pode afetar.

if (racingwheel->WheelMotor != nullptr)
{
    auto axes = racingwheel->WheelMotor->SupportedAxes;

    if(ForceFeedbackEffectAxes::X == (axes & ForceFeedbackEffectAxes::X))
    {
        // Force can be applied through the X axis
    }

    if(ForceFeedbackEffectAxes::Y == (axes & ForceFeedbackEffectAxes::Y))
    {
        // Force can be applied through the Y axis
    }

    if(ForceFeedbackEffectAxes::Z == (axes & ForceFeedbackEffectAxes::Z))
    {
        // Force can be applied through the Z axis
    }
}

Efeitos de force feedback de carregamento

Os efeitos de force feedback são carregados no dispositivo de feedback onde são "jogados" de forma autônoma ao comando do seu jogo. São fornecidos vários efeitos básicos; efeitos personalizados podem ser criados por meio de uma classe que implementa a interface IForceFeedbackEffect.

Classe de efeito Descrição do efeito
ConditionForceEffect Um efeito que aplica força variável em resposta ao sensor de corrente dentro do dispositivo.
ConstantForceEffect Um efeito que aplica força constante ao longo de um vetor.
PeriodicForceEffect Um efeito que aplica força variável definida por uma forma de onda, ao longo de um vetor.
RampForceEffect Um efeito que aplica uma força linearmente crescente/decrescente ao longo de um vetor.
using FFLoadEffectResult = ForceFeedback::ForceFeedbackLoadEffectResult;

auto effect = ref new Windows.Gaming::Input::ForceFeedback::ConstantForceEffect();
auto time = TimeSpan(10000);

effect->SetParameters(Windows::Foundation::Numerics::float3(1.0f, 0.0f, 0.0f), time);

// Here, we assume 'racingwheel' is valid and supports force feedback

IAsyncOperation<FFLoadEffectResult>^ request
    = racingwheel->WheelMotor->LoadEffectAsync(effect);

auto loadEffectTask = Concurrency::create_task(request);

loadEffectTask.then([this](FFLoadEffectResult result)
{
    if (FFLoadEffectResult::Succeeded == result)
    {
        // effect successfully loaded
    }
    else
    {
        // effect failed to load
    }
}).wait();

Usando efeitos de realimentação de força

Uma vez carregados, os efeitos podem ser iniciados, pausados, retomados e interrompidos de forma síncrona chamando funções na propriedade WheelMotor do volante de corrida ou individualmente chamando funções no próprio efeito de feedback. Normalmente, você deve carregar todos os efeitos que deseja usar no dispositivo de feedback antes do início do jogo e, em seguida, usar suas respectivas funções SetParameters para atualizar os efeitos à medida que a jogabilidade progride.

if (ForceFeedbackEffectState::Running == effect->State)
{
    effect->Stop();
}
else
{
    effect->Start();
}

Finalmente, você pode ativar, desativar ou redefinir de forma assíncrona todo o sistema de force feedback em um volante de corrida específico sempre que precisar.

Confira também