Documentación de Steamworks
Emulación de gamepad de Steam Input - Mejores prácticas
Steam Input es un servicio de Steam que permite a los usuarios de Steam jugar a cualquier juego basado en mandos/controles con el dispositivo de su elección. Steam Input traducirá las entradas del usuario a algo que el juego entienda a través de ya sea emulación de
gamepad, emulación de ratón y tablero o API de Steam Input. Aquí nos centraremos en cómo usar la emulación de gamepad de Steam Input para extender la compatibilidad del mando existente de tu juego.

¿Qué es la emulación de gamepad?
  • En Windows, la interfaz de Steam enlazará la API de entrada tradicional de gamepad, como Xinput, DirectInput, RawInput y Windows.Gaming.Input e insertará un dispositivo de mando Xbox emulado. En macOS y Linux la entrada de mando emulada es proporcionada por un controlador.
  • El mando aparecerá en el juego como un mando Xbox, lo que significa que los mandos con entradas adicionales tendrán algunas de ellas duplicadas (p. ej., el clic del panel táctil y el botón «OPTIONS» de PlayStation, ambos asignados al botón «START» de XInput).
  • Además de la entrada de gamepad normal, es posible asignar las entradas de giroscopio de los mandos de Nintendo Switch y PlayStation, y del Steam Controller a la emulación de ratón y proporcionar controles de movimiento. Esto funciona solamente en juegos con un solo jugador local debido a que solo hay una entrada de ratón y también depende de que el juego acepte simultáneamente entradas de ratón y gamepad. Si estás interesado en estas características sin todas esas advertencias, por favor, considera añadir la API de Steam Input
  • Puedes consultar sobre el tipo de mando desde Steam para mostrar los glifos específicos de cada dispositivo, pero estás limitado a los tipos compatibles con tu SDK Steamworks actual. Si estás interesado en soporte para glifos independientemente de desarrollos futuros, por favor, añade la API de Steam Input.
  • Puede que tengas usuarios jugando con Steam Input incluso con mandos/controles que ya soportas debido a que Steam Remote Play lo usa para proporcionar entradas durante la retransmisión y una considerable cantidad de usuarios han habilitado Steam Input para sus mandos/controles en todas sus bibliotecas de Steam. En general, aproximadamente una cuarta parte de todas las sesiones en Steam en 2020 usaron Steam Input, incluyendo casi la mitad de todas las sesiones con mandos/controles de PlayStation.

Más de 2000 juegos en Steam usan Gamepad Emulation para al menos un tipo de mando, incluidos juegos notables como Monster Hunter: World, Ace Combat 7, Dragon Quest XI, Into the Breach y Middle Earth: Shadow of War, aunque no todos siguen la lista completa de mejores prácticas. Aquí destacaremos "Into the Breach" porque hacen un gran trabajo al seguir cada una de estas mejores prácticas.

Mostrando arte específico del dispositivo

Admitimos dos formas de obtener glifos específicos del dispositivo con la emulación de Steam Input Gamepad: una para juegos que pueden cargar imágenes en tiempo de ejecución que está preparada para el futuro (lo que significa que cuando Steam actualice Steam Input, funcionará sin ninguna actualización de juegos) y otra para juegos que necesitan integrar las imágenes en sus recursos o usar el mismo arte estilizado desde los puertos de la consola de su juego. Into the Breach usa su propio arte para el mando Xbox y el Steam Controller:

intothebreach_xbox.PNG

PlayStation:

intothebreach_ps4.PNG

y los mandos/controles de la Nintedo Switch:

intothebreach_switchpro.PNG
Nota: Incluso cuando uses tu propio arte, te sugerimos que recurras al arte de Steam cuando no reconozcas un control o llames a nuestra función de ayuda para encontrar la opción más cercana que existió cuando se lanzó tu juego, de modo que cuando se agreguen dispositivos futuros, tengan glifos razonables.

Mostrando los glifos a prueba de futuro de Steam

Tendrás que utilizar las siguientes funciones:

Ejemplo de código:

// Inicializa la interfaz antes de usar funciones individuales - ¡solo se tiene que llamar una vez! SteamInput()->Init() // ... // SteamAPI_RunCallbacks() llamará a las funciones RunFrame para cualquier interfaz inicializada y la mayoría // de los juegos ya la llamarán periódicamente. Si no lo haces, deberás actualizar manualmente // la interfaz de Steam Input SteamInput()->RunFrame(); // ... // Reemplazar con el espacio XInput por el que consultas. Este número está entre 0 y 3 // Si estás usando RawInput para la detección de dispositivos antes de decidir qué API usar // consulta la sección «Uso de RawInput para la detección de dispositivos». int nXinputSlot = 0; // Reemplaza con el botón por el que consultas EXboxOrigin eXboxButtonToGetGlyphFor = k_EXboxOrigin_A; EInputActionOrigin buttonOrigin = k_EInputActionOrigin_XBoxOne_A; // Si el mando está configurado a través de Steam Input - traduce el botón InputHandle_t controller1Handle = SteamInput()->GetControllerForGamepadIndex( nXinputSlot ); if ( controller1Handle > 0 ) { // Los handles válidos son distintos de cero; este es el mando configurado a través de Steam Input // Nota: los mandos que están usando la API de Steam Input no devolverán un handle a través de GetControllerForGamepadIndex() buttonOrigin = SteamInput()->GetActionOriginFromXboxOrigin( controller1Handle, k_EXboxOrigin_A ); } else { // Los handles válidos son distintos de cero; este es un mando normal de Xbox // Continúa usando el botón original } // Los valores de EInputActionOrigin seguirán aumentando en tanto Steam agregue compatibilidad, pero eso está bien debido a que // en este ejemplo tomaremos las imágenes del dispositivo de Steam que también proporciona una nueva imagen de glifo // Obtén la imagen del cliente de Steam const char *localGlyphPath = SteamInput()->GetGlyphForActionOrigin( buttonOrigin ); printf( "path = %s\n", localGlyphPath ); // "path = C:\Program Files (x86)\Steam\tenfoot\resource\images\library\controller\api\ps4_button_x.png" // Haz el cambio con una función del juego que convierta una ruta de archivo en una textura de juego utilizable glyphTextureID = loadButtonGlyphTextureFromLocalPath( localGlyphPath );

Mostrar tus propios diseños: Paleta de glifos

Si estás usando varias paletas de glifos y eliges cuál usar basándote en tu tipo de mando, puedes pedirle a Steam esta información usando la posición de Xinput:

Ejemplo de código:
// Inicializa la interfaz antes de usar funciones individuales - ¡solo se tiene que llamar una vez! SteamInput()->Init() // ... // SteamAPI_RunCallbacks() llamará a las funciones RunFrame para cualquier interfaz inicializada y la mayoría // de los juegos ya la llamarán periódicamente. Si no lo haces, deberás actualizar manualmente // la interfaz de Steam Input SteamInput()->RunFrame(); // ... // Reemplazar con el espacio XInput por el que consultas. Este número está entre 0 y 3 // Si estás usando RawInput para la detección de dispositivos antes de decidir qué API usar // consulta la sección «Uso de RawInput para la detección de dispositivos». int nXinputSlotIndex = 0; InputHandle_t inputHandle = SteamInput()->GetControllerForGamepadIndex( nXinputSlotIndex ); if ( inputHandle == 0 ) { // Los handles válidos de entrada son distintos de cero; este es un mando normal de Xbox. } else { // Steam siempre devolverá un valor de enumeración válido para tu versión del SDK. ESteamInputType inputType = SteamInput()->GetInputTypeForHandle( inputHandle ); switch( inputType ) { case k_ESteamInputType_Unknown: printf( "¡Dispositivo desconocido!\n" ); break; case k_ESteamInputType_SteamController: printf( "¡Steam Controller!\n" ); break; case k_ESteamInputType_XBox360Controller: printf( "¡Mando de Xbox 360!\n" ); break; case k_ESteamInputType_XBoxOneController: printf( "¡Mando de Xbox One!\n" ); break; case k_ESteamInputType_GenericXInput: printf( "¡XInput genérico!\n" ); break; case k_ESteamInputType_PS4Controller: printf( "¡Mando de PS4!\n" ); break; } }

Mostrar tu propio arte: Arte por botón

