GameProgrammar's Night

ゲームプログラム系の覚え書き

UnrealEngine4で2Dゲームを作ろう! その13 DirectInputを使おう編

 UE4でゲームパッドを使おう!(Windows)というお話です。

※追記※ JoystickPluginを本採用するのはオススメできなくなりましたので、DirectInputプラグインを作りました。こちらを見てみてください。

UE4のゲームパッド対応

 UE4(4.10現在)が対応してるパッドは、XInputパッドのみです。
 XInputというのはWindowsXboxのパッドを使えるようにするものでAPIが整理されてて使いやすくはあるんですが、Xboxのパッドしか使えないという欠点を抱えています。もちろん、XInput対応をうたっているゲームパッドは使えますが、世の中に出回っているほとんどのPC用ゲームパッドはXInputに対応されていません。*1

 多くのゲームパッドに対応するには、DirectInputへの対応が必須なのですが、UE4はサポートしていません。
 フォーラムでもDirectInput対応して欲しいという要望は上がっていますが反応がよくないので、公式サポートは期待できません。

UE4でDirectInputゲームパッドを使う!

 やり方は、2つあります。

Xbox 360 Controller Emulatorを使う

 DirectInput入力をXInput入力に変換してアプリに渡してくれるフリーソフトです。通称360ce。使い方は、適当にググってください。
 ざっとですが使ってみた所、UE4でも動きます。UE4エディタで動かすには出てきたDLLをエディタのexeファイルがあるフォルダにコピーしてください。ゲーム本体で使うなら、パッケージ化してゲームexeと同じフォルダにDLLをいれてください。

 DLLを同梱して、適当にコンフィグファイルをでっち上げれば動かせるかなと思ったのですが、動作を調べた所、パッド名などを使って変換すべき入力を判定してるようです。つまり、制作側でコンフィグファイル用意しても意味がないという……。
 そうなると、360ceの設定をユーザーに強いることになるので、どうなんだろうなぁ、と思ったので自分は使わないことにしました。元々使ってるユーザー向けに360ceでも大丈夫だよ!みたいなことをマニュアルに書く分にはありかなとは思います。

JoystickPluginを使う!

github.com

 というわけで、UE4でDirectInputパッドを使えるようにするプラグインを使用します。ここでは、Ikarus76氏が作ったオリジナル版を対象に話を進めます。DirectInputを使いたいという要望は多いので、このプラグインをベースに派生がいくつも作られていますので気になる方はそちらも見てみると良いかと思います。

 DLできるプラグインは、古いバージョンのUE4でビルドされたものですので、4.10で使うにはビルドしなおす必要があります。
 ビルド方法は、前記事を参考にして下さい。

katze.hatenablog.jp

JoystickPluginを4.10でビルドを通す

 そのままではコンパイルエラーでビルドが通りませんので、以下の場所を修正します。

JoystickSingleController.h 15行目/18行目

  • 変更前
15: int64 ButtonsPressedLow;
18: int64 ButtonsPressedHigh;

 int64をint32に変更

  • 変更後
15: int32 ButtonsPressedLow;
18: int32 ButtonsPressedHigh;

 この変数は、DirectInputから取得したボタンの押下情報をbitごとに保存する変数です。
 int64となっていたのは、DirectInputの仕様である128個のボタン押下情報を保存するつもりだったようですが、実際には32個ずつ64個の情報しか保存してませんのでint32で問題ありません。(WinJoystick.h 878行目~898行目の処理)
 

WinJoystick.h 467行目

  • 変更前
