Documentação do Steamworks
Emulação de controle da Entrada Steam — Práticas recomendadas
A Entrada Steam é um serviço que permite que usuários Steam joguem jogos compatíveis com controle usando o dispositivo que preferirem. A Entrada Steam converte a entrada do usuário em algo que o jogo compreenda, seja por emulação de controle, emulação de mouse e teclado ou pela API da Entrada Steam. Neste artigo, falaremos sobre como melhor usar a emulação de controle da Entrada Steam para expandir a compatibilidade com controle de um jogo.

O que é a emulação de controle?
  • No Windows, o Painel Steam se associará a APIs tradicionais de entrada por controle, como XInput, DirectInput, RawInput e Windows.Gaming.Input, e injetará um controle de Xbox emulado. No macOS e Linux, a entrada do controle emulado é feita por meio de um driver.
  • O jogo verá o controle como um controle de Xbox, o que significa que controles com entradas a mais terão algumas ações repetidas; por exemplo, o botão Start da API XInput será mapeado ao clique do trackpad e ao botão OPTIONS em controles DUALSHOCK 4.
  • Além da entrada normal do controle, é possível associar a entrada do giroscópio de controles de Switch, PlayStation e do Controle Steam a ações de emulação do mouse, permitindo controles de movimento. Isso só funciona em jogos com um único jogador local porque só há uma entrada de mouse por sistema. Além disso, esse recurso requer que o jogo aceite entrada simultânea do mouse e do controle. Caso esteja interessado em usar tais recursos sem essas ressalvas, considere integrar a API da Entrada Steam ao jogo.
  • É possível perguntar ao Steam o tipo do controle para exibir os ícones do dispositivo em questão, mas você estará limitado aos tipos compatíveis com a versão atual do SDK do Steamworks. Para que não precise se preocupar com ícones de tipos adicionados futuramente, integre a API da Entrada Steam ao jogo.
  • Alguns usuários podem usar a Entrada Steam com controles reconhecidos pelo jogo porque o Steam Remote Play faz uso dela para a entrada durante transmissão. Além disso, muitos usuários ativam a Entrada Steam para reconfigurar os controles das suas Bibliotecas Steam. No geral, aproximadamente 25% de todas as seções com controle no Steam em 2020 fez uso da Entrada Steam, incluindo quase metade de todas as seções com controles de PlayStation.

Mais de 2.000 jogos Steam usam a emulação de controle para pelo menos um tipo de controle, incluindo jogos notáveis como Monster Hunter: World, Ace Combat 7, Dragon Quest XI, Into the Breach e Terra-média: Sombras da Guerra, mas nem todos seguem todas as práticas recomendadas. Neste artigo, daremos destaque ao jogo Into the Breach pelo seu ótimo trabalho em segui-las.

Exibição de ícones específicos de cada dispositivo

Há algumas formas de se obter os ícones de dispositivos ao usar a emulação de controles — uma para jogos que podem carregar imagens em tempo de execução (e que exibirá ícones de novos dispositivos adicionados à Entrada Steam sem precisar atualizar o jogo) e duas outras para jogos que precisam integrar as imagens aos seus recursos gráficos ou que desejam usar as mesmas artes das versões do jogo para consoles. Into the Breach usa artes próprias para o Controle Steam e controles de Xbox:

intothebreach_xbox.PNG

de PlayStation:

intothebreach_ps4.PNG

e de Nintendo Switch:

intothebreach_switchpro.PNG
Observação: mesmo ao usar artes próprias, aconselhamos usar as artes do Steam quando não reconhecer um controle ou então usar a nossa função auxiliar para usar a opção mais próxima existente na época do lançamento do jogo. Assim, dispositivos futuros terão ícones próximos aos verdadeiros.

Exibição de ícones "autoatualizáveis" do Steam

Use as funções a seguir:

Código de exemplo:

