UINavitagion入門

f:id:nca03132:20200427133029p:plain

UINavitagionとは

UINavigationはマウス、キーボード、ゲームパッド、またはそれらの任意の組み合わせで操作できるUMGメニューを構築するためのフレームワークで、なんとオープンソースで無料でマーケットプレイスで入手できます!

自分でイチからUMGのゲームパッド、マウス・キーボード両対応を行おうとすると結構大変なので、その辺を楽に共通化したいと思ってる方には非常に有用なプラグインです。

ただ、通常のUMGにはないNavigationGridという概念をベースに機能拡張がなされているため、使いこなすにはよく仕様を理解しておく必要があります。

本家チュートリアル

UI Navigation Tutorials - YouTube

こちらになっており一通り見ておけば使えるようになるのですが、英語動画なのでちょっとハードルが高いかも……ということで自分への備忘録も兼ねて記事として残しておきます。

プラグインの有効化

なにはなくとも、まずプラグインを有効化しましょう。

対象となるプロジェクトで"編集">"プラグイン"と選んで

f:id:nca03132:20200426125750p:plain

UINavigationにチェックを入れて有効化しエディタを再起動します。

f:id:nca03132:20200426125850p:plain

 

インプットアクションの設定

まずはUINavigationで使うインプットアクションをプロジェクトに設定する必要があります。UIで設定するのは大変なので、プラグインのinput設定をプロジェクトにコピペしましょう。コンフィグを書き換えるためUE4エディタはいったん終了しておいてください。

UnrealEngineをインストールしたディレクトリ配下の"Plugins">"Marketplace">"UINavigation">"Content"に"UINavInput.ini"というファイルがあるのでテキストエディタなどで開きます。

f:id:nca03132:20200426130435p:plain

ファイル中でActionMappingsを設定している部分をコピーし

f:id:nca03132:20200426130738p:plain

対象となるプロジェクトのConfig/DefaultInput.iniを開いて、ActionMapping設定している部分にペーストします。

f:id:nca03132:20200426131136p:plain

これでアクションの登録は終了です。

ウィジェットの作成

UINavigationではウィジェットはUserWidgetではなくUINavWidgetを親クラスにする必要があります。

コンテンツブラウザ上で普通にウィジェットを作ったあとで、"グラフ"、"クラス設定"をクリックし、クラスオプションで親クラスをUINavWidgetに変更します。

f:id:nca03132:20200426134723p:plain

UINavWidgetはUserWidgetの派生クラスで同等の機能を持っているため、このリペアレントは問題なく実行できるはずです。

またゲームパッドやキーボードによるボタンの制御を有効化するために"クラスのデフォルト"をクリックし詳細の"UINavWidget"の中にある"User Button States"にチェックを入れてください(これを忘れるとキーボードやゲームパッドでボタンを操作できないので注意)。

f:id:nca03132:20200426140054p:plain

"Use Text Color"は必要に応じてチェックしてください。この機能は、キーボードやパッドで現在選択されているボタンの文字色を変更します。文字色は"UINavitgationText"の項目で変更することができます。

今回の解説ではカーソル移動をわかりやすくするためにUseTextCursorはチェックした状態で進めていきます。

 ウィジェットへのボタン配置

UINavigationではボタンは通常のButtonではなく、UINavButtonを使用します。

CanvasPanel上に普通においてもいいでし、VerticalBox,HorizontalBoxなどのパネルに入れても大丈夫です。

f:id:nca03132:20200426141208p:plain

これはCanvasPanel上にVerticalBoxを置いてUINavButtonを3つ配置した例です。

UINavButtonはIsFocusableのチェックを外したままにしておいてください(UINavigatonによるセレクション移動と標準のNavigationによるキーボードフォーカス移動が混在して動いてしまうため) 

NavigationGridの作成

次にUINavigationのキモであるNavigationGridを作成します。

NavigationGridとは

キーボードやゲームパッドでUMGを操作する時は上下左右のカーソル入力で選択を移動させますが、NavigationGridというのはこの移動範囲を規定するものになります。

例えばVerticalBoxで縦方向に3個ボタンを並べたときは、縦方向のNavigationGridを生成し中にその3ボタンの参照を入れておきます。

GridPanelにボタンが入っている場合は縦横方向にカーソルが移動するので、2次元のNavigationGridを生成し中にそのボタンの参照を入れておきます。

ゲーム実行時、UINavigationはカーソルをこのNavigationGrid内で移動させます。

NavigationGridを作成する

NavigationGridはウィジェットのグラフ上でReadyForSetupイベントからAppendNavigationGrid1D、AppendNavigationGrid2Dいずれかのノードを呼び出すことにより作成します(これ以外のイベントからは呼び出さないように)。

上記の2つのノードは内部的にNavigationGridを生成してウィジェットに配置されたボタンを階層の上から順に引数で指定された数だけグリッドにいれます。

 AppendNavigationGrid1D

