GameProgrammar's Night

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

UnrealEngine4で2Dゲームを作ろう! その15 BehaviorTreeのDecorator/Task作ってみた

という感じで、BehaviorTree(以下BT)で敵AIを作ってみた時にわかったことを書きます。

BT全体

f:id:katze_7514:20160103170558j:plain

BT全体は上図の通りで、移動して攻撃したらちょっと待つを繰り返します。

Decorator/IsEnableAI

 BTは、外からPauseしたりStopしたりする機能がないので、代わりに根元で実行を止めるDecoratorを付けました。
 中身は、対応するControllerの状態を調べてBTを実行する状態かをチェックしています。

Decorator実装

 Decoratorの実装は、「PerformConditionCheckAI」をオーバーライドし、boolをリターンします。
f:id:katze_7514:20160103174211j:plain
 AIがついていない「PerformConditionCheck」もあり、どっちも使えるのですが、AI付きの方はBTを実行しているController/Pawnを引数として取れるのでAI付きの方が使いやすいと思います。

Task/SimpleMoveTo,Attack

SimpleMoveTo

 NavMeshがリビルドエラーを吐き使えなかったので、MoveToを自前実装したのがSimpleMoveToです。中身は単純にPlayerPawnに近づいて一定距離になったら止まるというだけです。
 このリビルドエラーは、AnswerHubにも投稿されていますが完全な解決には至ってないようです。

 移動中は処理を継続し、PlayerPawnの一定距離内に来たらFinishExecuteを読んでTaskを終了します。
 また、処理を途中で打ち切るための仕込みもいれてあります。BlackBoardにAbortフラグが立っているかをチェックして、立っていたらFinishAbortします。この辺りについては後述します。

Attack

 攻撃アニメを再生して再生終わりを待つTaskです。アニメ再生終わりは、Controllerの方で検出してBlackBoardにフラグを書き込んでいます。Taskの中で直接再生終了をイベントをbindしても良いと思います。
 ダメージのやりとりはコリジョンイベントの方で行っています。

Task実装

 Taskは2つのイベントを実装します。

f:id:katze_7514:20160103191119j:plain
f:id:katze_7514:20160103191122j:plain

 ExecuteはActorのBeginPlayに相当するのもので、Taskが実行開始時に呼ばれます。BeginPlayと違うのは、Taskが実行開始する度に呼ばれることです。
 つまり、今回の例ですと、SimpleMoveTo→Attack→Wait と実行され、最初に戻り再びSimpleMoveToが実行される時にもまたExecuteが実行されます。

 Tickは、Task内でFinish系ノードが呼ばれるまで毎フレーム呼ばれます。

Taskの実行中断

※ DecoratorのObserveAbortsという項目を使うことで途中中断ができたみたいです。Decoratorの値が変化した時に接続されているノードを中断するかを選択することができます。

 今回、BT(というかTask)の実行を中断する必要があったのは、Playerの攻撃でノックバックしたり、HPが0になったら死亡エフェクトを表示したりする必要があるからでした。
 先にも書きました通り、BT自体に動作を止める手段が用意されてないため、実行中のTask以外の動作をさせるには一端Taskを止める必要があります。

 BTのデバッグ表示を見ると以下のようになっていたので、
f:id:katze_7514:20160103192338j:plain

 SequenceのIsEnableAIも毎フレーム実行されるのかと勝手に思っていたのですが、そんなことはなく、上図の場合はSimpleMoveToのTickだけが呼ばれている状態になります。
 Decoratorは実行"開始"条件判定を行うだけで、一度判定が通り先に進むとDecoratorは実行されなくなります。実行"継続"条件としては使用できません。
 よって、Taskの実行を中断させるには、根元にIsEnableAIを仕込むだけは足りないということになり、Task内に中断する仕組みを導入する必要があります。

 TaskのTick内で中断フラグをチェックする機構をいれておきます。
f:id:katze_7514:20160103194309j:plain
 IsAbordはBlackBoardに用意したAbordフラグを見にいっています。BlackBoardはBT内からでも外からでも操作できますので、どこからでもTaskの実行を中断できます。

UE4の入力取得の仕組みとプラグインの作り方

Unreal Engine 4 (UE4) 其の弐 Advent Calendar 2015」 3日目の記事になります。

qiita.com

UE4エディタ・BPなどが一切でません。C++のみのプログラマ向けの記事になります。

