Документація Steamworks
Емуляція ґеймпадів введенням Steam: корисні поради
Введення Steam — це служба Steam, що дозволяє користувачам грати в будь-яку сумісну з контролерами гру на своєму улюбленому пристрої. Введення Steam перекладає користувацьке введення на щось зрозуміле для гри за допомогою емуляції контролера, миші та клавіатури, або API введення Steam. Тут ми зосередимося на тому, як найкраще використовувати емуляцію ґеймпада введення Steam для розширення підтримки наявних у вашій грі контролерів.

Що таке емуляція ґеймпада?
  • У Windows оверлей Steam підключатиме традиційні API, як-от Xinput, DirectInput, RawInput і Windows.Gaming.Input, щоби вставити емульований контролер Xbox. На системах MacOS і Linux введення емульованого контролера надається драйвером.
  • Контролер з’явиться у вашій грі як контролер Xbox, а це означає, що якщо на контролері є додаткові елементи введення, то вони можуть продублюватися. Наприклад, натискання сенсорної панелі й кнопки параметрів контролера PlayStation призначається для кнопки «Старт» у XInput.
  • Окрім звичайного введення ґеймпада можна призначити введення гіроскопа контролерів Switch, PlayStation і Steam для емуляції миші й надання керування рухом. Це працює лише для локальних самітних ігор, оскільки є лише одне введення миші. Також це залежить від того, що гра приймає одночасно введення миші й ґеймпада. Якщо вам цікаві ці функції без обмежень, то розгляньте можливість додавання API введення Steam.
  • Ви можете отримати дані про тип контролера, котрий наразі підключено до Steam, щоби показувати значки кнопок для певних пристроїв, але ви обмежені типами, що підтримуються вашою поточною версією SDK Steamworks. Якщо ви зацікавлені в підтримці майбутніх значків без необхідності оновлення гри, то інтегруйте API введення Steam.
  • Можуть траплятися випадки, коли користувачі грають на контролерах із введенням Steam, хоча ви маєте власну підтримку цих контролерів. Це відбувається через те, що Steam Remote Play використовує його для введення під час транслювання, а також велика кількість користувачів ввімкнули введення Steam для всіх ігор у своїй бібліотеці Steam. Загалом, близько чверті всіх сеансів із контролерами в Steam у 2020 році використовували введення Steam, зокрема майже половина всіх ігрових сеансів із контролерами PlayStation.

Емуляцію ґеймпаду принаймні для одного типу контролерів використовують понад 2 000 ігор у Steam, включно з такими видатними як Monster Hunter: World, Ace Combat 7, Dragon Quest XI, Into the Breach та Middle Earth: Shadow of War, хоча не всі з них дотримуються всіх найкращих практик. Ми писатимемо про Into the Breach, бо її розробники дотримуються всіх рекомендованих найкращих практик.

Піктограми відповідають пристроям

Є кілька способів отримати значки певного пристрою під час емуляції ґеймпадів: один використовується, якщо гра може завантажувати зображення під час роботи (можна не турбуватися про майбутні зміни, бо після оновлення системи введення Steam не доведеться оновлювати гру), а інший — якщо необхідно вбудувати зображення у файли гри чи використовувати стилізовані піктограми з консольних версій. Into the Breach використовує власні зображення для контролерів Xbox та Steam:

intothebreach_xbox.PNG

PlayStation:

intothebreach_ps4.PNG

Контролери Nintendo Switch:

intothebreach_switchpro.PNG
Примітка: навіть якщо ви користуєтеся власними зображеннями, рекомендуємо використовувати наші зображення для незнайомих контролерів або викликати функцію, що визначить найближчу дію на момент виходу гри, щоби після додавання наступних пристроїв значки збігалися.

Показ значків Steam із захистом від змін

Вам знадобляться такі функції:

Приклад коду:

// Запустити інтерфейс, перш ніж використовувати індивідуальні функції — потрібно викликати лише раз! SteamInput()->Init() // … // SteamAPI_RunCallbacks() створить виклик функції RunFrame для всіх запущених інтерфейсів. Більшість // ігор уже періодично створюватимуть цей виклик. Якщо ви це не робите, то вам потрібно буде вручну оновити // інтерфейс введення Steam SteamInput()->RunFrame(); // … // Замінити ланкою XInput, яку ви запитуєте. Це число від 0 до 3 // Якщо ви використовуєте RawInput для виявлення пристрою перед вибором API, // перегляньте розділ про використання RawInput для виявлення пристроїв. int nXinputSlot = 0; // Замінити кнопкою, для якої ви запитуєте EXboxOrigin eXboxButtonToGetGlyphFor = k_EXboxOrigin_A; EInputActionOrigin buttonOrigin = k_EInputActionOrigin_XBoxOne_A; // Якщо контролер сконфігуровано через введення Steam, то перекласти кнопку InputHandle_t controller1Handle = SteamInput()->GetControllerForGamepadIndex( nXinputSlot ); if ( controller1Handle > 0 ) { // Дійсні дескриптори не є 0, це контролер сконфігурований через введення Steam // Примітка: контролери, що використовують API введення Steam не повертатимуть дескриптор через GetControllerForGamepadIndex() buttonOrigin = SteamInput()->GetActionOriginFromXboxOrigin( controller1Handle, k_EXboxOrigin_A ); } else { // Дійсні дескриптори не є 0, це звичайний контролер Xbox // Продовжує використовувати початкову кнопку } // Значення EInputActionOrigin продовжуватимуть зростати з додаванням підтримки Steam, але це нормально, бо // в цьому прикладі ми отримаємо зображення приладів зі Steam, що також надасть зображення значка // Отримує зображення з клієнта 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" // Замініть це функцією гри, що повертає шлях до файлу з потрібною ігровою текстурою glyphTextureID = loadButtonGlyphTextureFromLocalPath( localGlyphPath );

Показ власних зображень — палітра значків

Якщо ви використовуєте кілька палітр значків і обираєте їх залежно від типу контролера, то можете запитати цю інформацію в Steam через ланку XInput за допомогою:

Приклад коду:
// Запустити інтерфейс, перш ніж використовувати індивідуальні функції — потрібно викликати лише раз! SteamInput()->Init() // … // SteamAPI_RunCallbacks() створить виклик функції RunFrame для всіх запущених інтерфейсів. Більшість // ігор уже періодично створюватимуть цей виклик. Якщо ви це не робите, то вам потрібно буде вручну оновити // інтерфейс введення Steam SteamInput()->RunFrame(); // … // Замінити ланкою XInput, яку ви запитуєте. Це число від 0 до 3 // Якщо ви використовуєте RawInput для виявлення пристрою перед вибором API, // перегляньте розділ про використання RawInput для виявлення пристроїв. int nXinputSlotIndex = 0; InputHandle_t inputHandle = SteamInput()->GetControllerForGamepadIndex( nXinputSlotIndex ); if ( inputHandle == 0 ) { // Дійсні дескриптори введення не є 0, це звичайний контролер Xbox. } else { // Steam завжди повертає значення перерахування, що є дійсним для вашої версії SDK. 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; } }

Показ власних зображень — ілюстрації для кнопок

Якщо ви використовуєте одну таблицю з пошуком значень, індексовану за діями, то ці функції дозволять додати окремі значки для кожного пристрою та перекласти невизначені контролери на їхні найближчі еквіваленти.