ボタンが縦方向、もしくは横方向に一列に並んでいるときに使います。GridTypeはレイアウトに応じてHorizontalかVerticalから選択してください(Grid2Dを指定するとエラーになる)。

引数のDimensionはNavigationGridに挿入するボタンの数を指定します。

f:id:nca03132:20200427133358p:plain

このようにVerticalBoxにボタンを配置した場合は

f:id:nca03132:20200426151218p:plain

このようにノードを組んでVerticalBoxに積まれたボタンをすべていれます。

AppendNavigationGrid2D

UniformGridPanelのように縦横両方にカーソル移動できる配置で使われます。横と縦の次元数をDimensionX,DimensionYで指定します。

グリッド内のボタンの数がDimensionX×DimensinYより少ない場合、Buttons in Gridにボタンの数を直接設定して調整してください(ピッタリの場合は-1のままでよい)。

f:id:nca03132:20200426153300p:plain

UniformPanelでこのように配置した場合は

f:id:nca03132:20200426153326p:plain

このようにDimensionXを3,DimensionYを2にします。

この例ではボタンは1個足りないのでButtonInGridで5に指定する必要があります。

プレイヤーコントローラーの設定

プレイヤーコントローラーの作成

UI NavigationではプレイヤーコントローラーをUINavControllerを派生したものにするか、コンポーネントにUINavPCを追加し更にインターフェースとしてUINavPCReceiverを継承させたものに変更する必要があります。

派生させる場合は、プレイヤーコントローラーのBPを開いて"クラス設定"をクリックし、詳細で親クラスを"UINavController"を選択します。

 f:id:nca03132:20200426173209p:plain

コンポーネントを追加する場合、プレイヤーコントローラーに"UNavPC"コンポーネントを追加し、"クラス設定"をクリックし、実装インターフェースで"追加"を押して"UINav PCReceiver"を追加します。

f:id:nca03132:20200426173652p:plain

プレイヤーコントローラーを作成したら、それが使われるようにゲームモードのプレイヤーコントローラー指定を書き換えておいてください。

f:id:nca03132:20200426174857p:plain

動作確認

ここで実際に動作確認してみます。

今回の例ではサードパーソンのテンプレートをベースにしています。

プラグインの有効化、インプットアクションの設定を終えた後

この4つの手順を踏んでください。

今回はWBP_Sampleというウィジェットを作成しVerticalBoxに3つのUINavButtonを配置し、AppendNavigationGrid1DでVerticalにNavigationGridを生成した場合の動作を見ていきます。

ウィジェットの見た目はこんな感じで

f:id:nca03132:20200427081718p:plain

グラフは

f:id:nca03132:20200427081851p:plain

こうなります。

次に先ほどUINavControllerを派生して作ったプレイヤーコントローラーにウィジェットを表示するノードを追記していきます。

シンプルに表示するだけなのでBeginPlayにウィジェットの作成、ビューポートへの追加、マウスカーソル表示ONとつなぎます。

f:id:nca03132:20200427082305p:plain

さらに、プレイヤーの移動、視点回転の動きを抑制するノードを追加。

f:id:nca03132:20200427082627p:plain

最後に入力モードをGameAndUIに設定します。

f:id:nca03132:20200427082653p:plain

InputModeはUIOnlyでも動くことは動くのですが、UMGが表示されてない個所をマウスクリックしてしまうとウィジェット選択が切れてゲームパッドやキーボードでUMGが操作できなくなってしまうためお勧めしません(一応、マウスで再度UMGのどこかをクリックすると復帰します)。

またSetIgnoreMoveInput、SetIgnoreLookInputでキャラや視点の移動を防いではいますが、実際のゲームではこのほかにも攻撃ボタン、アイテム使用ボタンなど多数の入力が存在すると思います。

もしメニューを開いている間はそういった入力を受け付けないようにしたい場合は、メニューを開いているフラグなどを用意して自前でプレイヤーがアクションしないように制御してあげる必要がある点に注意してください。 

これで設定は完了です。ちゃんと設定できていれば

f:id:nca03132:20200427084049p:plain

 こんな感じの画面になるかと思います。マウスでボタンを直接クリックすることもできますしキーボード(ADSW)やゲームパッドでカーソルを移動、決定させることも可能です。

複数のNavigationGrid

NavigationGridを複数生成する

先ほどの例ではNavigationGridを1つだけ生成しましたが、当然複数生成することもできます。VerticalGridのほかに3x2のUniformGridPanelを追加しボタンを5個追加してみます。

f:id:nca03132:20200427090319p:plain

これでパネルは追加されましたが、NavigationGridはVerticalBoxの分しか作っていません。UniformedGrid用にもNavigationGridが必要なのでAppendNavigationGrid2Dで追加します(UniformedGridは上下左右にカーソルが動くので)。

ウィジェットのグラフは

f:id:nca03132:20200427091211p:plain

こんな感じになります。これでNavigationGridが2つになりました。

ここで補足ですがAppendNavigationGrid系ノードはウィジェットの階層上のボタンを上から順にNavigationGridに追加していきます。今回の例では