ここで示すソースコードは「4.10.0-release」タグのものになります。

UE4の入力取得

 確認しておきますと、ここでいう入力とは、ゲームパッド・キーボード・マウスといった入力デバイスのことを指しています。

 UE4でこの入力状態をチェックしイベントを発生させているのは2ヶ所あります。

 まず、ゲームパッド及び独自の入力デバイスの処理が以下の場所。

LaunchEngineLoop.cpp 2414~2415行目

2414: FSlateApplication& SlateApp = FSlateApplication::Get();
2415: SlateApp.PollGameDeviceState();

 2415行目のPollGameDeviceStateメソッドが、ゲームパッドと入力プラグインなどで指しこんだ独自入力デバイスのチェックを行いイベントを発生します。全ActorのTick前に一括して処理しています。
 FSlateApplicationクラスはプラットフォームを抽象化したクラスです。プラットフォームごとのApplicationクラスを内部でもっており、プラットフォームAPI処理を行っています。*1

 2つ目は、キーボードやマウスなどウインドウメッセージによる入力処理です。メッセージ系はチェックとイベント発生の2段階にわかれて処理されています。
 1段目、メッセージの確認が以下の場所。

LaunchEngineLoop.cpp 2391行目

2391: FPlatformMisc::PumpMessages(true);

 メッセージキューにたまっているメッセージを確認して、一端、メッセージバッファに情報を格納しています。
 その後、全ActorのTickが終わった後に、メッセージイベントを発生します。もちろん、すぐに処理すべきウインドウメッセージは、メッセージチェックの段階でも処理されていますが、入力系は遅延処理されます。
 その遅延処理しているのが、以下の場所。

LaunchEngineLoop.cpp 2460行目

2460: FSlateApplication::Get().Tick();

FSlateApplicationのTickメソッドの中で、ProcessDeferredEventsメソッドが呼ばれてメッセージによるイベントを発生させます。

 ざっくりですが、UE4の入力処理は上のようになっています。

 ちなみにLaunchEngineLoopは、いわゆるメインループの実装です。メインスレッドが何をやってるのかを見たい時は、LaunchEngineLoopをのぞくと良いと思います。

独自の入力デバイプラグインを作る

 以上を踏まえて、独自の入力デバイスを使うためのプラグインの作り方を紹介します。独自といってもオリジナルハードという意味ではなく、UE4が公式対応していない程度の意味です。Oculusタッチなどが該当します。*2
 プラグイン全般の作り方は公式ドキュメントなどを参考にしていただくとして、ここでは入力プラグインに特化した内容を紹介します。

*1:プラットフォームごとのApplictionクラスの名前は、F[プラットフォーム名]Appicationという名前になっています。Windowsだと、FWindowsApplicationクラスになります。

*2:まぁ、公式でプラグインが開発中なので公式対応と言っても良い気もしなくもないですが

続きを読む

UnrealEngine4用プラグイン DirectInputPadPluginの使い方

 UE4(Windows)でDirectInputゲームパッドを使えるようにするプラグインを作りました。

github.com

 DirectInputPadPlugin Ver.0.9のものです。
 これ以降、DirectInputゲームパッドのことをDIパッド(DIPad)、XInputのゲームパッドのことをXIパッド(XIPad)、両方合わせた場合はたんにゲームパッド(GamePad)と表記します。

インストール

  1. 上のGithubからソースをチェックアウト、もしくはZIPをDOWNLOADします。
  2. それらのソースを「DirectInputPadPlugin」という名前のフォルダを作って、そこに入れます
  3. 「DirectInputPadPlugin」フォルダを、[プロジェクトフォルダ]/Plugins に移動します

あとは、

katze.hatenablog.jp

を参考にソースをビルドしてください。

DirectInputゲームパッドの接続確認

 ゲームパッドをPCに挿した状態でUE4エディタを立ち上げます。動的な抜き差しには対応していませんので、エディタやゲーム起動前には挿しておいてください。
 接続が成功していれば、アウトプットログに

DirectInputPadPlugin: DirectInputDriver initialized.
DirectInputPadPlugin: DirectInputPad detected: 1
DirectInputPadPlugin: DirectInput Joystick Create Success. : Elecom Wired Gamepad

 見つかったDIパッドの数と、その数だけ接続が確認できたDIPadの名前が表示されます。
 これでDIパッドを使用する準備が完了です。

キーイベント

 GamePadイベントをそのまま使うことができます。

