1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[UE4]RawInputを使ってDirectInputのゲームパッドを動かす

Last updated at Posted at 2022-09-30

概要

UE4は基本的にXInputでのみ動作するようになっている。
しかし昨今のPCゲームでは手持ちのPS4コントローラや従来品のDirectInputコントローラを使用することも多く、DirectInput対応を入れる開発者も多いだろう。

UE4公式のRawInputプラグインを用いれば簡単にDirectInputのゲームパッドを使えるようにできる!わけもなく。
当記事はいろいろ試して動くようにした忘備録である。

開発環境

UE4.27
VisualStadio2019
PS4ワイヤレスコントローラ
LogicoolF310コントローラ

実装

プラグイン設定

RawInputのプラグインを有効にする。
image.png

プロジェクト設定からRawInputで認識するデバイスを登録する。
image.png
この設定ではLogicoolF310とワイヤレスコントローラの2つのデバイスを登録している。
VendorIDとProductIDは、デバイスマネージャーから取得した。
image.png
この設定を行えばゲーム側で最低限DirectInputの入力イベントをとることができるようになる。

入力値の確認

細かい設定を行う前に、RawInputが提供しているコマンド showdebug rawinput を使って入力がどう取得されるのか確認してみる。
logicoolコントローラを接続した状態で何も押していない状態(=ニュートラル)ではこんな入力値になっているようだ。
image.png
Button1~Button20がFaceButtonやLB,RBなどのデジタル入力、Axis1~Axis8がスティックなどのアナログ入力となっている。
しかしこの表示をみるだけでは、Buttonの何番にパッドのどのキーが割り振られているかがわからない。
実際にパッドを押してみてどこが反応するか見るしかないようだ。

また、アナログ入力(Axis1~4)のニュートラル値が0.5となっている。
これはXInputが(-1.0)~(1.0)の入力が取れるのに対して、DirectInputでは(0.0)~(1.0)が取れているからだ。
このままでは使いづらいため、RawInputの設定で調整を行う。

入力値の設定

使用したいAxis入力に対して設定を行う。
DeviceConfigurations>[任意コントローラ]>AxisProperties内のIndexは showdebug rawinput で表示されるテキストのGenericUSBController_Axis1~Axis8に該当する。
image.png
・Enable:このキーを有効にするか
・Key:対応するキー
・Inverted:入力値を逆にするか
・GamePadStick:入力値を(-1.0)~(1.0)にマッピングするか
・Offset:入力値のオフセット

これらの設定を各イベントに対して行う。
GenericUSBController_Button1~20はEnableの設定しかないため、ほぼ触らなくていいはずだ。

設定を入れた上で入力の対応表を以下にまとめる。

RawInputButton名 ワイヤレスコントローラの入力 LogicoolF310の入力
Button1 X
Button2 × A
Button3 B
Button4 Y
Button5 L1 LB
Button6 R1 RB
Button7 L2 LT
Button8 R2 RT
Button9 SHARE BACK
Button10 OPTION START
Button11 LeftStick押し込み LeftStick押し込み
Button12 RightStick押し込み RightStick押し込み
Button13 PlayStationボタン -
Button14 TouchPad -
Button15 - -
Button16 - -
Button17 - -
Button18 - -
Button19 - -
Button20 - -
RawInputAxis名 ワイヤレスコントローラの入力 LogicoolF310の入力 設定備考
Axis1 RightStick
↑(1.0) ・(0.004) ↓(-1.0)
RightStick
↑(1.0) ・(0.004) ↓(-1.0)
Inverted=true
GamePadStick=true
Offset=0.0
Axis2 RightStick
→(1.0) ・(0.004) ←(-1.0)
RightStick
→(1.0) ・(0.004) ←(-1.0)
Inverted=false
GamePadStick=true
Offset=0.0
Axis3 LeftStick
↑(1.0) ・(0.004) ↓(-1.0)
LeftStick
↑(1.0) ・(0.004) ↓(-1.0)
Inverted=true
GamePadStick=true
Offset=0.0
Axis4 LeftStick
→(1.0) ・(0.004) ←(-1.0)
LeftStick
→(1.0) ・(0.004) ←(-1.0)
Inverted=false
GamePadStick=true
Offset=0.0
Axis5 方向キー
※詳細は後述
方向キー
※詳細は後述
Inverted=false
GamePadStick=false
Offset=0.0
Axis6 - - -
Axis7 R2 - -
Axis8 L2 - -

表中の括弧内はその方向入力時のAxisValue、「・」はニュートラル状態を示す。
方向キーの入力はAxis5に割り振られているが、全方向が一つのイベントとして取れるためスティックの入力値と同じように扱うことはできない点に注意が必要である。

方向キーの変換

方向キーの入力は一つのAxisとして取得が可能で、どの方向の入力なのかは計算で算出することができる。
image.png