f:id:nca03132:20200427092153p:plain

という順にボタンが並んでいるので、最初のAppendNavigationGrid1DでVerticalBoxに入っている3個のボタン、AddNavigationGrid2DでUniformGridPanelに入っている5個のボタンを各自生成したNavigationGridに追加します。この順序が逆になるとパネルの構成とNavigationGridの構成がズレてしまうので注意してください。

この状態で実行してみると

f:id:nca03132:20200427091741p:plain

ゲームパッドやキーボードでカーソルを動かすとVerticalBox内はカーソル移動できますがUniformGridPanel側にはカーソル移動しません。

UniformGridPanel側のボタンをマウスでクリックすると今度はUniformGridPanel内はカーソル移動できるようになりますが、やはりVerticalBox側にカーソルを戻すことはできません。

そもそもカーソルは1つのNavigationGrid内で動くように設計されているので正しい動作ではあるのですが、このままではちょっと困ります。そこで次で紹介するEdgeNavigationを使用します。

EdgeNavigation

NavigationGridにはカーソルをGridの外に動かそうとしたときに、指定したボタンに強制的にカーソルをジャンプさせるEdgeNavigationという設定があります。とび先には別のNavigationGridのボタンも指定できます。

EdgeNavitagionはAppendNavigationGridの引数として与えることができます。

f:id:nca03132:20200427124213p:plain

EdgeNavigationのピンをドラッグ&ドロップして"作成"と入力すると"ButtonNavitagionを作成"という候補が出てくるので選択します。

EdgeNavigation構造体を作成するノードが出来るので、カーソルがVerticalBoxのNavigationGridから外に出ようとしたとき、上下左右どの方向に出た時にどのボタンにジャンプするかを設定します。

f:id:nca03132:20200427125225p:plain

今回の場合、カーソルがVerticalBoxのNavigationGridから右方向へ出ようとしたらボタン4へ、UniformGridPanelのNavigationGridから左方向へ出ようとしたらボタン1へジャンプするようにします。f:id:nca03132:20200427130152p:plain

コンパイルして実行してみましょう。マウスを使わなくてもVerticalBoxとUniformGridPanelでカーソルが行き来できるようになっているかと思います。

NavigationGridの仕様

NavigationGridは単純にボタンのリストを保持していて、その中で指定されたグリッド形状に合わせてカーソルを動かしているだけなので、UMG上のパネルやグリッドのレイアウト・親子関係とは無関係に動作します。例えば上記の例でNavigationGridを最初のVerticalBoxの1つしか作成せずDimensionに8を設定するとVerticalBox内の3ボタン、UniformGridPanel内の5ボタンすべてが縦に並んでいるとみなして上下入力でカーソルが動きます。

f:id:nca03132:20200427131258p:plain

が、そんなことをすれば当然操作系は混乱するので、極力レイアウト上の配置と操作系はマッチするように運用したほうがよいでしょう。

またUINavigationは配置したボタンの数とNavigationGridに挿入したボタンの数が違うとエラーを出すので、きちっとボタンの配置数は把握してNavigationGridを作成してください。

MenuSelector

MenuSelectorはカーソルがあるボタンに対して表示するマーカー的なものです。

例えば選択中のボタンの左横に">"マークを出したり、縁取りで飾りを表示したりする場合に使います。

MenuSelectorは通常のウィジェットとして作成します。UINavWidgetではなく普通のUserWidgetで構いません。今回はサンプルに入っているMenuSelectorを使います。

UMGのデザイナー上でキャンバスにMenuSelectorを配置し、名称を"TheSelector"に変更します。("TheSelector"への名称変更はUINavigationの仕様です)

デフォルトでは"<"という形状になっているのでレンダートランスフォームのScaleで-1を指定して左右反転し、適当なボタンの左横に配置して大きさを変更しながらサイズ感を確認します。

f:id:nca03132:20200427143714p:plain

次にMenuSelectorをボタンの左側に表示されるように設定を行います。

"グラフ"をクリックし、"クラスのデフォルト"を選択して"UINavigationSelector"の中の"SelectorPositioning"を"Left"に指定し、にSelectorOffsetのxを-50を設定します(これはMenuSelectorのサイズに合わせて変更してください)。

f:id:nca03132:20200427144338p:plain

これで選択中のボタンの左に">"マークが表示されるようになるはずです。

f:id:nca03132:20200427144618p:plain

基本的な使い方は以上です。

あとは表示するMenuSelectorを変更したり、表示位置、オフセットなどを調整するなど自由にカスタマイズしてください。

ボタンにアニメーションをつける

UINavWidgetは配置中の各ボタンに対しカーソルが移動してきたときに再生するUMGアニメーションを"UINavAnimations"という配列で保持しています。

初期状態でこの配列は空ですが、ウィジェットのPreConstructイベントで各ボタンごとにUMGアニメーションを設定しておけばボタンにカーソルが移動してきたときに対応するアニメーションを自動的に再生してくれます。