f:id:katze_7514:20151127232235j:plain

など。
 基本的には、GamePadイベントを使うことを想定しています。なぜなら、GamePadイベントはXIパッドのイベントでもありますので、ユーザーがXIパッドでもDIパッドでもどちらのゲームパッドを使っていても良くなるからです。

DIPadイベント

 DIパッドとして直接イベントを取ることもできます。こちらの場合だと、プラグインが対応してる最大限のキーイベントを取得することができます。

  • 6つの軸(XYZ軸、XYZ回転軸)
  • 1つのPOV
  • 32個のボタン(XIパッドは個数にすると12個まで)

 DIGamePadで始まる名前にしてあります。

f:id:katze_7514:20151127234617j:plain

 こちらは、DIパッドのボタン配置をXIパッドに合わせるキーコンフィグや、通常のゲームパッドではないDirectInputの入力デバイスを使う時に使います。

パッドキャリブレーション

 GamePadイベントをDIパッドでも使うには、DIパッドをXIパッドの配置にする必要があります。XIパッドというのは、ようはXBOXのパッドのことです。
 デフォルトでは以下のような対応になっています。

XIパッド DIパッド
左スティックX X軸
左スティックY Y軸
右スティックX Z軸
右スティックY Z回転軸
方向パッド上 POV上
方向パッド右 POV右
方向パッド下 POV下
方向パッド左 POV左
A ボタン1
B ボタン2
X ボタン3
Y ボタン4
LB(L1) ボタン5
RB(R1) ボタン6
Lトリガー(L2) ボタン7
Rトリガー(R2) ボタン8
BACK ボタン9
START ボタン10
左スティックボタン ボタン11
右スティックボタン ボタン12

 変更に使うノードは、SetKeyMapです。

f:id:katze_7514:20160827132842p:plain

 Ver.0.9からの機能として、軸にボタン、ボタンに軸を割り当てられるなど、キャリブレーションの自由度を上げました。
 図の左から

  • 「XIパッドの左スティックX軸として、DIパッドのX軸を割り当てる」
  • 「XIパッドの右スティックY軸(下方向)として、DIパッドのボタン1を割り当てる」
    • ボタン1が押されていないなら0,ボタン1が押されていると-1 が返るようになります
    • 上方向に割り当てる時は、Negativeフラグをオフにします
  • 「XIパッドのボタンAとして、DIパッドのX回転(正方向)を割り当てる」
    • X回転の正方向の値に応じて、ボタンAが押されているという判定がされます
    • X回転の負方向の値を使うならば、Negativeをオンにします

 Ver.0.8では対応できていなかったLT(L2)/RT(R2)への軸の割り当てもできるようになっています。

 SetKeyMapは重複してキーを割り当てられるのでご注意ください。KeyMapを削除するには、DIKeyの項目を「DIGamePad END」にすることでできます。

GetDirectInputPadJoystickノード

 PlayerIndexを使ってDIパッドを取得するノードです。DIパッドは見つかった順番にPlayerIndexが割り当てられて、対応するプレイヤーの入力として使われます。
 XIパッドが同時に挿しこまれていた場合は、XIパッドを優先します。DIパッドにはXIパッドの次のPlayerIndexが割り当てられます。つまり、XIパッドが挿しこまれてないないなら0番から、XIパッドが1つ挿しこまれていたら1番から割り当てられます。
 PlayerIndexに対応するDIパッドがなかった場合は、nullが返ります。

f:id:katze_7514:20160827133534p:plain

(回転)軸の反転

 DIパッドによっては、XIパッドと軸の値が逆になっていることがあります。XIパッドの左スティックYは上がプラスで下がマイナスになりますが、私が普段使っているDIパッドのY軸は上がマイナスで下がプラスになっています。
 このまま軸を割り当てると上下が反転した状態で処理されてしまいますので、軸の値を反転させる必要があります。その時は、SetAxisReverseノードを使います。

f:id:katze_7514:20151128012340j:plain

 引数上ではボタンも選べますが、当然意味はありません。
 反転フラグの値を調べるには、IsAxisReverseノードを使います。

ChangedState系ノード

 パッドキャリブレーションをサポートするノード群です。
 呼び出したフレームで状態に変化のあったキー情報を取得するノードです。
 状態の変化とは、軸なら初期値(通常は0)以外の入力値になっている、ボタンなら押されたか離されされたか、ということになります。