467: INT_PTR WINAPI WinProcCallback(

 INT_PTRをLRESULTに変更

  • 変更後
467: LRESULT WINAPI WinProcCallback(

 これはWinAPIの仕様です。

WinJoystick.h 643行目/646行目

  • 変更前
643: if (strVid && swscanf_s(strVid, L"VID_%4X", &dwVid) != 1)
646: if (strPid && swscanf_s(strPid, L"PID_%4X", &dwPid) != 1)

 swscanf_sでの受け取り型の不一致ですので、適当にキャストします。

  • 変更後
643: if (strVid && swscanf_s(strVid, L"VID_%4X", reinterpret_cast<uint32*>(&dwVid)) != 1)
646: if (strPid && swscanf_s(strPid, L"PID_%4X", reinterpret_cast<uint32*>(&dwPid)) != 1)

 DWORDはunsined longなので、uint32と(VCでは)互換があるはずなのですが、なぜかエラーになるのでキャストします。

以上のことをしてビルドを通して、JoystickPluginを有効にすると、DirectInputゲームパッドから入力を取る準備ができたことになります。

JoystickPluginを動作させる

 JoystickPluginを有効にしてエディタを起動します。アウトプットログに「JoystickPluginLog: Direct Input initialized.」と出れば使用準備が完了です。Failになってたり、ログに何もなかったらプラグインの起動に失敗していますので、ビルドをやり直して見て下さい。

 入力データを取る方法は2つあります。どちらかを選択してください。両方同時には動きません。*2

JoystickPluginActorを使う

 JoystickPluginが用意している基本のActorです。これをJoystickを使いたいレベルに配置することで、Joystickの入力をBPで取得することができるようになります。

 こちらの方法で大体のことはできると思います。通常の入力イベントは使えますし、アクションマッピングも動作します。
 ただ、JoystickInterfaceに実装されている一括イベントが、JoystickPluginActorを使う方法では取得できませんので、それが必要な時は次の方法を使います。

自前のJoystickActorを作る

 JoystickPluginActorを継承したActorか、JoystickComponentとJoystickInterfaceを両方持たせたActorを作ります。

 そうすることで、何かボタンが押された、軸入力があったなどのイベントを取ることできるようになります。キーコンフィグとか作る時は便利です。

データ接続はActorのTickで行われている

 入力データの接続はActorのTickで行われているので、ポーズするとJoystickの入力が取得できなくなります。JoystickActorはポーズ中もTickを呼ぶ設定にしておくと良いと思います。

f:id:katze_7514:20151117172224j:plain

JoystickPluginから取得できる情報

 ボタンはゲームパッドカテゴリ、軸系はなぜかキーボードカテゴリになっています。

通常の入力イベント

 キーボード入力やマウス入力と同様に、Joystick入力イベントが使えるようになります。

f:id:katze_7514:20151117171349j:plain

アクションマッピング

 アクションマッピングにもJoystick入力が追加されます。

f:id:katze_7514:20151117164849j:plain

一括イベント

f:id:katze_7514:20151117172554j:plain

 上図のように、入力の変化を一括して取得することができます。同時に複数の変化があった場合(ボタンの同時押しなど)は、変化があった回数だけイベントが呼ばれます。
 この動作イベントは、JoystickInterfaceを持ったActorのグラフでのみ使用することができます。

詳細情報(JoystickSingleController)

 イベントに頼らず入力情報を確認する方法もあります。

f:id:katze_7514:20151117173051j:plain

 上図のGetLatestFrameノードを使うことで、最新の入力データを見ることができます。コンパイルを通す時に紹介したJoystickSingleControllerがそのデータです。詳細は、右クリしてJoystickFrameで検索すると以下のような感じです。

f:id:katze_7514:20151117173658j:plain

 よく使うプロパティの使い方を軽く説明しますと、

Buttons Pressed High/Low

 ボタンが押されているかをビット単位で持っています。下位ビットから始まって、Lowがボタン1から32、Highがボタン33から64まで持っています。そのため、ボタンの押下情報は「bitwise and」を使って取得します。

GetAxis/RAxis

 Axisが軸入力、RAxisが回転入力です。Vectorになっていまして、VectorのXがX軸の値という風に、XYZがそのまま対応しています。
 DirectInputの場合、多くのパッドは、左スティック横X軸・縦Y軸、右スティック横Z軸・縦Z回転に割り当てられてるのをちょっとだけ覚えておくと良いかもしれません。

GetPOV

 POV入力です。0~2まで3つあるのはDirectInputの仕様だからですが、通常はPOV0しか使いません。いわゆる十字キーに割り当てられてることが多いです。押されてる方向のEnum値が取得できます。

XInputとの共存

 JoystickPluginは、XInputパッドとして見つかったとしてもDirectInputパッドとして動作するようになっていますので、パッドはすべてJoystickとして実装する場合は特に問題はありません。*3
 XInputならXInputとして扱いたい場合は、UE4のゲームパッド入力をチェックしてイベントがあったらJoystick動作を止めるとかそういう処理を書くことになるかと思います。

JoystickPlugin使用上の注意

入力を取得してるJoystickは1つだけ

 最初に発見したJoystickのみ入力を取りにいっています。複数のJoystickを使う必要があるときは、派生JoystickPluginを使いましょう。

github.com

とか。複数のJoystick対応しつつ、SDLベースにしてWin以外でも動くらしいです。

複数のJoystickActorを切り替えながら使うのも非推奨

 同時に動かせなくても切り替えられるだろう? と思いきや、JoystickPluginの設計上の問題で簡単にはいきません。
 JoystickActorを切り替える時はTickのON/OFFだけではダメでBeginPlayも呼ぶ必要があります。いらなくなったActorをDestoryして、新しいActorをSpawnすれば良いということではあります。
 ただ、どうやっても一括イベントを取得できるActorは必ず1つだけです。

*1:XInput対応パッドはちょっとお値段が上がるので、よほどわかってる人以外買って無いと思われます

*2:両方使ってしばらく悩みましたとさ

*3:はじめはXInputパッドはDirectInputパッドから削除する実装にしようとしてたみたいですがコメントアウトされてました