Documentation Steamworks
Émulation de manette Steam Input : bonnes pratiques
Steam Input est un service permettant aux personnes qui utilisent Steam de jouer à tout jeu qui prend en charge des contrôleurs avec le périphérique de leur choix. Steam Input traduit les commandes d'entrée en quelque chose que le jeu comprend, au moyen de l'émulation de manette, de l'émulation de souris et clavier ou encore de l'API Steam Input. Dans cet article, nous montrerons comment utiliser l'émulation de manette de Steam Input pour que votre jeu prenne en charge encore plus de contrôleurs.

Qu'est-ce que l'émulation de manette ?
  • Sur Windows, l'overlay Steam s'accrochera à des API d'entrées manette traditionnelles telles que Xinput, DirectInput, RawInput ou Windows.Gaming.Input, et injectera une émulation de manette Xbox. Sur macOS et Linux, l'entrée émulant une manette est fournie par un pilote.
  • Dans votre jeu, la manette sera représentée par une manette Xbox. Les manettes disposant d'entrées supplémentaires verront donc certaines d'entre elles dupliquées : par exemple, le clic du trackpad PlayStation et le bouton OPTIONS seront tous deux associés au bouton Start de Xinput.
  • Outre les entrées classiques du contrôleur, il est possible d'associer les entrées gyroscopiques des manettes Switch, PlayStation et du Steam Controller à l'émulation d'une souris et de fournir des contrôles de mouvement. Cela ne fonctionne que dans les jeux en solo, car il y a seulement une entrée souris. De plus, le jeu doit accepter des entrées simultanées souris et contrôleur. Si vous voulez profiter de ces fonctionnalités sans vous soucier de ces restrictions, vous pouvez utiliser l'API Steam Input.
  • Vous pouvez demander à Steam le type de contrôleur actuellement utilisé pour pouvoir afficher des représentations spécifiques au périphérique, mais cela sera limité aux types pris en charge par le SDK Steamworks actuel. Si vous souhaitez une prise en charge évolutive des représentations graphiques, intégrez l'API Steam Input.
  • Certaines personnes peuvent jouer en utilisant Steam Input, même pour des contrôleurs que vous prenez déjà en charge. En effet, Steam Remote Play l'utilise pour fournir les entrées au cours de la diffusion et une bonne partie des joueurs et joueuses ont activé Steam Input pour reconfigurer leurs contrôleurs sur l'ensemble de leur bibliothèque Steam. Sur l'ensemble des sessions de jeu avec des contrôleurs sur Steam en 2020, à peu près un quart utilisaient Steam Input, et c'était en particulier le cas pour près de la moitié des sessions avec des manettes PlayStation.

Sur Steam, plus de 2000 jeux ont recours à l'émulation de manette pour au moins un type de contrôleur. Citons notamment Monster Hunter: World, Ace Combat 7, Dragon Quest XI, Into the Breach et Middle Earth: Shadow of War parmi les plus marquants, bien que tous ne suivent pas complètement la liste des bonnes pratiques. Nous utiliserons ici l'exemple de Into the Breach qui suit bien chacune de ces recommandations.

Afficher des images spécifiques au périphérique

Nous prenons en charge plusieurs façons d'obtenir des glyphes spécifiques au périphérique avec l'émulation de manette Steam Input. L'une concerne les jeux qui peuvent charger des images pendant qu'ils s'exécutent, et est indépendante de futures mises à jour ; ainsi, lorsque Steam mettra à jour Steam Input, le jeu fonctionnera sans mise à jour. Deux autres concernent les jeux qui nécessitent l'intégration d'images dans leurs ressources ou l'utilisation des mêmes graphismes stylisés que ceux de leurs portages sur consoles. Into the Breach utilise ses propres graphismes pour les manettes Xbox et le Steam Controller :

intothebreach_xbox.PNG

pour les manettes PlayStation :

intothebreach_ps4.PNG

et les manettes Nintendo Switch :