先ほどのVerticalBoxに3ボタン、UniformGridPanelに5ボタン設置してある例だと、各ボタンごとに計8個のアニメーションを用意し、PreConstructイベントで"UINavAnimations"配列に設定します。

f:id:nca03132:20200427154007p:plain

これでボタンにカーソルが移った時に各アニメーションが再生されます。

本家動画では

https://www.youtube.com/watch?v=lZjR2ogcBt8&feature=youtu.be&list=PLAcWSem_HT4hT-3viMU2_z4WW94gKhgn0&t=80

この辺の操作を参考にするとよいでしょう。(動画ではボタンは6個です)

UINavComponents

ここまでの記事ではカーソル移動といえばボタン(UINavButton)に対するものとして話を進めてきましたが、実はUINavigationにおいてカーソル移動できるパーツはUINavComponentというクラスが基本になっています。

UINavButtonはこの派生クラス(一種)で、ほかにもカーソル移動を受け付けられるパーツがいくつか存在します。

コンテンツブラウザの表示オプションで"エンジンのコンテンツを表示"、"プラグインコンテンツを表示"にチェックを入れ、プラグインのフォルダを表示状態にすると

f:id:nca03132:20200427162107p:plain

UINavigationコンテンツ>Examples>Components

の中を見ることができるようになります。

この中には各種UINavComponentの実装サンプルが入ってます。

f:id:nca03132:20200427162457p:plain

これらのパーツもキーボードやゲームパッドでのカーソル移動を受けることができるものです。

この実装サンプルをそのままUMGデザイナーに配置して使ってもいいですし、プロジェクト側にコピーしてデザインを変更したり実装をカスタムして使うこともできます。

自分で実装を書く場合、UINavComponentはOnNavigatedToとOnNavigatedFromというイベントをオーバーライドできるので、ナビゲートを受けた時・出ていく時の処理を書き加えることができます。

以下、用意されているUINavComponentをいくつかを紹介します。

OptionBox(クラス名:UINavOptionBox)

f:id:nca03132:20200427172343p:plain

f:id:nca03132:20200427172539p:plain

ボタンの中にテキストと左右ボタンがついていて、中のテキストや数値変更が可能です。

数値を切り替える場合、UserNumberRangeにチェックを入れMinRange,MaxRange,Intervalにそれぞれ数値の最小値、最大値、左右ボタンを押したときの増減幅を設定します。

f:id:nca03132:20200427172658p:plain

テキストを切り替える場合は、UserNumberRangeのチェックを外してStringOptionsに切り替える文字列を追加します。

f:id:nca03132:20200427172832p:plain

どちらの場合もUINavComponentBox>LoopOptionsにチェックを入れると、最大値、最小値まで行ったときに値がループするようになります。

OptionBoxはGridTypeがHorizontalのNavigationGridに入れると、カーソルがOptionBoxに移ったあと左右キーが数値・文字列の切り替えに食われてカーソル移動できなくなります。VerticalなGridに入れて運用したほうがよいでしょう。

OptionBoxの左右ボタンやテキストのデザインを変更したい場合は、自分のプロジェクトにOptionBoxをコピーするか、UINavOptionBoxの子クラスを作ってカスタマイズする必要があります。

SliderBox(クラス名:UINavSliderBox)

f:id:nca03132:20200427173850p:plain

OptionBoxと似ていますが数値しか指定できません。その代わりスライダーバーが表示されます(クリックしての値変更はできない)。

プロパティやカスタム方法についてはOptionBoxの数値指定したときと同じです。

Slider(クラス名:UINavSlider)

f:id:nca03132:20200427174214p:plain

いわゆるスライダーバーですが、スピンボックスがついてます。

プロパティやカスタムの方法はSliderBoxとほぼ同じです。

こちらはマウス操作の時バーをクリックすることで数値を変更することができます。

配置初期状態ではHandleHoverColor(バー上に表示される現在の値を示すハンドル)、BarHoverColor(バーの色)のアルファ値が0でバーが表示されないので注意してください(色指定してください)。

InputContainer

f:id:nca03132:20200427234109p:plain

InputContainerはゲームパッドやキーボードのコンフィグを表示・設定・保存するための特殊なUINavComponentです。これを使うと簡単にキー・パッドコンフィグ画面が作れます。

データテーブルの設定

InputContainerはその機能上、画面にゲームパッド、キーボード、マウスの各種操作(Aボタン、スペースキー、マウス左クリックなど)をアイコンや名称で表示します。そのためゲームパッド・キーボード・マウスの各操作に対して、どのアイコン、どのテキストを表示するかを規定したデータテーブルが必要となります。

UINavigationにはこのデータテーブルのサンプルが用意されているのでそれを使えば新規に用意する必要はありません。カスタムする場合もこのサンプルをコピーして書き換える形になります。
※サンプルはコンテンツブラウザの表示オプションで"エンジンのコンテンツを表示","プラグインコンテンツを表示"の2つにチェックを入れた状態でないと表示されないことに注意してください。