// Inicializa a interface antes de usar funções individuais. Só precisa ser chamada uma vez! SteamInput()->Init() // ... // A função SteamAPI_RunCallbacks() chamará as funções RunFrame para quaisquer interfaces inicializadas. // A maioria dos jogos já deve estar chamando-a periodicamente. Caso não seja o caso do seu jogo, // você precisará atualizar a interface da Entrada Steam manualmente. SteamInput()->RunFrame(); // ... // Troque o valor pelo índice do jogador na API XInput que deseja consultar. Os valores possíveis são 0 a 3. // Caso esteja usando a API RawInput para detectar dispositivos antes de decidir qual API usar, // consulte a seção "Uso da API RawInput para detecção de dispositivos". int nJogadorXInput = 0; // Troque pelo botão que será consultado EXboxOrigin eBotaoXboxCujoIconePegar = k_EXboxOrigin_A; EInputActionOrigin origemBotao = k_EInputActionOrigin_XBoxOne_A; // Se o controle estiver configurado pela Entrada Steam, converta o botão InputHandle_t handleControleUm = SteamInput()->GetControllerForGamepadIndex( nJogadorXInput ); if ( handleControleUm > 0 ) { // Handles válidos possuem valores diferentes de zero, logo este é um controle configurado pela Entrada Steam // Aviso: controles que usam a API da Entrada Steam não retornarão um handle pela função GetControllerForGamepadIndex() origemBotao = SteamInput()->GetActionOriginFromXboxOrigin( handleControleUm, k_EXboxOrigin_A ); } else { // Handles válidos possuem valores diferentes de zero, logo este é um controle de Xbox // Continue usando o botão original } // A enumeração EInputActionOrigin terá cada vez mais valores conforme o Steam adiciona compatibilidade, mas não há problema // porque neste exemplo pegaremos as imagens do Steam, que também pode fornecer um novo ícone // Recupera a imagem do cliente Steam const char *caminhoLocalIcone = SteamInput()->GetGlyphForActionOrigin( origemBotao ); printf( "caminho = %s\n", caminhoLocalIcone ); // "caminho = C:\Program Files (x86)\Steam\tenfoot\resource\images\library\controller\api\ps4_button_x.png" // Substitua com uma função do jogo que transforma o caminho para um arquivo em uma textura usável pelo jogo idTexturaIcone = transformarCaminhoEmTexturaIconeBotao( caminhoLocalIcone );

Exibição de arte própria: paleta de ícones

Caso esteja usando mais de uma paleta de ícones e escolha qual usar de acordo com o tipo do controle, pergunte ao Steam o tipo do controle associado a um índice XInput com as funções:

Código de exemplo:
// Inicializa a interface antes de usar funções individuais. Só precisa ser chamada uma vez! SteamInput()->Init() // ... // A função SteamAPI_RunCallbacks() chamará as funções RunFrame para quaisquer interfaces inicializadas. // A maioria dos jogos já deve estar chamando-a periodicamente. Caso não seja o caso do seu jogo, // você precisará atualizar a interface da Entrada Steam manualmente. SteamInput()->RunFrame(); // ... // Troque o valor pelo índice do jogador na API XInput que deseja consultar. Os valores possíveis são 0 a 3. // Caso esteja usando a API RawInput para detectar dispositivos antes de decidir qual API usar, // consulte a seção "Uso da API RawInput para detecção de dispositivos". int nJogadorXInput = 0; InputHandle_t handleControle = SteamInput()->GetControllerForGamepadIndex( nJogadorXInput ); if ( handleControle == 0 ) { // Handles válidos possuem valores diferentes de zero, logo este é um controle de Xbox. } else { // O Steam sempre retornará um valor da numeração válido para a versão do SDK em uso. ESteamInputType tipoEntrada = SteamInput()->GetInputTypeForHandle( handleControle ); switch(tipoEntrada) { case k_ESteamInputType_Unknown: printf("controle desconhecido!\n"); break; case k_ESteamInputType_SteamController: printf("Controle Steam!\n"); break; case k_ESteamInputType_XBox360Controller: printf("Controle de Xbox 360!\n"); break; case k_ESteamInputType_XBoxOneController: printf("Controle de Xbox One!\n"); break; case k_ESteamInputType_GenericXInput: printf("Controle XInput genérico!\n"); break; case k_ESteamInputType_PS4Controller: printf("Controle DUALSHOCK 4!\n"); break; } }

Exibição de arte própria: arte por botão