intothebreach_switchpro.PNG
Remarque : même si vous utilisez vos propres images, nous vous suggérons d'opter pour les images de Steam quand vous ne reconnaissez pas un contrôleur, ou d'appeler notre fonction d'aide pour trouver l'option la plus proche qui existait au moment de la sortie de votre jeu. Ainsi, quand des périphériques feront leur apparition, ils auront des représentations raisonnables.

Afficher les glyphes évolutifs de Steam

Vous devrez utiliser les fonctions suivantes :

Exemple de code :

// Initialiser l'interface avant d'utiliser les fonctions individuelles – un seul appel suffit ! SteamInput()->Init() // ... // SteamAPI_RunCallbacks() appellera les fonctions RunFrame pour toute interface initialisée. La plupart des // jeux appellent d'ores et déjà cette fonction périodiquement. Dans le cas contraire, vous devrez mettre à jour // l'interface Steam Input manuellement SteamInput()->RunFrame(); // ... // Remplacer par l'emplacement Xinput demandé. Il s'agit d'un nombre entre 0 et 3. // Si vous utilisez RawInput pour détecter les périphériques avant de décider quelle API utiliser, // veuillez consulter la section « Utilisation de RawInput pour détecter les périphériques ». int nXinputSlot = 0; // Remplacer par le bouton que vous demandez. EXboxOrigin eXboxButtonToGetGlyphFor = k_EXboxOrigin_A; EInputActionOrigin buttonOrigin = k_EInputActionOrigin_XboxOne_A; // Si le contrôleur est configuré via Steam Input, traduire le bouton. InputHandle_t controller1Handle = SteamInput()->GetControllerForGamepadIndex( nXinputSlot ); if ( controller1Handle > 0 ) { // Les handles valides sont non nuls. Il s'agit d'un contrôleur configuré via Steam Input. // Remarque : les contrôleurs qui utilisent l'API Steam Input ne renverront pas de handle via GetControllerForGamepadIndex() buttonOrigin = SteamInput()->GetActionOriginFromXboxOrigin( controller1Handle, k_EXboxOrigin_A ); } else { // Les handles valides sont non nuls. Il s'agit d'une manette Xbox classique. // Continuer à utiliser le bouton original. } // De nouvelles valeurs vont continuer à être ajoutées à EInputActionOrigin au fil de l'ajout // de nouvelles compatibilités par Steam. Ce n'est pas un problème car dans cet exemple, nous récupèrerons // les images du périphérique par Steam, qui peut aussi fournir un nouveau glyphe. // Récupérer l'image du client 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" // Remplacer ceci par une fonction du jeu qui transforme un chemin d'accès en une texture du jeu utilisable. glyphTextureID = loadButtonGlyphTextureFromLocalPath( localGlyphPath );

Afficher vos propres images : palettes de glyphes

Si vous utilisez plusieurs palettes de glyphes et faites votre choix en fonction du type de contrôleur, vous pouvez demander à Steam cette information par un emplacement Xinput en utilisant :

Exemple de code :
// Initialiser l'interface avant d'utiliser les fonctions individuelles – un seul appel suffit ! SteamInput()->Init() // ... // SteamAPI_RunCallbacks() appellera les fonctions RunFrame pour toute interface initialisée. La plupart des // jeux appellent d'ores et déjà cette fonction périodiquement. Dans le cas contraire, vous devrez mettre à jour // l'interface Steam Input manuellement SteamInput()->RunFrame(); // ... // Remplacer par l'emplacement Xinput demandé. Il s'agit d'un nombre entre 0 et 3. // Si vous utilisez RawInput pour détecter les périphériques avant de décider quelle API utiliser, // veuillez consulter la section « Utilisation de RawInput pour détecter les périphériques ». int nXinputSlotIndex = 0; InputHandle_t inputHandle = SteamInput()->GetControllerForGamepadIndex( nXinputSlotIndex ); if ( inputHandle == 0 ) { // Les handles valides sont non nuls. Il s'agit d'une manette Xbox classique. } else { // Steam renverra toujours une valeur énumérée valide pour la version du SDK que vous aviez. ESteamInputType inputType = SteamInput()->GetInputTypeForHandle( inputHandle ); switch( inputType ) { case k_ESteamInputType_Unknown: printf( "unknown\n!" ); break; case k_ESteamInputType_SteamController: printf( "Steam controller\n!" ); break; case k_ESteamInputType_Xbox360Controller: printf( "Xbox 360 controller\n!" ); break; case k_ESteamInputType_XboxOneController: printf( "Xbox One controller\n!" ); break; case k_ESteamInputType_GenericXInput: printf( "Generic XInput\n!" ); break; case k_ESteamInputType_PS4Controller: printf( "PS4 controller\n!" ); break; } }