Si estás usando una tabla de búsqueda sencilla indexada por orígenes de acción, puedes usar estas funciones para implementar glifos de dispositivos específicos y traducir cualquier mando no reconocido a sus equivalentes más cercanos.

Ejemplo de código:
// Inicializa la interfaz antes de usar funciones individuales - ¡solo se tiene que llamar una vez! SteamInput()->Init() // ... // SteamAPI_RunCallbacks() llamará a las funciones RunFrame para cualquier interfaz inicializada y la mayoría // de los juegos ya la llamarán periódicamente. Si no lo haces, deberás actualizar manualmente // la interfaz de Steam Input SteamInput()->RunFrame(); // ... // Reemplazar con el espacio XInput por el que consultas. Este número está entre 0 y 3 // Si estás usando RawInput para la detección de dispositivos antes de decidir qué API usar // consulta la sección «Uso de RawInput para la detección de dispositivos». int nXinputSlot = 0; // Reemplaza con el botón por el que consultas EXboxOrigin eXboxButtonToGetGlyphFor = k_EXboxOrigin_A; EInputActionOrigin buttonOrigin = k_EInputActionOrigin_XBoxOne_A; // Si el mando está configurado a través de Steam Input - traduce el botón InputHandle_t controller1Handle = SteamInput()->GetControllerForGamepadIndex( nXinputSlot ); if ( controller1Handle > 0 ) { // Los handles válidos son distintos de cero; este es el mando configurado a través de Steam Input // Nota: los mandos que están usando la API de Steam Input no devolverán un handle a través de GetControllerForGamepadIndex() buttonOrigin = SteamInput()->GetActionOriginFromXboxOrigin( controller1Handle, k_EXboxOrigin_A ); //i.e, k_EInputActionOrigin_PS4_X } else { // Los handles válidos son distintos de cero; este es un mando normal de Xbox // Continúa usando el botón original } // Steam continuará agregando orígenes de acción y los futuros mandos superarán el rango actual // Si deseas probarlo puedes simular que los mandos de Switch o PS5 no existen y cambiarlo a: // if ( buttonOrigin >= k_EInputActionOrigin_XBox360_Reserved10 ) if ( buttonOrigin >= k_EInputActionOrigin_Count ) { // ¡No lanzamos nada de arte en nuestro juego para este origen! Supongo que Steam ha agregado soporte para // un nuevo mando. Obtengamos el valor más cercano permitido por el SDK utilizado buttonOrigin = SteamInput()->TranslateActionOrigin( k_ESteamInputType_Unknown, buttonOrigin ); } // Reemplázalo con una función del juego que devuelva tu arte por un botón int glyphTextureID = getHardCodedButtonGlyphTexture( buttonOrigin );

Uso de RawInput para la detección de dispositivos

Algunos juegos utilizarán RawInput para admitir más de 4 mandos/controles o para determinar si hay mandos/controles de PlayStation conectados. Esta API también devolverá otros dispositivos que no son gamepads y carecen de un índice fiable para asignar directamente desde las funciones de Steam Input. En su lugar, puedes obtener esta información consultando la cadena RIDI_DEVICENAME desde GetRawInputDeviceInfo(), que se codifica con el VID/PID USB del dispositivo real, el índice del gamepad y el handle de Steam Input con el siguiente formato:
\\.\pipe\HID#VID_045E&PID_028E&IG_00#{VID del dispositivo real}&{PID del dispositivo real}&{handle de la API de Steam Input}#{índice del gamepad de Steam Input}}#{id. de proceso}
Por ejemplo, \\.\pipe\HID#VID_045E&PID_028E&IG_00#045E&0B00&00045EB00704DAF3#0#20160 para un mando de Xbox One con un VID/PID de 0x45e/0x0b00.