では実際にデータテーブルを設定してみましょう。

プレイヤーコントローラーのUINavPCコンポーネントの詳細で設定を行います。UINavControllerカテゴリに以下のようにデータテーブルの設定が並んでいるので以下に従って指定してください。

f:id:nca03132:20200427192131p:plain

  • GamepadKeyIconData
    ゲームパッド操作と対応するアイコンファイルが記述されているデータテーブルです。
    テーブルで使われている構造体はInputIconMappingです。
    標準でXBoxIconTableというデータテーブルが提供されています。
  • KeyboardMouseKeyIconData
    キーボード・マウス操作とアイコンファイルが記述されているデータテーブルです。
    テーブルで使われている構造体はInputIconMappingです。
    標準でKeyboardMouseIconTableというデータテーブルが提供されています。
  • GamepadKeyNameData
    ゲームパッド操作と対応する名称が記述されているデータテーブルです。
    テーブルで使われている構造体はInputNameMappingです。
    標準でXBoxNameTableというデータテーブルが提供されています。
  • KeyboardMouseKeyNameData
    キーボード・マウス操作と対応する名称が記述されているデータテーブルです。
    テーブルで使われている構造体はInputNameMappingです。
    標準でKeyboardMouseNameTableというデータテーブルが提供されています。

これらを設定すると

f:id:nca03132:20200427192417p:plain

このようになります。

補足として、アイコンやキー名称を独自に書き換える方法も紹介しておきます。

基本的にはサンプルのデータテーブルを自分のプロジェクトにコピーして必要に応じて書き換えてください。

データテーブルはキー(RowName)がUE4のエンジンで規定されている操作名になっていて、データ列はアイコンテーブルの場合アイコンファイル、ネームテーブルの場合TEXT型の操作名になっています。

これはゲームパッドのアイコンテーブルの一部です。

f:id:nca03132:20200427183937p:plain

Rowには操作名、InputIconにはアイコンファイルのパスが入っています。

もしアイコンを変更したい場合はInputIcon列のアイコンファイルを変更してください。
こっちはゲームパッドのネームテーブルです。

f:id:nca03132:20200427184126p:plain

一行も入ってないですね。実はこれには理由があります。

各操作を画面に表示する際はアイコンデータテーブルのほうが優先的に使用されるので、そちらですべて対応できていればネームテーブルは参照されません。そのためこちらは空になっています。もし特定の操作に対してアイコンではなく名称で表記したい場合はアイコンテーブル側の行を削除し、ネームテーブル側に該当する操作について追記します。

アイコンテーブルもネームテーブルもヒットしなかった場合はエンジンで規定されているディスプレイネームが表示されます。

 プロジェクト側にデータテーブルをコピーして書き換えた場合はUINavPCコンポーネントのデータテーブル指定も書き換えることを忘れないようにしてください。

InputContainerの配置

ではUINavWidget内にInputContainerを配置してみましょう。

今回は単純にCanvasPanel上に直接置いてみます。

f:id:nca03132:20200427222908p:plain

InputContainer詳細で以下のプロパティを設定します。

f:id:nca03132:20200427223217p:plain

  • InputNames
    ここにはキーコンフィグしたいアクションマッピング名を追加していきます(プロジェクト設定のインプットに記述してあるアクションマッピング(Jumpなど))。
    複数指定可能で、その数だけInputContainerの行が増えます。
    今回は以下のような感じでJumpとAttackというアクションを指定してみます。

    f:id:nca03132:20200427223623p:plain

  • InputRistrictions
    InputContainerは各アクションに割り当てるキーを最大3個まで列に表示して確認・設定できる機能を持っています。
    InputRistrictionsではその列ごとに、操作に関して制限なし(None)、キーボード(Keyboard)のみ、マウスのみ(Mouse)、キーボード&マウスのみ(Keyboard and Mouse)、ゲームパッドのみ(Gamepad)の制限を設定することができます。
    今回は1列目はキーボードのみ、2列目はゲームパッドのみのキー割り当てを表示・変更できるようにします。

    f:id:nca03132:20200427233836p:plain

  • EmptyKeyText
    アクションに対して操作が何もバインドされてないときに表示するテキストを指定します。デフォルトではUnboundになっているので必要に応じて変更してください。

    f:id:nca03132:20200427233913p:plain

  • PressKeyText
    操作変更ボタンを押して入力待ちの間に表示されるテキストなので適切なテキストを設定してください。デフォルトではPressAnyKeyとなっています。

NavigationGridの生成

次にウィジェットのグラフでNavigationGridを生成します。

InputContainerはそれ単体でNavigationGrid2Dを1つ占有するのでReadyForSetupイベントからAppendNavigationGrid2Dを呼び出して2D上のグリッドを作成します。

DimensionXには各アクションに対して設定可能な操作の数を指定します。

これはInputContainer>KeysPerInputで取得することができます。

DimensionYには設定可能なアクション数を指定します。