Afficher vos propres images : bouton par bouton

Si vous utilisez une seule table de recherche indexée par les origines d'action, vous pouvez utiliser ces fonctions pour implémenter des glyphes spécifiques aux périphériques et remplacer les contrôleurs non reconnus par leurs équivalents les plus proches.

Exemple de code :
// Initialiser l'interface avant d'utiliser les fonctions individuelles – un seul appel suffit ! SteamInput()->Init() // ... // SteamAPI_RunCallbacks() appellera les fonctions RunFrame pour toute interface initialisée. La plupart des // jeux appellent d'ores et déjà cette fonction périodiquement. Dans le cas contraire, vous devrez mettre à jour // l'interface Steam Input manuellement SteamInput()->RunFrame(); // ... // Remplacer par l'emplacement Xinput demandé. Il s'agit d'un nombre entre 0 et 3. // Si vous utilisez RawInput pour détecter les périphériques avant de décider quelle API utiliser, // veuillez consulter la section « Utilisation de RawInput pour détecter les périphériques ». int nXinputSlot = 0; // Remplacer par le bouton que vous demandez. EXboxOrigin eXboxButtonToGetGlyphFor = k_EXboxOrigin_A; EInputActionOrigin buttonOrigin = k_EInputActionOrigin_XboxOne_A; // Si le contrôleur est configuré via Steam Input, traduire le bouton. InputHandle_t controller1Handle = SteamInput()->GetControllerForGamepadIndex( nXinputSlot ); if ( controller1Handle > 0 ) { // Les handles valides sont non nuls. Il s'agit d'un contrôleur configuré via Steam Input // Remarque : les contrôleurs qui utilisent l'API Steam Input ne renverront pas de handle via GetControllerForGamepadIndex() buttonOrigin = SteamInput()->GetActionOriginFromXboxOrigin( controller1Handle, k_EXboxOrigin_A ); //par exemple, k_EInputActionOrigin_PS4_X } else { // Les handles valides sont non nuls. Il s'agit d'une manette Xbox normale. // Continuer à utiliser le bouton original. } // Steam continuera à ajouter des origines d'actions et les futurs contrôleurs sortiront de l'intervalle. // Pour tester, vous pouvez prétendre que les manettes Switch/PS5 n'existent pas et changer ceci en : // if ( buttonOrigin >= k_EInputActionOrigin_Xbox360_Reserved10 ) if ( buttonOrigin >= k_EInputActionOrigin_Count ) { // Nous n'avons pas livré de graphismes dans notre jeu pour cette origine ! Je crois que Steam a ajouté la prise en charge // d'un nouveau contrôleur. Récupérons la valeur la plus proche compatible avec le SDK sur lequel notre équipe s'est appuyée. buttonOrigin = SteamInput()->TranslateActionOrigin( k_ESteamInputType_Unknown, buttonOrigin ); } // Remplacer ceci par une fonction du jeu qui renvoie votre image pour un bouton. int glyphTextureID = getHardCodedButtonGlyphTexture( buttonOrigin );

Utilisation de RawInput pour détecter les périphériques