Caso esteja usando uma tabela de consulta indexada pela origem da ação, você pode usar as funções abaixo para implementar ícones específicos de cada dispositivo e converter botões de controles desconhecidos pelos equivalentes mais próximos.

Código de exemplo:
// Inicializa a interface antes de usar funções individuais. Só precisa ser chamada uma vez! SteamInput()->Init() // ... // A função SteamAPI_RunCallbacks() chamará as funções RunFrame para quaisquer interfaces inicializadas. // A maioria dos jogos já deve estar chamando-a periodicamente. Caso não seja o caso do seu jogo, // você precisará atualizar a interface da Entrada Steam manualmente. SteamInput()->RunFrame(); // ... // Troque o valor pelo índice do jogador na API XInput que deseja consultar. Os valores possíveis são 0 a 3. // Caso esteja usando a API RawInput para detectar dispositivos antes de decidir qual API usar, // consulte a seção "Uso da API RawInput para detecção de dispositivos". int nJogadorXInput = 0; // Troque pelo botão que será consultado EXboxOrigin eBotaoXboxCujoIconePegar = k_EXboxOrigin_A; EInputActionOrigin origemBotao = k_EInputActionOrigin_XBoxOne_A; // Se o controle estiver configurado pela Entrada Steam, converta o botão InputHandle_t handleControleUm = SteamInput()->GetControllerForGamepadIndex( nJogadorXInput ); if ( handleControleUm > 0 ) { // Handles válidos possuem valores diferentes de zero, logo este é um controle configurado pela Entrada Steam // Aviso: controles que usam a API da Entrada Steam não retornarão um handle pela função GetControllerForGamepadIndex() origemBotao = SteamInput()->GetActionOriginFromXboxOrigin( handleControleUm, k_EXboxOrigin_A ); //ou seja, k_EInputActionOrigin_PS4_X } else { // Handles válidos possuem valores diferentes de zero, logo este é um controle de Xbox // Continue usando o botão original } // O Steam continuará a adicionar origens de ação e controles futuros ultrapassarão o intervalo atual // Para testar, finja que controles de Switch e PS5 não existem e use o if comentado no lugar do if não comentado: // if ( origemBotao >= k_EInputActionOrigin_XBox360_Reserved10 ) if ( origemBotao >= k_EInputActionOrigin_Count ) { // Não temos nenhuma arte para esta origem no jogo! Parece que o Steam adicionou compatibilidade //com um controle novo. Vamos recuperar o valor mais próximo disponível no SDK que usamos origemBotao = SteamInput()->TranslateActionOrigin( k_ESteamInputType_Unknown, origemBotao ); } // Substitua com uma função do jogo que retorna a arte de um botão int idTexturaIcone = recuperarTexturaIconeBotaoIncluso( origemBotao );

Uso da API RawInput para detecção de dispositivos

Alguns jogos usam a API RawInput para funcionar com mais de quatro controles ou para determinar se há controles de PlayStation conectados. Essa API também retorna dados sobre dispositivos que não são controles e não possui um índice confiável para mapeamento a partir das funções da Entrada Steam. No lugar, você pode obter esses dados ao consultar a string RIDI_DEVICENAME a partir da função GetRawInputDeviceInfo(), que usa o seguinte formato para codificar o VID/PID USB do dispositivo verdadeiro, índice do controle e handle na API da Entrada Steam:
\\.\pipe\HID#VID_045E&PID_028E&IG_00#{VID do dispositivo verdadeiro}&{PID do dispositivo verdadeiro}&{handle na API da Entrada Steam}#{Índice do controle}#{ID do processo}
Por exemplo, \\.\pipe\HID#VID_045E&PID_028E&IG_00#045E&0B00&00045EB00704DAF3#0#20160 representa um controle de Xbox One com VID/PID igual a 0x45e/0x0b00.