これはInputContainer>NumberOfInputsで取得することができます。

ノードとしては

f:id:nca03132:20200427234637p:plain

のようになります。

動作検証

では実際にプレイしてみます。

画面上には

f:id:nca03132:20200427234849p:plain

このようにJump、Attackに割り当てられているキー操作、パッド操作が表示されます。

ゲームパッドやADSWでカーソル移動が可能で決定ボタンを押すとアクション変更状態となり入力待ちに入り、次に押された操作がそのアクションに割り当てられます。

設定の変更を行うと自動的に保存され次回以降のゲーム起動でも有効となることに注意してください。

キーバインドのリセット

以上で説明は終わりなのですが、ゲームパッドやキーボードのコンフィグは時としてリセットしたくなることがあります。

そんなときはResetInputSettingsノードを使えばすべてのコンフィグはリセットされます。

UINavCollections

UINavWidgetはネスト出来ないという大きな制限があり、別で作成したUINavWidgetを子として貼り付けようとするとエラーになってしまいます。

ネストしなければUINavWidgetを同時に複数ビューポートにアタッチすることはできますが、その場合カーソルは常に最後にAddViewportしたウィジェットに行ってしまい、手前のウィジェットに移動させることができません(ボタンをマウスで直接クリックすることは可能)。

UINavigationはUINavWidget1つですべてのナビゲーションをコントロールする設計で作られているので当然といえば当然なのですが、子ウィジェットが使えないというのは非常に使い勝手が悪いです。

そこで登場するのがUINavCollectionです。

UINavCollectionは普通のウィジェットのように様々なパーツを自由に設置でき、かつUINavWidgetの子に入れることができます。UINavCollection内にUINavigationをネストで入れることも可能です。

UINavCollectionを作成する

実際にUINavCollectionを作ってみます。

通常のウィジェットと同じようにユーザーインターフェースを作成したあと、親クラスをUINavCollectionに変更します。

"グラフ"をクリックし"クラス設定"を選択し、クラスオプションで"UINavCollection"を指定してください。

f:id:nca03132:20200428081050p:plain

あとは普通のウィジェットと同じように各種パーツを自由にレイアウトします。

今回はUniformGridPanelを下地にしてボタンを6個を配置してみます。

f:id:nca03132:20200428082004p:plain

NavigationGridを作成する

UINavCollectionでも配置したパーツに対してNavigationGridを作る必要があります。

UINavWidgetではReadyForSetupイベントでNavigationGridを生成していましたがUINavCollectionではSetupNavigationイベントを使います。

今回の例では3x2のUniformGridPanelなのでAppendNavigationGrid2Dで以下のようにノードをつないでグリッドを作ります。

f:id:nca03132:20200428091532p:plain

UINavWidgetに配置する

次にUINavWidgetに上記で作ったUINavCollectionを配置します。

左右に2つほど並べてみます。

f:id:nca03132:20200428100312p:plain

UINavWidgetでNavigationGridを作成する

UINavWidgetにボタンなどのUINavComponentを配置した場合はAppendNavigationGridでNavigationGridを生成しますが、UINavCollectionを配置した場合はAppendCollectionというノードを使います。

AppendCollectionノードは内部で配置されたUINavCollectionに対してSetupNavigationを呼び出すので、そこで子ウィジェット側のNavigationGridが作成される仕組みになっています。(UINavWidgetは配置されたUINavCollectionの内部構成について感知しないのでNavigationGridの生成をUINavCollectionに任せるのは至極正しい設計です)

今回の例ではUINavCollectionを2つ配置しているので

f:id:nca03132:20200428102048p:plain

こんな感じでAppendCollectionを2回呼び出します。

引数のEdge Navigationには何かしらピンを結んでおく必要があるため空配列を作って渡していますが、UINavCollection側のSetupNavigationイベントにそのまま渡される値なので、必要であれば設定してUINavCollectionに渡してあげるとよいでしょう。

実行してみると、EdgeNavigationを設定してないのでパネルの行き来はできませんが、それぞれのパネル内では正常にカーソル移動できることが確認できると思います。

f:id:nca03132:20200428104532p:plain

 

EdgeNavigationを設定する

最後にUINavWidgetでEdgeNavitagionの設定を行います。

便宜上、上記のUINavCollectionのうち、左側をCollection1、右側をCollecton2とすると

  • Collection1のNavigationGridのEdgeNavigationのRightをCollection2の最初のボタンにつなぐ
  • Collection2のNavigationGridのEdgeNavigationのLeftをCollection1の最初のボタンにつなぐ

この2つの処理が必要です。

AppendCollectionは引数でEdgeNavitaionを渡すことができますがCollection1を生成するときにEdgeNavigationにCollection2のボタンを設定しようと思っても、Collection2のNavigationGridがまだ生成されてないのでボタンを取得することができません。

ですのでCollection1、Collection2両方ともAppendCollectionでNavigationGridを生成したあとにEdgeNavigationを設定します。