Приклад коду:
// Запустити інтерфейс, перш ніж використовувати індивідуальні функції — потрібно викликати лише раз! SteamInput()->Init() // … // SteamAPI_RunCallbacks() створить виклик функції RunFrame для всіх запущених інтерфейсів. Більшість // ігор уже періодично створюватимуть цей виклик. Якщо ви це не робите, то вам потрібно буде вручну оновити // інтерфейс введення Steam SteamInput()->RunFrame(); // … // Замінити ланкою XInput, яку ви запитуєте. Це число від 0 до 3 // Якщо ви використовуєте RawInput для виявлення пристрою перед вибором API // перегляньте розділ про використання RawInput для виявлення пристроїв. int nXinputSlot = 0; // Замінити кнопкою, для якої ви запитуєте EXboxOrigin eXboxButtonToGetGlyphFor = k_EXboxOrigin_A; EInputActionOrigin buttonOrigin = k_EInputActionOrigin_XBoxOne_A; // Якщо контролер сконфігуровано через введення Steam, то перекласти кнопку InputHandle_t controller1Handle = SteamInput()->GetControllerForGamepadIndex( nXinputSlot ); if ( controller1Handle > 0 ) { // Дійсні дескриптори не є 0, це контролер сконфігурований через введення Steam // Примітка: контролери, що використовують API введення Steam Input не повертатимуть дескриптор через GetControllerForGamepadIndex() buttonOrigin = SteamInput()->GetActionOriginFromXboxOrigin( controller1Handle, k_EXboxOrigin_A ); //i.e, k_EInputActionOrigin_PS4_X } else { // Дійсні дескриптори не є 0, це звичайний контролер Xbox // Продовжувати використовувати початкову кнопку } // Steam продовжує додавати джерела дій, і майбутні контролери перевищать поточний діапазон // Якщо хочете тестувати, то можете зімітувати неіснуючі контролери Switch/PS5 і змінити це на: // if ( buttonOrigin >= k_EInputActionOrigin_XBox360_Reserved10 ) if ( buttonOrigin >= k_EInputActionOrigin_Count ) { // У нашій грі немає ніяких зображень для цього джерела! Імовірно, Steam додав підтримку для // нового контролера. Отримаймо найближче значення, що підтримується використовуваним SDK buttonOrigin = SteamInput()->TranslateActionOrigin( k_ESteamInputType_Unknown, buttonOrigin ); } // Замінює функцією з гри, що повертає зображення для кнопки int glyphTextureID = getHardCodedButtonGlyphTexture( buttonOrigin );

Використання RawInput для виявлення пристроїв

Деякі ігри використовуватимуть RawInput для підтримки понад 4 контролерів чи для визначення підключення контролерів PlayStation. Цей API також повертатиме інші неґеймпадні пристрої й не має надійного індексу для безпосереднього мапування функцій уведення Steam. Натомість ви можете отримати цю інформацію з рядка RIDI_DEVICENAME від GetRawInputDeviceInfo(), який закодований за допомогою VID/PID USB дійсного пристрою, індексу ґеймпада та дескриптора введення Steam у наступному форматі:
\\.\pipe\HID#VID_045E&PID_028E&IG_00#{Real device VID}&{Real Device PID}&{Steam Input API handle}#{Steam Input Gamepad Index}}#{ProcessID}
Наприклад, \\.\pipe\HID#VID_045E&PID_028E&IG_00#045E&0B00&00045EB00704DAF3#0#20160 для контролера Xbox One із VID/PID від 0x45e/0x0b00.

Приклад коду:
#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 well. */ } devices = (PRAWINPUTDEVICELIST)malloc(sizeof(RAWINPUTDEVICELIST) * device_count); if (devices == NULL) { return; } if (GetRawInputDeviceList(devices, &device_count, sizeof(RAWINPUTDEVICELIST)) == -1) { free(devices); return; /* oh well. */ } 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 ); // Тепер ви можете використовувати VID/PID для прямого визначення пристрою або використати ulDeviceHandle з 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 { // Пристрій не є контролером введення Steam — використовуйте свою звичайну логіку для ID контролера continue; } } } free(devices); }

Конфігурація налаштувань Steamworks

Спершу слід визначити, які контролери використовуватимуть введення Steam, у розділі реєстрації: налаштування Steamworks → «Застосунок» → «Введення Steam». Для стандартної гри з підтримкою контролера Xbox ми рекомендуємо позначити всі контролери, крім Xbox:

steamworks_steam_input_optin_settings.png

Якщо ваша гра має підтримку штурвала чи керма, то варто зняти позначки для звичайних контролерів і контролерів DirectInput, бо Steam не підтримує перепризначення цих пристроїв.