Código de exemplo:
#define VID_CONTROLE_DIRECTINPUT_VALVE 0x28DE #define PID_CONTROLE_DIRECTINPUT_VALVE 0x11FF #define ENTRADA_STEAM_INDICE_VID 37 #define ENTRADA_STEAM_INDICE_PID 42 #define ENTRADA_STEAM_INDICE_HANDLE_API_ES 47 #define ENTRADA_STEAM_INDICE_CONTROLE 64 #define TAM_MAX_CAMINHO 260 //... void SuaFuncao() { PRAWINPUTDEVICELIST dispositivos = NULL; UINT i, j, qtd_dispositivos = 0; if ((GetRawInputDeviceList(NULL, &qtd_dispositivos, sizeof(RAWINPUTDEVICELIST)) == -1) || (!qtd_dispositivos)) { return; /* sem controles. */ } dispositivos = (PRAWINPUTDEVICELIST)malloc(sizeof(RAWINPUTDEVICELIST) * qtd_dispositivos); if (dispositivos == NULL) { return; } if (GetRawInputDeviceList(dispositivos, &qtd_dispositivos, sizeof(RAWINPUTDEVICELIST)) == -1) { free(dispositivos); return; /* sem controles. */ } for (i = 0; i < qtd_dispositivos; i++) { RID_DEVICE_INFO rdi; char nomeDisp[TAM_MAX_CAMINHO]; UINT tamRdi = sizeof(rdi); UINT tamNome = TAM_MAX_CAMINHO; rdi.cbSize = sizeof(rdi); if ( dispositivos[i].dwType == RIM_TYPEHID && GetRawInputDeviceInfoA( dispositivos[i].hDevice, RIDI_DEVICEINFO, &rdi, &tamRdi ) != (UINT)-1 && GetRawInputDeviceInfoA( dispositivos[i].hDevice, RIDI_DEVICENAME, nomeDisp, &tamNome ) != (UINT)-1 ) { if ( rdi.hid.dwVendorId == VID_CONTROLE_DIRECTINPUT_VALVE && rdi.hid.dwProductId == PID_CONTROLE_DIRECTINPUT_VALVE ) { uint32 ulVID = strtoul( &nomeDisp[ENTRADA_STEAM_INDICE_VID], NULL, 16 ); uint32 ulPID = strtoul( &nomeDisp[ENTRADA_STEAM_INDICE_PID], NULL, 16 ); uint64 ulHandleControle = strtoull( &nomeDisp[ENTRADA_STEAM_INDICE_HANDLE_API_ES], NULL, 16 ); uint32 unIndiceControle = strtoul( &nomeDisp[ENTRADA_STEAM_INDICE_CONTROLE], NULL, 16 ); [/i][/i][/i] Log( "Dispositivo RawInput: VID = 0x%x, PID = 0x%x, handle = 0x%llx, índice = 0x%x, %s\n", ulVID, ulPID, ulHandleControle, unIndiceControle, nomeDisp ); // Agora, use o VID/PID para identificar o dispositivo diretamente ou use ulHandleControle com GetInputTypeForHandle ESteamInputType tipoEntrada = SteamInput()->GetInputTypeForHandle( ulHandleControle ); switch( tipoEntrada ) { case k_ESteamInputType_Unknown: printf( "controle desconhecido!\n" ); break; case k_ESteamInputType_SteamController: printf( "Controle Steam!\n" ); break; case k_ESteamInputType_XBox360Controller: printf( "Controle de Xbox 360!\n" ); break; case k_ESteamInputType_XBoxOneController: printf( "Controle de Xbox One!\n" ); break; case k_ESteamInputType_GenericGamepad: printf( "Controle genérico (DirectInput)!\n" ); break; case k_ESteamInputType_PS3Controller: case k_ESteamInputType_PS4Controller: case k_ESteamInputType_PS5Controller: printf( "Controle de PlayStation!\n" ); break; } } else { // O dispositivo não é um controle usado pela Entrada Steam, use a sua lógica de identificação normal continue; } } } free(dispositivos); }

Configuração no Steamworks

O primeiro passo é configurar quais controles usarão a Entrada Steam na seção "Usar a Entrada Steam" da página Aplicativo->Entrada Steam das configurações do Steamworks. Para um jogo normal compatível com controles de Xbox, aconselhamos assinalar todas as caixas, menos a de Xbox:

steamworks_steam_input_optin_settings.png

Caso o jogo seja compatível com manetes de voo ou volantes, desmarque a caixa para controles genéricos/DirectInput porque o Steam não permite remapear tais dispositivos.

Seleção de configuração

