CommonUI の入力システムでは、クロスプラットフォームの入力サポート (特に複雑なメニューや多層化されたメニューのサポート) を管理します。このガイドでは、以下のトピックを含む CommonUI の入力システムの仕組みについて解説します。
-
合成カーソル を使ってナビゲーションを処理する。
-
ビューポートで入力をキャプチャする。
-
入力を受け取るウィジェットを入力ルーターが判断する仕組み。
-
個々のウィジェットが入力ルーティング プロセスに反応して影響を及ぼす仕組み。
-
ウィジェットが入力ルーティングに反応する方法を変更する。
プロジェクトで入力ルーティングのサポートを設定するには、「Common UI クイックスタート ガイド」で説明されている手順に従ってください。
合成カーソルを使ったゲームパッド ナビゲーション
CommonUI では、いくつかのゲームパッド インタラクションが不可視の合成カーソルによって実行されます。これは、マウスを使用するように UI を設定する際に、不可視のカーソルを正しい位置に配置して、マウスのようにクリックできるようにするだけで、CommonUI を正しく動作させることができることを意味します。この設定により、すべてのクロスプラットフォームの入力が 1 つの入力パスを通じて送信されることで、そのフローが簡素化されます。
このセクションでは、合成カーソルと基本的な入力の仕組みについて解説します。最初に、全般的な Unreal Motion Graphics (UMG) と スレート ベースの入力フローで、合成カーソルがクリックを登録する基本的な仕組みについて説明します。次に、そこから CommonUI の実装がどのように分岐されるかについて詳細を説明します。
合成カーソル/ゲームパッドを使ったクリック
このセクションでは、合成カーソルが機能する仕組みを紹介します。例として、合成カーソルではゲームパッドでの「Accept (承諾)」または「Default Click (デフォルト クリック)」アクションに入力フローを使用します。
通常、Virtual Accept キーは EKeys::Virtual_Accept にマッピングされます。内部では、GenericApplication から派生するプラットフォーム特有のアプリケーションから入力フローが始まります。たとえば、Windows では FWindowsApplication を使用します。
この入力は、FSlateApplication によって FSlateApplication::ProcessKeyDownEvent を使って処理されます。そのメソッドでは、その入力を受け取って、可能であれば 処理 する IInputProcessor インターフェースを実装する入力プロセッサを使用します。入力が処理されると、それ以上の処理は行われません。FCommonAnalogCursor は、その親クラスである FAnalogCursor と同様に IInputProcessor であり、FCommonAnalogCursor::HandleKeyDownEvent でキー押下入力の処理を試みます。
FCommonAnalogCursor では、現在のウィジェットで 結合アクション によってキャプチャされていないゲームパッド上の標準的な「Accept」アクションの入力は処理しません。代わりに、その入力が FAnalogCursor::HandleKeyDownEvent に送信されて、FAnalogCursor によって FSlateApplication で処理する合成マウス クリック イベントが作成されます。
この時点で、このマウス イベントは通常のクリックと同じような入力処理プロセスを経て、最終的なクリックをトリガーします。
この通常のクリック入力フローを調べる場合は、SButton:OnMouseButtonDown にブレークポイントを加えます。
合成カーソル クリックのトラブルシューティング
CommonUI の合成カーソル クリックが期待どおりに動作しない場合は、FPointerEvent のプロセスのいずれかが原因であることがほとんどです。
発生し得る問題には次のようなものがあります。
-
FPointerEvent が適切なユーザーの入力を処理しない。
-
合成カーソルが予定の位置付近に配置されない。
-
FSlateApplication::ProcessMouseButtonDownEventでクリックが処理される際に、キャプチャを含む別のウィジェットの影響がFWidgetPathに及ぶ。これは、使用している入力コンフィグのMouseCaptureModeに応じて発生する場合があります。 -
FWidgetPathが、位置に応じてFSlateApplication::LocateWindowUnderMouseを使って自然に生成される。
FWidgetPath には、入力が送信される可能性のあるウィジェットのリストが含まれます。
合成カーソルとゲームパッドによるナビゲーションとフォーカスの方法
ナビゲーションに限っては、CommonUI の動作は UMG のベース実装と変わりありませんが、利便上、このセクションではそのプロセスについて説明します。
前のセクションで述べたように、入力はプラットフォーム特有のアプリケーションから開始されます。このセクションでは、矢印キーまたはアナログ移動の例を使用します。入力ルーティング システムでは、この UI ナビゲーション入力を UI 内のウィジェットに送信します。
この例では、ナビゲーションが発生した際に、そのウィジェットがこの特定のナビゲーション入力の処理に特化されていないことを想定します。
ナビゲーション入力が発生すると、その入力がデフォルトの SWidget::OnKeyDown メソッドまたは SWidget::OnAnalogValueChanged メソッドによって処理されます。ただし、これらのデフォルト メソッドではウィジェットのフォーカスは直接変更されず、代わりに以下のフローが発生します。
-
ナビゲーション メソッドが
FSlateApplication::GetNavigationDirectionFromKeyまたはFSlateApplication::GetNavigationDirectionFromAnalogのいずれかを使用して、入力をナビゲーション方向に移動します。このメソッドでは、この移動を実行する際にウィジェットのナビゲーション コンフィグが考慮されます。 -
ナビゲーション方向がキャプチャされて、
FReply::SetNavigationを介して送信されるFReply::Handledリプライに含まれます。FReplyには膨大な量のコンテキスト情報を保持できます。FReply の詳細については、後述の「CommonUI 入力ルーティング」セクションを参照してください。 -
スレートが
FSlateApplication::ProcessReplyを使ってFReplyの処理を開始します。これによってナビゲーションが発生します。ナビゲーション イベントが方向によって緩やかに定義されている場合は、_FSlateApplication::AttemptNavigation_ がナビゲート先の適切なウィジェットの検索を試みます。 -
可能であれば、
FSlateApplication::ExecuteNavigationが目的のウィジェットにナビゲートします。 -
目的のウィジェットが有効であれば、そのウィジェットで
FSlateApplication::SetUserFocusが呼び出されます。これは、目的のウィジェットが直接指定されたか、事前に見つかっていたかにかかわらず実行されます。 -
スレートのフォーカス ナビゲーションが発生すると、次のティック時に、
FCommonAnalogCursor::Tickが自動的に 合成カーソルをフォーカス ウィジェットに移動して中央に揃えます。
これにより、ゲームパッドを使う際にホバー エフェクトを使用できるようになります。
CommonUI で合成カーソルの動作をカスタマイズする
CommonUI では、独自のアナログ カーソルまたは合成カーソルを使用できます。こうすべき理由は数多くあります。たとえば、ゲームパッドと同様に機能するキーボード ナビゲーションを作成しようとしている場合は、ゲームパッドを使用していないときでもウィジェットの中央にスナップされるように FCommonAnalogCursor::Tick を設定することができます。また、その代替方法として、合成マウスを可視にして、フォーカスの変更間にトゥイーンのようなものを実装することも可能です。
カスタム仕様のカーソルを作成するには、次のステップを実行します。
-
UCommonUIActionRouterBaseから派生させます。 -
カスタム仕様のアナログ カーソル クラスを返すように
UCommonUIActionRouterBase::MakeAnalogCursorをオーバーライドします。
UCommonUIActionRouterBase::ShouldCreateSubsystem を独自のアクション ルーター クラスでオーバーライドした場合、それによって自身のインスタンスは作成されません。
入力プロセッサはすべての入力で実行される ことに注意してください。これには Unreal Editor の入力も含まれます。FCommonAnalogCursor::IsGameViewportInFocusPathWithoutCapture を使用することで、アプリケーションとそれ以外のものを区別しやすくなります。
CommonUI 入力ルーティング
次は、入力ルーティング プロセスの大まかな概要です。
-
CommonUI により、アクティベート可能なウィジェットがナビゲーションを処理するノード ツリーにそれぞれ整理されます。無効なウィジェットは一連のノードには追加されないため、これらが入力ルーティングの対象になることはありません。
-
CommonGameViewportClient が入力をキャプチャして、階層の最も上にあるノードを見つけます。
-
このノードが、利用可能な入力ハンドラのいずれかを使って入力を処理できるかどうかをチェックします。プレイヤーの入力に一致する入力ハンドラがない場合は、このノードが最も上にあるその子ノードに入力を送信して、このステップを繰り返します。
このプロセスは、すべてのノードがチェックされるまで再帰的に繰り返されます。以下のセクションでは、これらのステップとこのシステムのコンポーネントについて、より詳しく説明します。
入力ルーティング実行フロー
「合成カーソル/ゲームパッドを使ったクリック」セクションで述べたとおり、入力プロセッサは、入力ルーティングが発生する前に入力イベントを処理するための最初の機会を得ます。入力プロセッサで処理されない入力イベントには、CommonUI の入力ルーティング フローが適用されます。
クリック イベント プロセス
入力は、プラットフォーム特有のアプリケーションによって FSlateApplication::ProcessKeyDownEvent がトリガーされた際に開始されます。
次に、スレート アプリケーションにより、現在のフォーカス パスに基づいて入力イベントがウィジェットに送信されます。通常、ゲーム内でこの入力イベントは SViewport::OnKeyDown で、これによってキー押下が現在のビューポート インターフェースを実装するクラスに送信されます。キー押下を送信することで、通常は FSceneViewport::OnKeyDown がトリガーされます。
最後に、シーン ビューポートによって入力が現在のゲーム ビューポート クライアントの InputKey メソッドに送信されます。CommonUI の場合は UCommonGameViewportClient::InputKey です。これが、CommonUI を正しく機能させるためには、ゲーム ビューポート クラスを CommonViewportClient に設定する必要がある理由です。
ビューポート クライアント クラスの設定に関する詳細については、「Common UI クイックスタート ガイド」を参照してください。
アクション ルーター プロセス
入力がゲーム ビューポート クライアントに送信されると、CommonUI の入力ルーティング特有の実装が開始されます。UCommonGameViewportClient::InputKey により、**アクション ルーター** に UCommonGameViewportClient::HandleRerouteInput で入力を処理する機会が与えられます。これが成功すると、次に UCommonUIActionRouterBase::ProcessInput を呼び出します。
入力が処理されない場合は、共通ゲーム ビューポート クライアントによって Super::InputKey が呼び出される際に、ゲームが通常のメソッドを介して入力を処理しようと試みます。
アクティベート可能なウィジェットは、FActivatableRootNode を使ってツリー内のノードとして抽象化されます。それぞれのノードは、UI のウィジェット ツリーにおけるアクティベート可能なウィジェットの階層に基づいて階層に分けられます。親ノードは ルート ノード として動作し、その子供は 子ノード または リーフ ノード として扱われます。詳細については、「CommonUI の概要」を参照してください。
アクション ルーターは、アクティベート可能なウィジェットのツリー内で現在有効なルート ノードを維持します。UCommonUIActionRouterBase::ProcessInput は、FActivatableTreeNode::ProcessNormalInput を呼び出して入力の処理を試みるようルート ノードに指示します。**これは、すべての子ノードに入力の処理を試みるよう再帰的に指示します。
FActivatableTreeNode は FActionRouterBindingCollection であり、ノードのアクティベート可能なウィジェットのすべてのアクション バインディングのリストを維持します。現在のノード内のすべての子ノードが入力の処理に失敗した場合は、現在のノードで FActionRouterBindingCollection::ProcessNormalInput が呼び出されます。現在のノードでは、すべてのウィジェットのアクション バインディングが、バインディング コレクションとしてチェックされます。アクション バインディングが対応するキーと一致する場合は、その関連する動作が実行されて、入力が「処理済み」として扱われます。
入力ルーティング システムを変更する
次は、入力ルーティング システムを変更する場合に推奨される主要な方法です。
-
新しいビューポート クラスを
UCommonGameViewportClientから派生して、そのあらゆる入力処理メソッドをオーバーライドします。次に、[Project Settings (プロジェクト設定)] でゲーム ビューポートをその派生クラスに設定します。 -
新しいクラスを
UCommonUIActionRouterBaseから派生して、そのあらゆる仮想関数をオーバーライドします。たとえば、UCommonUIActionRouterBase::ApplyUIInputConfigをカスタム仕様の 入力コンフィグ の設定でオーバーライドできます。
UCommonUIActionRouterBase::ShouldCreateSubsystem を独自のアクション ルーター クラスでオーバーライドした場合、それによってベース アクション ルーターのインスタンスは作成されません。