Вибір конфігурації

Ви можете обрати створення вашої власної конфігурації або використати один із наших шаблонів. Створювати власну конфігурацію необов’язково, якщо ви не плануєте відступати від стандартних конфігурацій чи перепризначати окремі поєднання кнопок у грі. Вбудовані шаблони для введення Steam локалізуються на всі мови Steam, тож, будь ласка, переконайтеся, що локалізували ваші конфігурації, аби англійська не показувалася для користувачів, які не володіють англійською.

Вибір шаблону

Консольні контролери не розрізняються за варіантами шаблонів ґеймпадів, але ці налаштування важливі для контролера Steam. Ось перелік шаблонів із типами ігор, для яких вони призначені:
  • Ґеймпад — емулює контролер Xbox за допомогою правої сенсорної панелі, що стає емульованим джойстиком. Чудово для ігор двома стіками, платформерів чи спортивних ігор.
  • Ґеймпад із високоточною камерою чи прицілюванням. Цей шаблон ідеально підходить для стрілянки від першої особи чи будь-якої гри, яка використовує керування камерою. Перед вибором цього шаблону перевірте, чи ваша гра підтримує одночасне керування мишею й ґеймпадом, зокрема на додаткових екранах, як-от інвентарю чи мапи.
  • Ґеймпад із керуванням камерою — ця конфігурація бере схоже на поведінку миші введення із сенсорної панелі й переводить його в сигнали емульованого джойстика. Якщо гру можна налаштувати на високу чутливість джойстика, відсутність мертвої зони й лінійну криву прискорення, то ця конфігурація працюватиме добре. В іншому випадку, можливо, варто використовувати конфігурацію миші й клавіатури.

steam_input_gamepad_templates.png

Кроки для встановлення шаблону до вашої гри в Steamworks можна знайти ось тут

Створення нестандартної конфігурації

Конфігурації можна автоматично конвертувати між більшістю типів контролерів, але вам варто створити окремі конфігурації принаймні для контролера Steam і контролера Xbox.
Примітка: якщо ви плануєте скористатися перевагами особливостей пристрою (наприклад, керуванням рухом), то також варто створити розкладки для контролерів PlayStation 4 чи Nintendo Switch.

Введення тексту


Технічно введення екранного тексту не є частиною ISteamInput (введення Steam), а розташоване в ISteamUtils. Наразі це реалізовано лише тоді, коли гравець запускає гру в режимі Big Picture.

Деякі посилання:

Локалізація конфігурації
Якщо ви не використовуєте API введення Steam, то створити локалізовану конфігурацію непросто, але можливо.
  • Спершу переконайтеся, що ви не змінювали конфігурацію з часу останнього експорту. Відкрийте конфігуратор і спробуйте щось змінити (якщо це вийшло, то скасуйте зміну).
  • Відкрийте журнал Steam\Logs\controller_ui.txt і знайдіть там такий рядок:
    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
  • Вставте токени локалізації для призначень, які хочете локалізувати
    • Назва й опис:
      "controller_mappings" { "version" "3" "revision" "5" "title" "#title" "description" "#description" ...
    • Призначення кнопок:
      "button_a" { "activators" { "Full_Press" { "bindings" { "binding" "xinput_button A, #abutton" } } } } ...
  • Потім вкажіть відповідні значення для кожної мови в блоці локалізації:
    "localization" { "english" { "title" "Локалізована назва" "description" "Локалізований опис. Назви чотирьох лицевих кнопок також локалізуються." "abutton" "Ваша назва 1" "bbutton" "Ваша назва 2" "xbutton" "Ваша назва 3" "ybutton" "Ваша назва 4" } ...
  • Тепер ви можете експортувати конфігурацію та встановити її на партнерському сайті за цими інструкціями. Для однієї з конфігурацій вам потрібно буде встановити прапорець «Використовувати блок дій».

Конфігурацію прикладу можна оглянути в клієнті Steam, якщо ввести в браузер цю адресу: steam://controllerconfig/681280/1744110334. Також можна завантажити файл VDF ось тут.