Steam入力は、コントローラに対応したゲームを、Steamユーザーが好みのデバイスでプレイできるようにするサービスです。 Steam入力は、ゲームパッドエミュレーション、マウス&キーボードエミュレーション、またはSteam入力APIのいずれかを通じて、ユーザーの入力をゲームが理解できるように変換します。 ここでは、Steam入力ゲームパッドエミュレーションを最大限に利用して、ゲームの既存のコントローラサポートを拡張する方法に焦点を当てて説明します。
ゲームパッドエミュレーションとは?
- Windowsでは、SteamオーバーレイがXInput、DirectInput、RawInput、Windows.Gaming.Inputなどの従来のゲームパッド入力APIをフックし、エミュレートされたXboxコントローラーデバイスを挿入します。 macOSおよびLinuxでは、エミュレートされたコントローラの入力はドライバーによって提供されます。
- コントローラはゲーム内でXboxコントローラーとして表示されます。追加の入力があるコントローラでは、一部重複することを意味します。例:PlayStationのトラックパッドのクリックとオプションボタンの両方がXInputのスタートボタンにマッピングされます。
- 通常のゲームパッド入力に加え、Switch、PlayStation、Steamコントローラジャイロ入力をマウスエミュレーションに割り当てることで、モーションコントロールを提供することができます。 ただし、マウス入力が1つしかなく、ゲームがマウスとゲームパッドの同時入力を受け付けることに依存するため、この機能は、シングルローカルプレイヤーのゲームでのみ動作します。 このような制限なしでこれらの機能を使用することに興味がある方は、Steam入力APIの追加を検討してください。
- デバイス特有のグリフを表示するために、Steamからコントローラタイプを照会できますが、照会できるのは現在Steamworks SDKでサポートされているタイプに限られます。 将来にも有効なグリフサポートに興味がある場合は、Steam入力APIを統合してください。
- すでにサポートしているコントローラであっても、Steam入力を介してプレイしているユーザーがいる場合があります。これは、Steam Remote Playがストリーミング中の入力の提供にSteam入力を使用しており、かなりのユーザーがSteamライブラリ全体でコントローラを再設定するためにSteam入力を有効にしているためです。 2020年のSteamの全コントローラセッションの約4分の1がSteam入力を使用し、その中には、PlayStationの全コントローラーセッションの約半数が含まれます。
『モンスターハンター:ワールド』、『エースコンバット7』、『ドラゴンクエストXI』、『Into the Breach』、『ミドルアース:シャドウ・オブ・ウォー』といった注目のゲームを含め、Steam上の2000以上のゲームが、少なくとも1つのコントローラタイプでゲームパッドエミュレーションを採用しています。ただし、すべてのゲームがベストプラクティスに挙げた内容に完全に沿っているわけではありません。 以下の各ベストプラクティスに従った非常に良い例であることから、ここでは『Into the Breach』を紹介します。
デバイスごとにアートを表示
Steam入力ゲームパッドエミュレーションでは、デバイス特有のグリフを取得する方法をいくつかサポートしています。1つは実行時に画像を読み込むことができるゲーム用で、将来においても有効です(Steam入力がアップデートされると、ゲームをアップデートすることなく使用できることを意味します)。2つ目は、アセット内に画像を焼き付けるか、家庭用ゲーム機器の移植版から同じ様式のアートの使用を想定したゲーム用です。 『Into the Breach』ではXbox/Steamコントローラ用に専用のアートを用意しています:

PlayStation:

Nintendo Switchコントローラ-:

注意:専用のアートを使用する際の注意として、コントローラが認識されない場合には、Steamのアートにフォールバックするか、またはヘルパー関数を呼び出して、ゲームのリリース時から利用可能な最も適切なオプションを見つけてください。それにより、将来デバイスが追加されたときにも適切なグリフが利用できます。
将来にも有効な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入力を介して構成されたコントローラです
// 注:Steam入力APIを使用しているコントローラはGetControllerForGamepadIndex()を介してハンドルを返しません
buttonOrigin = SteamInput()->GetActionOriginFromXboxOrigin( controller1Handle, k_EXboxOrigin_A );
}
else
{
// 有効なハンドルは0以外です。これは通常のXboxコントローラです
// 元のボタンを引き続き使用してください
}
// Steamでサポートするデバイスが増えるにつれ、EInputActionOriginの値も増え続けますが問題ありません
// なぜなら、この例では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 );
オリジナルのアートを表示-グリフパレット
複数のグリフパレットを使用していて、コントローラの種類に基づいて使用するグリフを選択している場合は、以下を使用してXInputスロットでこの情報をSteamに要求できます。
コード例:
// 個々の関数を使用する前にインターフェイスを初期化します-呼び出す必要があるのは一度だけです!
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入力を介して構成されたコントローラです
// 注: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を使用
一部のゲームは、4台以上のコントローラをサポートするため、またはPlayStationコントローラーが接続されているかどうかを判断するために、RawInputを使用します。 このAPIは、ゲームパッド以外のデバイスも返し、Steam入力関数から直接マッピングするための信頼できるインデックスはありません。 代わりに、実際のデバイスのUSB VID/PID、ゲームパッドインデックス、および次の形式のSteam入力ハンドルでエンコードされたGetRawInputDeviceInfo()からRIDI_DEVICENAME文字列を照会することで、この情報を取得できます。
\\.\pipe\HID#VID_045E&PID_028E&IG_00#{Real device VID}&{Real Device PID}&{Steam Input API handle}#{Steam Input Gamepad Index}#{ProcessID}
たとえば、VID/PIDが0x45e/0x0b00のXbox Oneコントローラーの場合は、\\.\pipe\HID#VID_045E&PID_028E&IG_00#045E&0B00&00045EB00704DAF3#0#20160です。
コード例:
#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のいずれかを使用してデバイスを直接識別することや、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での設定
最初のステップは、「アプリケーション」>「Steam入力」の下にあるSteamworks設定のオプトインセクションで、どのコントローラがSteam入力を使用するかをセットアップすることです。 Xboxコントローラーをサポートする標準のゲームの場合、Xbox以外のすべてのチェックボックスをオンにすることを推奨しています:

ゲームがフライトスティックやレーシングホイールをサポートしている場合は、ジェネリック/DirectInputコントローラのチェックボックスをオフにしてください。Steamはこれらのデバイスの再マッピングをサポートしていません。
設定を選択
オリジナルの設定を作成するか、または事前に作成されたテンプレートから選択することができます。 Steam側で用意したデフォルトから微調整を行ったり、ゲーム内での個々のバインドにラベルを付けたりしない限り、専用の構成を自作する必要はありません。 Steam入力の組み込みテンプレートはSteam対応の全言語にローカライズされているため、英語以外を選択しているユーザーに英語が表示されないように、設定もローカライズしてください。
テンプレートを選択
家庭用ゲーム機のコントローラは、各種ゲームパッド用テンプレートを区別しませんが、Steamコントローラにとってこれらの設定は重要です。 以下は、各テンプレートの説明と、使用に適したゲームの種類です:
- ゲームパッド-Xboxコントローラーをエミュレートし、右トラックパッドは、未加工のエミュレートされたジョイスティックに変わります。 ツインスティック、プラットフォームゲームやスポーツゲームに適しています。
- ゲームパッド+高精度カメラ/エイム-この設定はFPSやカメラコントロールを使用するゲームに理想的です。 このテンプレートを選択する前に、ゲームがゲームパッドとマウスを同時に使用できることをテストしてください。インベントリやマップスクリーンなどの補助的な画面でも確認してください。
- ゲームパッド+カメラコントロール-この設定は、マウススタイルの入力をトラックパッドから受け取り、エミュレートされたジョイスティックのフリックに変換します。 ゲームで、ジョイスティックの感度を非常に高く設定でき、デッドゾーン無し、直線的な加速度カーブを持たせるようにできる場合、この設定は適切に機能しますが、それ以外の場合は、マウス/キーボード設定の使用を検討してください。

Steamworksでゲームのテンプレートを公開する方法は
こちらです。
カスタム設定の作成
ほとんどのコントローラ間で構成の自動変換ができますが、少なくとも、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"
{
"english"
{
"title" "ローカライズされたタイトル"
"description" "ローカライズされた説明がここに入ります。 ボタンのダイヤ型(菱形)バインド名もローカライズできます。"
"abutton" "ローカライズ名1"
"bbutton" "ローカライズ名2"
"xbutton" "ローカライズ名3"
"ybutton" "ローカライズ名4"
}
...
- 設定をエクスポートして、こちらの手順に従って、パートナーサイトで設定を公開します。 設定の1つに「アクションブロックを使用」チェックボックスを設定してください。
ブラウザーに次のURLを入れることでSteamクライアント内で設定例をプレビューできます:steam://controllerconfig/681280/1744110334 または、VDFファイルは
こちらです。