Axis5のGamePadStickをfalseにしているのであれば入力値は(0.0)~(1.0)になるため、方向キーの各方向(8方向分)に割り振ってあげればいい。
image.png
ただしコレには少し落とし穴がある。
基本的には機械の世界では0は入力なし、1は入力ありとして扱いたいものだ。
しかしこの方向キーは↑の入力が0.0としてとれてしまう。

そのため下記のようにAxisPropertiesのOffsetに1.0を設定する。
image.png

こうすることで、入力がある場合を1.0~2.0へ収めることができ、0.0は入力なしとして扱うことができる。
image.png

入力イベントの登録

さて、ここでようやくRawInputで取れる入力キーをActionMappingに紐づける。
なんてことはない簡単な作業だ。
GenericUSBControllerというカテゴリ内にキーがまとまっているため、先程確認しておいたキー番号を適当に設定する。
image.png
既存のActionMappings/AxisMappingsに組み込んでしまえば、ここまでの処理でDirectInputのパッドでもXInput同様に動かすことが可能である。

まとめ

RawInputを使うにあたって検索して出てきたページが「うまく動かないから使うのやーめた」となっており(しかも一つじゃない)少々不安ではあったがなんとか動かすところまでいけてよかった。

実は当初はPS5のコントローラも試してみたが、入力を取る時点でかなり面白おかしいことになってしまったため、当記事では省略している。
是非実際に試してみていただきたい。

参考

【おまけ】開発上の注意点

開発に当たってぶち当たって困ったことについて記載する。

入力が取れない

複数パッドの入力を試すため、パッドを代わる代わるPCに挿して試そうとしたところ、はじめに刺していたパッドの入力は取れるのに2つ目に挿したパッドの入力が正常に取れなかった。
→どうやらパッドの入力はエディタが立ち上がる際にささっていたものが正常に取れるようだった。
そのため上手く入力が取れなかったパッドもエディタ起動前に挿しておけば正常に取れることを確認した。

logicoolF310の入力値がおかしい

logicoolのパッドしか挿していないのにデバイスが2個あって、どちらの入力値もおかしい状態になった。
→エディタが立ち上がった状態でパッドの設定をXInput⇔DirectInput切り替えしてしまったため、パッドは1つなのに入力デバイスが2つになったようだ。
そのため2つの入力値が合算された入力値が取得されていたらしい。
これはXInput⇔DirectInputが切り替えられるパッドでのみ起こる問題だと思われる。

パッドはニュートラル状態なのにスティックの入力が入る

この問題は本当に多かった。
様々な要因が考えられるため、自分が引っかかったポイントをまとめておく。

・RawInput設定でAxis系の項にGamePadStickがfalseになっているものはないか?
showdebug rawinputでみるとAxis系のニュートラルの入力が0.5になっているものがあったらそいつが怪しい。

・エディタ起動時にXInputしか刺さっていない
→なぜかRawInputがXInputを自分のイベント(GeneralUSB系)にアサインしてしまうようだ。やめてほしい。
この問題を解決するため、RawInputのソースコードを調査した。
RawInputの実際のイベント処理などはRawInputWindowsというクラスが行っているようだった。

どうもRawInputWindowsの処理を追っていくと、コンストラクタ内でDeviceID=4がDirectInput、DeviceID=5がXInputになっており、DirectInputのパッドを見つけられなかった場合、XInputをRawInputのイベントに登録する処理が走ってしまうため、XInputの入力が重複してしまうようだ。

RawInputWindows.cpp
FRawInputWindows::FRawInputWindows(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler)
	: IRawInput(InMessageHandler)
{
	DefaultDeviceHandle = INDEX_NONE;
	DLLPointers.InitFuncPointers();

	FWindowsApplication* WindowsApplication = (FWindowsApplication*)FSlateApplication::Get().GetPlatformApplication().Get();
	check(WindowsApplication);

	WindowsApplication->AddMessageHandler(*this);
	QueryConnectedDevices();

	// Register a default device if desired
	if (GetDefault<URawInputSettings>()->bRegisterDefaultDevice)
	{
		const uint32 Flags = 0;
		const int32 PageID = 0x01;
		int32 DeviceID = 0x04;
		DefaultDeviceHandle = RegisterInputDevice(RIM_TYPEHID, Flags, DeviceID, PageID);
        // ここから-----------------------
        // ↓↓DirectInputのパッドを見つけられなかった場合、刺さっているXInputのパッドを入力デバイスとして登録する処理をしている↓↓
		if (DefaultDeviceHandle == INDEX_NONE)
		{
			DeviceID = 0x05;
			DefaultDeviceHandle = RegisterInputDevice(RIM_TYPEHID, Flags, DeviceID, PageID);
		}
        // ここまで------------------------
	}

	AHUD::OnShowDebugInfo.AddRaw(this, &FRawInputWindows::ShowDebugInfo);
}	

安直にここの処理を消してみたが、そうしたら今度はDirectInputのパッド入力が取れなくなってしまったため、下記のようにソースに手を入れた。