IsChangedKeyStateノード

f:id:katze_7514:20160827133734p:plain

 状態が変化したキーがあるかをチェックします。変化したキーがあればtrueが返ります。

Get(All)ChangedKeyStateノード

 
f:id:katze_7514:20160827134033p:plain

 GetChangedKeyStateノードは変化のあったキー1つだけ、GetAllChangedKeyStateは変化のあったキーすべて取得します。
 変化のあったDIパッドとしてのenumと変化値が取得できます。変化値は、軸の時は軸の値、ボタンの時は正だとPressed、負だとReleasedの意味になります。

 Real引数をtrueにすると、反転フラグなどキー設定を無視したパッドの実入力値で変化値を取得できます。

 GetChangedKeyStateは、通常は軸の変化を優先しますが、Btn引数をtrueにすることでボタンの変化を優先的に取得するようになります。

 ユーザーに「Aボタンの場所を押してください」などと促し、押されたボタンを検出(GetChangeKeyState)、そのボタンをAボタンとして設定する(SetKeyMap)、という使い方を想定しています。

キャリブレーションのセーブロード

f:id:katze_7514:20160827134219p:plain

 図のノードを使うことで、現在接続されているDIパッドに設定されたキャリブレーションデータ(KeyMap)をセーブロードすることができます。引数の意味は、SaveGameToSlot/LoadGameFromSlotと同じです。

ユーティリティー

ゲームパッドの数

 挿しこまれている各ゲームパッドの数は、次のノードで取得できます。
 取得できなかったの時は、-1が返ります。

f:id:katze_7514:20151128011543j:plain

ゲームパッドの名前と識別子

 ゲームパッドの名前はGetProductNameノード、識別子はGetGUIDノードで取得できます。

f:id:katze_7514:20151202181513j:plain

 名前はまったく同じパッドの場合、同じ名前になることがあるので識別には使えませんが、ユーザーがコントロールしているパッドを示すのには使えます。
 キーコンフィグ情報を保存/復元する際など、個々のパッドを識別するためにはGUIDを使用してください。ただし、このGUIDはローカルでのみ一意性が保証されてるGUIDですのでご注意ください。

入力値のクリア

 現在保持している入力値をクリアします。

f:id:katze_7514:20151213183116j:plain

プラグインが初期化できたか

f:id:katze_7514:20160220040421p:plain

コンフィグ

BACKGROUND設定にするか

DefaultInput.ini に以下のセクションを追加してください

[DirectInputPadPlugin]
Background=true

エディタ起動時は、常にBACKGROUND動作となります。

以上になります。

使用上の注意

 タイミングよっては、最初にロードされるLevelのBeginPlayにプラグインの初期化が間に合わないことがあります。その場合は、各APIが正しい値を返せません。プラグインの初期化を待ってからAPIを呼ぶようにしてください。

質問とか

 プラグインの使い方で、質問とかありましたら、この記事のコメントか、Twitterでリプを投げてください。
 バグがあったらプルリクお待ちしてます!

UnrealEngine4で2Dゲームを作ろう! その14 DirectInputプラグイン作った編

github.com

前回紹介したJoystickPluginは問題が発覚したために、結局自作しました。

ひとまず、

katze.hatenablog.jp

を参考にソースからビルドしてUE4に追加してください。
通常のゲームパッドイベントとして受け取れるようにしましたので、すぐに使えると思います。

とはいえ、ボタン配置がパッドごとに違うDirectInputゲームパッドを扱うにはもう少し手を入れる必要があるので開発は続けます。

詳しい使い方は、近いうちに記事にします。

JoystickPluginの問題

 あれからソースをさらに読み込んだ所、JoystickPluginは入力の取得をActorのTickタイミングで行っていることがわかりました。
 ちょっとこれは問題で、ゲームジャンルによっては致命的な問題となりかねません。

 ゲームパッドなどのデバイス入力は、全Actorが動く前に取得するのが望ましいです。
 UE4もその辺りの事情は当然わかっているので、独自入力デバイスをさしこんで、しかるべきタイミングで入力取得メソッドを呼んでくれる仕組み*1があります。それに沿った形で実装したプラグインとなっています。
 その辺りのソースを読んでいたら、キーイベント放出あたりの事情も理解できたので、通常のゲームパッドイベントと統合できたという感じです。

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パッドから削除する実装にしようとしてたみたいですがコメントアウトされてました