f:id:nca03132:20200428114311p:plain

まずCollectioin1、Collection2に設定されたNavigationGridを取得する必要があります。

UINavWidgetは生成したNavigationGridsという配列に生成されたすべてのNavigationGridをを保持しているのでこれを使いましょう。

Collection1、Collection2に何番目のNavigationGridから割り当てられているかはGetFirstGridIndexで調べることができます。

最初にそれらを変数化して保存します。

f:id:nca03132:20200428114720p:plain

これでCollection1に設定されているNavigationGridは

f:id:nca03132:20200428115039p:plain

という形で取得できるようになりました。この時のGetはRefを使用することに注意してください。

f:id:nca03132:20200428115203p:plain

次に、このNavigationGridのEdgeNavigationを設定します。

Getの右のピンを引っ張って"メンバ"と入力すると"Gridにメンバを設定"という候補が出るので選択します。

f:id:nca03132:20200428123431p:plain

生成された"Gridにメンバを設定"ノードを選択すると詳細のPin Optionsに設定するメンバを選択するチェックボックスが表示されるので"Edge Navigation"にチェックを入れます。

f:id:nca03132:20200428123801p:plain

すると"Edge Navigation"のピンが追加されるので、そこから引っ張って"ButtonNavigationを作成"ノードを作ります。

これでやっとCollection1のEdgeNavigationを設定する準備が整いました。

f:id:nca03132:20200428124211p:plain

次はこのRightButtonにCollection2の最初のボタンをつなぎます。

Collection1と同じ方法でCollection2のNavigationGridを取得し、そこからピンを伸ばして"Gridを分解"を選択すると取得できるパラメータが展開されます。

f:id:nca03132:20200428124750p:plain

この中の"First Button"がCollection2が保持している最初のボタンです。

これを先ほどのCollection1のNavigationGridの"RightButton"につないでくだい。

"Gridを分解"のノードは縦に大きいのでクリックして詳細に表示される"Hide UnconnectedPins"のボタンを押してノードを縮めます。

"ButtonNavigaionを作成"のノードも縦に長いのでクリックして詳細に表示されるチェックボックスでつながっているRightButton以外のチェックを外します。

結果、上記の処理はこのようなノードにまとまります。

f:id:nca03132:20200428125415p:plain

これでCollection1からCollection2へのナビゲーションは設定されました。

同様の手順でCollection2からCollection1へのナビゲーションを設定するノードを構築すると

f:id:nca03132:20200428125838p:plain

このようになります。

長いですが全体だと

f:id:nca03132:20200428125949p:plain

このようになります。

実際に実行して正しくEdgeNavigationが動作することを確認してください。

ウィジェットのランタイム生成

アイテムウィジェットなどで、動的にボタンなどのUINavComponentをグリッド内に追加していきたい、といったシチュエーションがあるかと思います。

ここではその方法について紹介したいと思います。

パネルの準備

まずはUMGデザイナーで動的にボタンを追加するパネルを作ります。

今回はVerticalBoxを用意します。VerticalBoxはグラフでも使用するのでIsVariableにチェックを入れておきましょう。

f:id:nca03132:20200428164224p:plain

 

NavigationGridの作成

次にNavigationGridを作成します。

今までの静的なウィジェットではボタンなどを配置してからNavigationGridを生成していましたが、今回は動的操作なので先にNavigationGridを生成してしまいます。

UINavWidget上のReadyForSetupイベントでAppendNavigationGrid系のノードを呼び出してください。ポイントとして、この時Dimensionは0を指定して中身を空にしておきます。

今回の例ではVertialBoxなのでNavigationGrid1DでVertial指定しておきます。

f:id:nca03132:20200428165826p:plain

追加するUINavComponentの作成

パネルに追加していくUINavComponentの派生クラスを作成します。アイテムメニューだと1アイテムを表すボタンに該当するパーツです。

プラグイン側のサンプルで用意されているUINavComponentDocs(UINavigationコンテンツ>Docs)を自分のプロジェクトにコピーして編集するか、新規クラス作成で親クラスをUINavComponentとして完全にイチから作ります。新規作成の場合は最低でもUMGのデザイナーでUINavButtonを配置し名称をNavButtonと変更しておく必要があります。

今回は標準で用意されているInventorySlotというUINavComponentを使います。

UINavComponentを追加

実際にUINavWidgetのグリッドに作成したUINavComponentを追加します。

ウィジェットセットアップ完了時に発生するUINavWidgetのOnSetupCompletedイベントにDelayを1秒挟んで、動的なタイミングでの処理であるかのように書いていきます。

まず、先ほど作ったUINavComponentのウィジェットを生成しグリッドにAddChildします。

f:id:nca03132:20200428170454p:plain

これで実行すると、1秒たった後にグリッドにInventorySlotが追加されて表示されます。が、NavigationGridに何も入ってないのでゲームパッドやキーボードによるカーソル移動はできません。

f:id:nca03132:20200428170726p:plain