RawInputWindows.cpp
void FRawInputWindows::SetupBindings(const int32 DeviceHandle, const bool bApplyDefaults)
{
	FRawWindowsDeviceEntry& DeviceEntry = RegisteredDeviceList[DeviceHandle];
	const URawInputSettings* RawInputSettings = GetDefault<URawInputSettings>();
	bool bDefaultsSetup = false;

	//@third party code BEGIN-----
	// 登録されているデバイスか.
	bool bRegisteredDevice = false;
	//@third party code END-------
	for (const FRawInputDeviceConfiguration& DeviceConfig : RawInputSettings->DeviceConfigurations)
	{
		const int32 VendorID = FCString::Strtoi(*DeviceConfig.VendorID, nullptr, 16);
		const int32 ProductID = FCString::Strtoi(*DeviceConfig.ProductID, nullptr, 16);

		// If VendorId or ProductId are 0, apply to everything
		if ((VendorID == 0 || VendorID == DeviceEntry.DeviceData.VendorID) && (ProductID == 0 || ProductID == DeviceEntry.DeviceData.ProductID))
		{
			//@third party code BEGIN-----
			bRegisteredDevice = true;
			//@third party code END-------
			const int32 NumButtons = FMath::Min(DeviceConfig.ButtonProperties.Num(), MAX_NUM_CONTROLLER_BUTTONS);
			DeviceEntry.ButtonData.SetNum(NumButtons);
			for (int32 Index = 0; Index < NumButtons; ++Index)
			{
				const FRawInputDeviceButtonProperties& ButtonProps = DeviceConfig.ButtonProperties[Index];
				DeviceEntry.ButtonData[Index].ButtonName = (ButtonProps.bEnabled ? ButtonProps.Key.GetFName() : NAME_None);
			}

			const int32 NumAnalogAxes = FMath::Min(DeviceConfig.AxisProperties.Num(), MAX_NUM_CONTROLLER_ANALOG);
			DeviceEntry.AnalogData.SetNum(NumAnalogAxes);
			for (int32 Index = 0; Index < NumAnalogAxes; ++Index)
			{
				const FRawInputDeviceAxisProperties& AxisProps = DeviceConfig.AxisProperties[Index];
				FAnalogData& AnalogData = DeviceEntry.AnalogData[Index];
				if (AxisProps.bEnabled)
				{
					AnalogData.KeyName = AxisProps.Key.GetFName();
					AnalogData.Offset = AxisProps.Offset;
					AnalogData.bInverted = AxisProps.bInverted;
					AnalogData.bGamepadStick = AxisProps.bGamepadStick;
				}
				else
				{
					AnalogData.KeyName = NAME_None;
				}
			}

			bDefaultsSetup = true;
			break;
		}
	}

	//@third party code BEGIN-----
	// Before..
	// if (!bDefaultsSetup && bApplyDefaults)
	// After..
	// デバイス登録されたものしかBindしない.
	if (!bDefaultsSetup && bApplyDefaults && bRegisteredDevice)
	//@third party code END-------
	{
		BindAnalogForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Axis1, 0);
		BindAnalogForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Axis2, 1);
		BindAnalogForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Axis3, 2);
		BindAnalogForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Axis4, 3);
		BindAnalogForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Axis5, 4);
		BindAnalogForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Axis6, 5);
		BindAnalogForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Axis7, 6);
		BindAnalogForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Axis8, 7);

		BindButtonForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Button1, 0);
		BindButtonForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Button2, 1);
		BindButtonForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Button3, 2);
		BindButtonForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Button4, 3);
		BindButtonForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Button5, 4);
		BindButtonForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Button6, 5);
		BindButtonForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Button7, 6);
		BindButtonForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Button8, 7);
		BindButtonForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Button9, 8);
		BindButtonForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Button10, 9);
		BindButtonForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Button11, 10);
		BindButtonForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Button12, 11);
		BindButtonForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Button13, 12);
		BindButtonForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Button14, 13);
		BindButtonForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Button15, 14);
		BindButtonForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Button16, 15);
		BindButtonForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Button17, 16);
		BindButtonForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Button18, 17);
		BindButtonForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Button19, 18);
		BindButtonForDevice(DeviceHandle, FRawInputKeyNames::GenericUSBController_Button20, 19);
	}
}

やっていることはデバイスにGenericUSBControllerのイベントをバインドする条件に、RawInput設定に記載したデバイスかどうかのチェックを追加しただけである。
これによってXInputしか刺さっていない状態でも、おかしな動きをすることはなくなった。


おまけのおまけ

RawInputの設定でVendorID = ProductID = 0にしておくとどのデバイスであろうとイベントをバインドしてくれるらしい。
XInputはここの処理までこない(と思う)ため、もしデバイスごとに設定を変えなくていいのであればRawInputのID設定は0にしておいていいかもしれない。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?