Steam 輸入是一項服務,讓 Steam 使用者可以用自己選擇的裝置遊玩任何支援控制器的遊戲。 Steam 輸入會透過遊戲手把模擬、滑鼠和鍵盤模擬或 Steam 輸入 API,將使用者的輸入轉譯為遊戲能明白的訊號。 本文將重點說明使用 Steam 輸入遊戲手把模擬的最佳作法,以擴展您遊戲現有的控制器支援。
何謂遊戲手把模擬?
- 在 Windows 中,Steam 內嵌介面會與傳統的遊戲手把輸入 API 掛鉤,例如 XInput、DirectInput、RawInput 和 Windows.Gaming.Input,並插入模擬的 Xbox 控制器裝置。 而 macOS 和 Linux 中的模擬控制器輸入則由驅動程式提供
- 模擬控制器會在您的遊戲內顯示為 Xbox 控制器,這代表在擁有額外輸入方式的控制器上,某些輸入會有重複,例如 PlayStation 觸控板的點擊和選項按鈕都對應至 XInput 的 Start 鍵
- 除了一般的遊戲手把輸入以外,還可以將 Switch、PlayStation 和 Steam 控制器陀螺儀輸入綁定為滑鼠模擬,並提供動態控制。 這僅適用於單人單機的遊戲,因為只會有一個滑鼠輸入,且遊戲必須同時接受滑鼠與遊戲手把輸入。 若對這些功能感興趣,但不想受到上述限制,請考慮添加 Steam 輸入 API
- 您可以向 Steam 查詢目前使用的控制器類型,來顯示特定裝置的字符,但只限於您目前的 Steamworks SDK 支援的類型。 若有興趣使用具未來沿用性的字符,請整合 Steam 輸入 API
- 即使您已經支援某些控制器,仍可能有使用者透過 Steam 輸入來使用這些控制器,因為 Steam 遠端暢玩會用來在串流時提供輸入,也有相當一部分的使用者啟用 Steam 輸入,為其整個 Steam 收藏庫重新配置控制器。 整體來說,Steam 在 2022 年所有的控制器遊戲階段中,約有四分之一使用的是 Steam 輸入,其中將近一半為 PlayStation 控制器
Steam 上有 2000 款遊戲使用至少一種控制器類型的遊戲手把模擬,包括《Monster Hunter: World》、《Ace Combat 7》、《勇者鬥惡龍XI》、《Into the Breach》和《Middle Earth: Shadow of War》,儘管並非所有遊戲都採用每一項最佳做法。 這裡我們將以《Into the Breach》作為範例,因為這款遊戲在遵照最佳作法方面做得非常好。
顯示裝置特定的圖像素材
我們支援數種使用 Steam 輸入遊戲手把模擬來獲得裝置特定的字符的管道——一種用於可在執行時載入具沿用性的圖片的遊戲(這表示在 Steam 更新 Steam 輸入時,無需更新遊戲也能使用),另外兩種是需要將圖片預先納入至其資產中的遊戲,或希望使用和遊戲主機連接埠相同的風格化圖像素材的遊戲。 《Into the Breach》採用了自己為 Xbox 和 Steam 控制器所製作的圖像素材:
PlayStation:
Nintendo Switch 控制器:
備註:即使是使用自己的圖像素材,我們也建議您在無法辨識控制器時以 Steam 的圖像素材為遞補,或呼叫我們的幫手函式,以尋找在遊戲發行當時存在的最接近的選項。如此一來,未來新增裝置時便有合適的字符可用。
顯示 Steam 具未來沿用性的字符
您將需要使用以下函式:
程式碼範例:
// 使用各函式前須先初始化介面——呼叫一次即可!
SteamInput()->Init()
// ...
// SteamAPI_RunCallbacks() 會為所有初始化的介面呼叫 RunFrame 函式
// 大多數遊戲應已開始定期呼叫此函式。 如果沒有,則必須手動更新
// Steam 輸入介面
SteamInput()->RunFrame();
// ...
// 以要查詢的 XInput 欄位取代。 數值為 0 到 3 之間
// 若要在選擇欲使用的 API 前先以 RawInput 辨別裝置,請見
// 下方的「使用 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 輸入所配置的控制器
// 備註:使用 Steam 輸入 API 的控制器不會透過 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 );
顯示自己的圖像素材——字符選擇區(Glyph Palette)
若根據控制器類型而從多個字符選擇區擇一使用,則可以使用以下函式以 XInput 欄位向 Steam 詢問所需資訊:
程式碼範例:
// 使用各函式前須先初始化介面——呼叫一次即可!
SteamInput()->Init()
// ...
// SteamAPI_RunCallbacks() 會為所有初始化的介面呼叫 RunFrame 函式
// 大多數遊戲應已開始定期呼叫此函式。 如果沒有,則必須手動更新
// Steam 輸入介面
SteamInput()->RunFrame();
// ...
// 以要查詢的 XInput 欄位取代。 數值為 0 到 3 之間
// 若要在選擇欲使用的 API 前先以 RawInput 辨別裝置,請見
// 下方的「使用 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 之間
// 若要在選擇欲使用的 API 前先以 RawInput 辨別裝置,請見
// 下方的「使用 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 輸入所配置的控制器
// 備註:使用 Steam 輸入 API 的控制器不會透過 GetControllerForGamepadIndex() 回傳控制代碼
buttonOrigin = SteamInput()->GetActionOriginFromXboxOrigin( controller1Handle, k_EXboxOrigin_A ); // 也就是 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 輸入函式對應。 您可以改為從 GetRawInputDeviceInfo() 查詢 RIDI_DEVICENAME 字串來獲得此資訊,而前者是以真實裝置的 USB VID/PID、遊戲手把索引碼、和 Steam 輸入控制代碼所編碼。字串格式如下:
\\.\pipe\HID#VID_045E&PID_028E&IG_00#{真實裝置 VID}&{真實裝置 PID}&{Steam 輸入 API 控制代碼}#{Steam 輸入遊戲手把控制代碼}#{處理序 ID}
舉例來說,\\.\pipe\HID#VID_045E&PID_028E&IG_00#045E&0B00&00045EB00704DAF3#0#20160 便是 VID/PID 為 0x45e/0x0b00 的 Xbox One 控制器。
程式碼範例:
#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; /* 好吧 */
}
devices = (PRAWINPUTDEVICELIST)malloc(sizeof(RAWINPUTDEVICELIST) * device_count);
if (devices == NULL) {
return;
}
if (GetRawInputDeviceList(devices, &device_count, sizeof(RAWINPUTDEVICELIST)) == -1) {
free(devices);
return; /* 好吧 */
}
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,或透過 GetInputTypeForHandle 使用 ulDeviceHandle 來辨別裝置
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 設定
第一步是在 Steamworks 設定中的應用程式 -> Steam 輸入之下的「讓控制器支援 Steam 輸入」勾選欲使用 Steam 輸入的控制器類型。 若為支援 Xbox 控制器的標準遊戲,我們建議勾選 Xbox 以外所有核取方塊:
若遊戲支援飛行搖桿或賽車方向盤,則最好取消勾選通用 / DirectInput 控制器的方塊,因為 Steam 不支援此類裝置的對應。
選擇配置
您可以選擇建立自己的配置,或從我們預先做好的模板中挑選一個。 除非您計畫修改我們預設的配置,或根據遊戲內的作用各別標出按鍵配置,否則不需要製作自己的配置。 Steam 輸入的內建模板皆已在地化為所有 Steam 支援的語言,因此請務必將您的配置在地化,以避免向非英文使用者顯示英文。
選擇模板
對主機的控制器而言,不同版本的遊戲手把模板沒有區別,但這些設定對 Steam 控制器則很重要。 以下為不同模板的說明,及適用的遊戲類型:
- 遊戲手把 - 將右觸控板轉變為原始模擬搖桿使用,以模擬 Xbox 控制器。 非常適合雙搖桿、平台遊戲,或體育類遊戲
- 高準度視角 / 瞄準的手把 - FPS 或任何使用視角控制的遊戲的理想配置。 選用此選項前,請細心測試遊戲是否支援同時使用遊戲手把及滑鼠,包括在物品庫或地圖等非主要畫面當中
- 可控制視角的手把 - 此配置將從觸控板取得滑鼠式的輸入,並將其轉譯為模擬搖桿的撥動。 如果您的遊戲可以配置為高搖桿靈敏度、無死區,以及線性加速,那麼此配置將順暢運作。否則,請考慮使用滑鼠 / 鍵盤配置
請見
此文了解在 Steamworks 中為您的遊戲r將模板開放使用的步驟。
建立自訂配置
大多數的控制器配置都可根據不同控制器類型自動轉換,但您至少需要製作一個 Steam 控制器和一個 Xbox 控制器的配置。
備註:如果您希望利用特定裝置的功能,像是動態控制,您可能也需要為 PlayStation 4 或 Nintendo Switch 控制器設定配置。
文字輸入
螢幕上的文字輸入嚴格說來並不是
ISteamInput(Steam 輸入)的一部份,而是屬於
ISteamUtils。 此功能目前只有實作用於玩家透過 Big Picture 模式啟動遊戲的情況。
快速參考資料:
在地化配置在不使用 Steam 輸入 API 的情況下將配置在地化會較為複雜,但仍有方法做到。
- 開始前,請先確定您的配置自上次匯出後未有任何修改 開啟配置器並進行修改(修改後再回復變更是無妨的)。
- 前往 Steam\Logs\controller_ui.txt 記錄檔,尋找類似以下文字的字串:
Steam Controller Saving Controller Configuration Autosave for [控制器序列號]- AppID: [您的 APPID].
Loaded Config for Local Selection Path for App ID [您的 APPID], Controller 0: F:\ProgramFiles\Steam\client\userdata\[STEAMID]\config\controller_configs\apps\[您的 APPID]\[控制器序列]\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"
{
"tchinese"
{
"title" "此為經過在地化的標題"
"description" "此為經過在地化的說明。" 按鈕的菱形配置也有在地化名稱。{
"abutton" "您的名稱 1"
"bbutton" "您的名稱 2"
"xbutton" "您的名稱 3"
"ybutton" "您的名稱 4"
}
...
- 現在即可匯出配置並根據這些指示在合作夥伴網站進行設置。 最好將其中一份配置設為勾選「使用動作區塊」。
於瀏覽器中輸入此網址即可在 Steam 用戶端中預覽範例配置:steam://controllerconfig/681280/1744110334 ,或
由此取得 VFD 檔案。