Código de ejemplo:
#define VALVE_DIRECTINPUT_GAMEPAD_VID 0x28DE #define VALVE_DIRECTINPUT_GAMEPAD_PID 0x11FF #define STEAM_INPUT_VID_INDEX 37 #define STEAM_INPUT_PID_INDEX 42 #define STEAM_INPUT_SIAPI_HANDLE_INDEX 47 #define STEAM_INPUT_GAMEPAD_INDEX 64 #define MAX_PATH 260 //... void YourFunction() { PRAWINPUTDEVICELIST devices = NULL; UINT i, j, device_count = 0; if ((GetRawInputDeviceList(NULL, &device_count, sizeof(RAWINPUTDEVICELIST)) == -1) || (!device_count)) { return; /* Pues bueno. */ } devices = (PRAWINPUTDEVICELIST)malloc(sizeof(RAWINPUTDEVICELIST) * device_count); if (devices == NULL) { return; } if (GetRawInputDeviceList(devices, &device_count, sizeof(RAWINPUTDEVICELIST)) == -1) { free(devices); return; /* Pues bueno. */ } for (i = 0; i < device_count; i++) { RID_DEVICE_INFO rdi; char devName[MAX_PATH]; UINT rdiSize = sizeof(rdi); UINT nameSize = MAX_PATH; rdi.cbSize = sizeof(rdi); if ( devices[i].dwType == RIM_TYPEHID && GetRawInputDeviceInfoA( devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize ) != (UINT)-1 && GetRawInputDeviceInfoA( devices[i].hDevice, RIDI_DEVICENAME, devName, &nameSize ) != (UINT)-1 ) { if ( rdi.hid.dwVendorId == VALVE_DIRECTINPUT_GAMEPAD_VID && rdi.hid.dwProductId == VALVE_DIRECTINPUT_GAMEPAD_PID ) { uint32 ulVID = strtoul( &devName[STEAM_INPUT_VID_INDEX], NULL, 16 ); uint32 ulPID = strtoul( &devName[STEAM_INPUT_PID_INDEX], NULL, 16 ); uint64 ulDeviceHandle = strtoull( &devName[STEAM_INPUT_SIAPI_HANDLE_INDEX], NULL, 16 ); uint32 unGamepadIndex = strtoul( &devName[STEAM_INPUT_GAMEPAD_INDEX], NULL, 16 ); [/i][/i][/i] Log( "Raw input device: VID = 0x%x, PID = 0x%x, handle = 0x%llx, index = 0x%x, %s\n", ulVID, ulPID, ulDeviceHandle, unGamepadIndex, devName ); // Ahora puedes usar el VID o el PID para identificar el dispositivo directamente o utilizar ulDeviceHandle con GetInputTypeForHandle. ESteamInputType inputType = SteamInput()->GetInputTypeForHandle( ulDeviceHandle ); switch( inputType ) { case k_ESteamInputType_Unknown: printf( "¡Dispositivo desconocido!\n" ); break; case k_ESteamInputType_SteamController: printf( "¡Steam Controller!\n" ); break; case k_ESteamInputType_XBox360Controller: printf( "¡Mando/Control Xbox 360!\n" ); break; case k_ESteamInputType_XBoxOneController: printf( "¡Mando/Control Xbox One!\n" ); break; case k_ESteamInputType_GenericGamepad: printf( "Gamepad genérico (DirectInput)!\n" ); break; case k_ESteamInputType_PS3Controller: case k_ESteamInputType_PS4Controller: case k_ESteamInputType_PS5Controller: printf( "¡Mando/Control PlayStation!\n" ); break; } } else { // El dispositivo no es un mando/control que vaya a través de Steam Input. Utiliza la lógica de id. de tu mando/control normal. continue; } } } free(devices); }

Configuración de los ajustes de Steamworks

El primer paso es establecer qué mandos/controles utilizará Steam Input en la sección "Vincular mandos/controles a Steam Input" de los ajustes de Steamworks en Aplicación->Steam Input. Para un juego estándar compatible con mando/control Xbox, recomendamos marcar todas las casillas que no sean la de Xbox:

steamworks_steam_input_optin_settings.png

Si tu juego es compatible con palanca de vuelo o volante de carreras, deberás desmarcar la casilla de mando/control "Genérico/DirectInput", pues Steam no soporta la reasignación de estos dispositivos.

Elección de una configuración

Puedes optar por crear tu propia configuración o elegir una de nuestras plantillas prediseñadas. No es necesario crear tu propia configuración a menos que planees retocar las configuraciones fuera de nuestros valores predeterminados o etiquetar asignaciones individuales con lo que hacen en el juego. Las plantillas de Steam Input integradas están localizadas a todos los idiomas de Steam, así que asegúrate de localizar también tu configuración para evitar que se muestre el inglés a los usuarios de otros idiomas.