Certains jeux utiliseront RawInput pour prendre en charge plus de 4 contrôleurs ou pour déterminer si des manettes PlayStation sont connectées. Outre les manettes, cette API renverra également d'autres appareils et ne dispose pas d'indice fiable pour établir une correspondance directe à partir des fonctions Steam Input. Vous pouvez obtenir cette information en interrogeant la chaine RIDI_DEVICENAME de GetRawInputDeviceInfo() qui est encodée avec le VID/PID USB du dispositif réel, l'index de manette et le handle Steam Input au format suivant :
\\.\pipe\HID#VID_045E&PID_028E&IG_00#{VID du périphérique réel}&{PID du périphérique réel}&{handle de l'API Steam Input}#{index de manette Steam Input}}#{ID de processus}
Par exemple, \\.\pipe\HID#VID_045E&PID_028E&IG_00#045E&0B00&00045EB00704DAF3#0#20160 pour une manette Xbox One avec un VID/PID de 0x45e/0x0b00.

Exemple de code :
#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; /* oh bon. */ } devices = (PRAWINPUTDEVICELIST)malloc(sizeof(RAWINPUTDEVICELIST) * device_count); if (devices == NULL) { return; } if (GetRawInputDeviceList(devices, &device_count, sizeof(RAWINPUTDEVICELIST)) == -1) { free(devices); return; /* oh bon. */ } 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 ); // Vous pouvez maintenant utiliser le VID/PID pour identifier le périphérique ou utiliser ulDeviceHandle avec GetInputTypeForHandle ESteamInputType inputType = SteamInput()->GetInputTypeForHandle( ulDeviceHandle ); switch( inputType ) { case k_ESteamInputType_Unknown: printf( "unknown!\n" ); break; case k_ESteamInputType_SteamController: printf( "Steam controller!\n" ); break; case k_ESteamInputType_Xbox360Controller: printf( "Xbox 360 controller!\n" ); break; case k_ESteamInputType_XboxOneController: printf( "Xbox One controller!\n" ); break; case k_ESteamInputType_GenericGamepad: printf( "Generic Gamepad(DirectInput)!\n" ); break; case k_ESteamInputType_PS3Controller: case k_ESteamInputType_PS4Controller: case k_ESteamInputType_PS5Controller: printf( "PlayStation controller!\n" ); break; } } else { // Le périphérique n'utilise pas Steam Input, utilisez votre logique d'identification normale continue; } } } free(devices); }

Paramétrer Steamworks

La première étape consiste à définir quels contrôleurs utiliseront Steam Input dans la section « Enregistrer des contrôleurs dans Steam Input » des paramètres Steamworks sous Application->Steam Input. Pour un jeu standard qui prend en charge les manettes Xbox, nous recommandons de cocher toutes les cases autres que Xbox :

steamworks_steam_input_optin_settings.png

Si votre jeu est compatible avec les volants de course ou les joysticks, nous vous conseillons de décocher la case « Générique (DirectInput) », car Steam ne prend pas en charge le remappage de ces périphériques.

Choix d'une configuration

Vous pouvez choisir de créer votre propre configuration ou sélectionner l'un de nos modèles prédéfinis. Il n'est pas nécessaire de créer votre propre configuration, sauf si vous prévoyez de modifier légèrement les configurations par défaut ou d'étiqueter les associations individuelles avec ce qu'elles font en jeu. Les modèles prédéfinis de Steam Input sont traduits dans toutes les langues prises en charge par Steam. Assurez-vous donc de traduire également votre configuration pour éviter d'afficher de l'anglais aux personnes qui ne parlent pas cette langue.

Choix d'un modèle