Você pode escolher criar uma configuração própria ou escolher um dos nossos modelos pré-configurados. Não é necessário criar uma configuração própria, a menos que planeje ajustar os nossos modelos ou rotular cada botão de acordo com o que fazem no jogo. Os modelos da Entrada Steam estão traduzidos em todos os idiomas do Steam, então traduza a sua configuração para evitar que usuários de outros idiomas vejam a configuração em inglês.

Escolha de um modelo

Não há diferenças entre os modelos de controle caso o usuário use um controle de console, mas há diferenças caso use o Controle Steam. Segue abaixo uma explicação dos modelos para quais tipos de jogos cada um deles foi feito:
  • Controle padrão — Emula um controle de Xbox. O trackpad direito funciona como a alavanca direita. Perfeito para jogos de nave, plataforma ou esportes.
  • Controle com câmera/mira precisa — Esta configuração é ideal para um jogo de tiro em primeira pessoa ou outros jogos com controle de câmera. Não se esqueça de testar se o jogo permite o uso simultâneo de controle e mouse antes de selecioná-lo; isso inclui telas auxiliares, como de inventário e mapa.
  • Controle com controle de câmera — Esta configuração pega a entrada absoluta (similar a mouse) de um trackpad e a traduz em movimentos relativos de uma alavanca emulada. Se o jogo puder ser configurado para usar alavancas com altíssima sensibilidade, sem zona morta e uma curva de aceleração linear, esta configuração poderá funcionar bem. Caso contrário, aconselhamos o uso de uma configuração de mouse e teclado.

steam_input_gamepad_templates.png

Os passos para definir a configuração de controle para o jogo no Steamworks estão disponíveis aqui

Criação de configuração personalizada

Configurações podem ser convertidas de um controle para outro automaticamente, mas é aconselhável ter pelo menos uma configuração para o Controle Steam e outra para controles de Xbox.
Observação: caso deseje usar recursos específicos de cada dispositivo, como controles por movimento, crie configurações também para controles de PlayStation 4 (DUALSHOCK 4) e Nintendo Switch.

Entrada de texto


A entrada de texto por teclado virtual não é tecnicamente parte da API ISteamInput (Entrada Steam), mas sim da API ISteamUtils. Atualmente, ela só funciona quando o usuário iniciar o jogo pelo modo Big Picture.

Referências rápidas:

Tradução da configuração
A criação de uma configuração traduzida é um pouco mais complicada quando não se está usando a API da Entrada Steam.
  • Para começarmos, confirme que não editou a configuração desde a última vez que foi exportada. Abra o configurador e edite-a (não tem problema editar alguma coisa e depois reverter).
  • Abra o arquivo Steam\Logs\controller_ui.txt e procure uma linha com o seguinte texto:
    Steam Controller Saving Controller Configuration Autosave for [Nº DE SÉRIE DO CONTROLE]- AppID: [APPID DO JOGO]. Loaded Config for Local Selection Path for App ID [APPID DO JOGO], Controller 0: F:\ProgramFiles\Steam\client\userdata\[ID STEAM]\config\controller_configs\apps\[APP ID DO JOGO]\[Nº DE SÉRIE DO CONTROLE]\guest\controller_configuration.vdf
  • Insira tokens de tradução nas ações que deseja traduzir:
    • Título e desc:
      "controller_mappings" { "version" "3" "revision" "5" "title" "#titulo" "description" "#descricao" ...
    • Ações de botões:
      "button_a" { "activators" { "Full_Press" { "bindings" { "binding" "xinput_button A, #abutton" } } } } ...
  • Defina valores correspondentes para cada idioma no bloco "localization":
    "localization" { "brazilian" { "titulo" "Título traduzido" "descricao" "Esta é uma descrição traduzida. Os botões frontais também têm nomes localizados." "botao_a" "Ação do botão A" "botao_b" "Ação do botão B" "botao_x" "Ação do botão X" "botao_y" "Ação do botão Y" } ...
  • Agora exporte a configuração e siga estas instruções para associá-la ao jogo. Assinale a opção "Usar bloco de ações" para uma das configurações.

Para pré-visualizar a configuração de exemplo no cliente Steam, acesse este URL no navegador: steam://controllerconfig/681280/1744110334. O arquivo VDF correspondente está disponível aqui.