静的なウィジェットでは最初からボタンが配置された状態でAppendNavigationGridが呼ばれたので、NavigationGridが生成されると同時にボタンもその中に追加されていましたが、今回は先にNavigationGridを空で生成してしまっているので、後生成であるUINavComponentはAddUINavComponentノードを使って明示的にNavigationGridに追加してあげなくてはなりません。

では実際にAddUINavComponentを追記していきます。

InventorySlotが1つだとカーソルが動いているかわからないので、今回は2つ追加します。

f:id:nca03132:20200428173515p:plain

細かくて見づらいと思うので必要に応じて拡大してください。

見てわかる通りAddUINavComponentのノードが最後に追加されています。

引数には生成したUINavComponent以外に、TargetGridIndexとIndexInGridの2つがあります。

TargetGridIndexは何番目のNavigationGridに追加するかです。今回NavigationGridは1つか作ってないので0が入ります。

IndexInGridはNavigationGrid内の何番目)登録するかを指定します。これはカーソルの移動順に関係します。

通常は-1を指定し自動的に最後のインデクスを割り振らせるようにします(階層上の上から順に0,1,2,3..とインデクスが振られます)。任意の数値を入れることも可能ですが保持している最大インデクスを超えた数値を入れると-1を入れた時と同じ挙動となります。

また、すでに存在するインデクスを指定するとNavigationGrid内に同一IndexInGridを持つUINavComponentが複数存在する状態となります。この場合はStackのように後から追加したUINavComponentがカーソル移動順位的に前にくるためカーソル移動が見た目の順番と異なってしまい使う側が混乱するのでお勧めしません。

通常はUINavComponentはそれぞれ必ず異なるGridInIndexが設定されるよう管理することをお勧めします。ソートなどでアイテムの見た目の順番を入れ替えた場合もAddUINavComponentでIndexInGridを振りなおすべきでしょう。

異なるNavigationGrid間でのUINavComponentの入れ替え

鞄から別の鞄へアイテムを移動させる場合など、あるグリッドから他のグリッドへUINavComponentを移動させたいことがあるかと思います。

その場合はMoveUINavElementToGridを使います。

引数のIndexは、移動させたいUINavButtonのButtonIndex(NavigationGridローカルではなくUINavWidget全体で振られるButtonIndex)を入れ、TargetGridIndexは移動先のNavigationGridのインデクス、IndexInGridは移動先のNavigationGridのどこ(何番目)にいれるかを指定します。

この処理はは操作に関係するNavigationGridで移動するだけなので、見た目でもきちんと移動先のパネル(VerticalBox、HorizontalBox)などにAddChildしなおしてください。

これらの実装は本家チュートリアル

https://youtu.be/3f-XDgGNsPc?list=PLAcWSem_HT4hT-3viMU2_z4WW94gKhgn0&t=559

あたりで紹介されているので動画で確認したい方はリンクからどうぞ。

不要なUINavComponentの削除

不要になったUINavComponentはDeleteUINavElementFromGridを使ってNavigationGridから削除します。

引数のGridIndexにNavigationGridのインデクス、IndexInGridはGrid内インデクスを入れて削除するUINavComponentを指定します。

上記は入れ替えと同様にNavigationGrid上で消すだけなので、見た目でもパネルからRemoveChild,RemoveChildAt,ClearChildrenなどを使って削除してあげる必要があります。

こちらも本家チュートリアルでは

https://youtu.be/3f-XDgGNsPc?list=PLAcWSem_HT4hT-3viMU2_z4WW94gKhgn0&t=720

あたりで紹介されています。

 

クラスドキュメント

コンテンツブラウザ上のUINavigationコンテンツ>Docsの中には、アセット名の最後にDocsがついているサンプルが入ってます。

f:id:nca03132:20200427174826p:plain

これらは各クラスごとに、どういったノード・イベントが用意されているかを説明するためのアセットとなっています。実装時の参考にするとよいでしょう。

実装サンプル

コンテンツブラウザ上のUINavigationコンテンツ>Examplesにはさまざまサンプル実装が入っています。

アイテムメニュー、グラフィック設定、セーブロード、ポーズメニューなどよく使われるものは大体そろっていると思います。

特にUINavExampleMenuは単体でウィジェットとしてAddViewportしてそのまま使用することができ、ここを起点としてさまざまUIへ遷移する仕組みをもっているので非常に参考になります。

記事を読んである程度UINavigationについて理解しておけば、すんなりとサンプルも読めるようになっていると思うので、ぜひチェックしてみてください。

 最後に

今回の記事は下調べから動作理解、実装確認までめちゃめちゃ手間がかかりました。本当に泣きそうでした。途中何度心が折れそうになったことか。

しかもそんなにメジャーでないアセットなので何人がこれを読んでくれるかを考えると……。

いやいや、これもUE4ユーザーの礎になれば本望というものです(泣)。

というわけで、もし気になったらぜひ使ってみてください。分からないことなどあれば何なり質問してください。わかる範囲ですがお答えしたいと思います!