Escoger una plantilla

Los mandos/controles de consola no diferencian entre las distintas variantes de plantilla de gamepad, pero estas configuraciones son importantes para el Steam Controller. He aquí una explicación de las plantillas y de los tipos de juegos a los que están destinadas:
  • Gamepad: Emula un mando/control Xbox con el trackpad derecho convertido en joystick emulado sin procesar. Perfecto para juegos de doble stick, plataformas o deportes.
  • Gamepad con cámara/objetivo de alta precisión: Esta configuración es la ideal para un juego de disparos en primera persona o cualquier título que utilice controles de cámara. Antes de seleccionar esta opción, asegúrate de que tu juego es compatible con el uso simultáneo de gamepad y ratón, incluyendo las pantallas auxiliares, como las de inventario o mapa.
  • Gamepad con controles de cámara: Esta configuración toma una entrada estilo ratón del trackpad y la traduce en movimientos de un joystick emulado. Si tu juego puede configurarse para tener una sensibilidad de joystick muy alta, sin zona muerta y una curva de aceleración lineal, esta configuración puede funcionar bien. Si no, considera la posibilidad de utilizar una configuración de ratón/teclado en su lugar.

steam_input_gamepad_templates.png

Los pasos para publicar una plantilla para tu juego en Steamworks puedes encontrarlos aquí.

Creación de una configuración personalizada

Las configuraciones se pueden convertir automáticamente entre la mayoría de los tipos de mandos/controles, pero como mínimo deberías crear una configuración para el Steam Controller y el mando/control Xbox.
Nota: Si piensas aprovechar características específicas del dispositivo, como los controles de movimiento, querrás tener también una para los mandos/controles de PlayStation 4 o Nintendo Switch.

Entrada de texto


La introducción de texto en pantalla técnicamente no forma parte de ISteamInput (Steam Input), sino que se encuentra en ISteamUtils. Actualmente esto solo se implementa cuando el usuario inicia el juego a través del modo Big Picture.

Algunas referencias rápidas:

Localización de la configuración
Crear una configuración localizada es un poco más complicado cuando no se utiliza la API de Steam Input, pero aun así se puede conseguir.
  • Para empezar, asegúrate de que no has modificado tu configuración desde la última vez que se exportó. Abre el configurador y haz un cambio (no pasa nada por hacerlo y luego revertirlo).
  • Ve a tu registro Steam\Logs\controller_ui.txt y busca una cadena de texto como esta:
    Steam Controller Saving Controller Configuration Autosave for [CONTROLLER SERIAL NUMBER]- AppID: [YOUR APPID]. Loaded Config for Local Selection Path for App ID [YOUR APPID], Controller 0: F:\ProgramFiles\Steam\client\userdata\[STEAMID]\config\controller_configs\apps\[YOUR APPID]\[CONTROLLER SERIAL]\guest\controller_configuration.vdf
  • Inserta los tokens de localización para las asignaciones que desees localizar.
    • Título/Descripción:
      "controller_mappings" { "version" "3" "revision" "5" "title" "#title" "description" "#description" ...
    • Asignaciones de botones:
      "button_a" { "activators" { "Full_Press" { "bindings" { "binding" "xinput_button A, #abutton" } } } } ...
  • Y luego crea los valores correspondientes para cada idioma en el bloque de localización:
    "localization" { "spanish" { "title" "Este es un título localizado" "description" "Esta es una descripción localizada." La asignación de la botonera también tiene nombres traducibles." "abutton" "Tu nombre aquí 1" "bbutton" "Tu nombre aquí 2" "xbutton" "Tu nombre aquí 3" "ybutton" "Tu nombre aquí 4" } ...
  • Ahora puedes exportar la configuración y ponerla en el sitio para asociados siguiendo estas instrucciones. Convendría que marcases la casilla "Usar bloque de acciones" para una de tus configuraciones.

La configuración de ejemplo se puede ver en el cliente de Steam introduciendo en el navegador el URL steam://controllerconfig/681280/1744110334, o bien puedes encontrar el archivo VDF aquí