Il n'y a pas de différence entre les diverses variantes de modèles pour les manettes de consoles, mais ces paramètres sont importants pour le Steam Controller. Nous décrivons ci-après les différents modèles et les types de jeux auxquels ils sont destinés.
  • Contrôleur : ce modèle émule une manette Xbox. Dans ce modèle, le trackpad droit émule un joystick. Ce modèle est parfait pour les jeux à deux joysticks, les jeux de plateformes ou les jeux de sport.
  • Contrôleur avec caméra et visée de haute précision : ce modèle est la configuration idéale pour un jeu de tir à la première personne ou tout autre jeu utilisant les commandes de la caméra. Avant de choisir ce modèle, assurez-vous que votre jeu prend en charge le contrôle simultané de la souris et de la manette, notamment sur des écrans supplémentaires tels que l'inventaire ou les cartes.
  • Contrôleur avec commandes de caméra : cette configuration prend les entrées de type souris du trackpad et les traduit en mouvements d'un joystick émulé. Si votre jeu peut être configuré de manière à comporter une sensibilité de joystick très élevée, une zone morte nulle et une courbe d'accélération linéaire, cette configuration peut fonctionner à merveille. Sinon, utilisez une configuration souris/clavier.

steam_input_gamepad_templates.png

Pour connaitre les étapes à suivre afin d'adapter le modèle à votre jeu, consultez la documentation Steamworks

Créer une configuration personnalisée

Les configurations peuvent être automatiquement converties entre la plupart des types de contrôleurs, mais il est conseillé de créer au minimum une configuration pour le Steam Controller ainsi que pour les manettes Xbox.
Remarque : si vous prévoyez d'exploiter des fonctionnalités spécifiques au périphérique comme les commandes de mouvement, il est préférable de disposer également de configurations pour manettes PlayStation 4 et Nintendo Switch.

Entrées de type texte


Les entrées de type texte sur l'écran ne font techniquement pas partie de l'API ISteamController (Steam Input), mais se trouvent plutôt dans ISteamUtils. Ceci n'est actuellement implémenté que lorsque le joueur ou la joueuse lance le jeu en mode Big Picture.

Quelques références rapides :

Traduire votre configuration
Il est un peu plus compliqué de créer une configuration localisée quand on n'utilise pas l'API Steam Input, mais c'est toujours possible.
  • Pour commencer, assurez-vous que vous n'avez pas modifié votre configuration depuis la dernière fois que vous l'avez exportée. Ouvrez le configurateur et faites une modification (vous pouvez faire un changement et l'annuler).
  • Consultez le fichier journal Steam\Logs\controller_ui.txt et cherchez une chaine semblable à ceci :
    Steam Controller Saving Controller Configuration Autosave for [NUMÉRO DE SÉRIE DE LA MANETTE]- AppID: [VOTRE APPID]. Loaded Config for Local Selection Path for App ID [VOTRE APPID], Controller 0: F:\ProgramFiles\Steam\client\userdata\[STEAMID]\config\controller_configs\apps\[VOTRE APPID]\[SÉRIE DE LA MANETTE]\guest\controller_configuration.vdf
  • Insérez les jetons de localisation pour les associations que vous voulez localiser.
    • Titre/Description :
      "controller_mappings" { "version" "3" "revision" "5" "title" "#title" "description" "#description" ...
    • Configuration des boutons :
      "button_a" { "activators" { "Full_Press" { "bindings" { "binding" "xinput_button A, #abutton" } } } } ...
  • Créez ensuite les valeurs correspondantes pour chaque langue dans le bloc « localization » :
    "localization" { "english" { "title" "This is a localized title" "description" "This is a localized description. } "french" { "title" "Traduction du titre" "description" "Traduction de la description. Les configurations des boutons de façade ont aussi des noms traduits." "abutton" "Votre nom 1" "bbutton" "Votre nom 2" "xbutton" "Votre nom 3" "ybutton" "Votre nom 4" } ...
  • Vous pouvez maintenant exporter la configuration et la définir sur le site des partenaires à l'aide de ces instructions. Nous vous conseillons de cocher « Utiliser un bloc d'actions » pour l'une de vos configurations.

Vous pouvez prévisualiser la configuration exemple dans le client Steam en saisissant cette URL dans votre navigateur : steam://controllerconfig/681280/1744110334, ou consulter le fichier VDF ici.