みつまめ杏仁

アンリアルエンジンでGUIを作るためにゴニョゴニョしてます。UIデザイナーの皆様の助けになれば幸いです。

ゲームパッドとマウスでカーソルを動かす 《後編》

前回の続きです。

早速Widgetを準備していきます。まずは 子Widgetから。

 f:id:hiyokosabrey:20200225205536p:plain


キャンバスに Image を置いて、TextBlockを重ねます。

とりあえず今回は280x280にしました。

 

f:id:hiyokosabrey:20200225205659p:plain

 

今回見た目と機能をシンプルにしたかったのが理由ですが、Buttonコンポーネントでも大丈夫。TextBlock は後から文字列を差し替えるので、Is Variable にチェックを付けています。

 

次に見た目をアニメーションで用意します。

 

まず カーソルの乗っていない Default

f:id:hiyokosabrey:20200225210747p:plain

f:id:hiyokosabrey:20200225210755p:plain

 

次にカーソルの乗った Focus

f:id:hiyokosabrey:20200225210834p:plain

f:id:hiyokosabrey:20200225210845p:plain


カーソルが離れた時 Unfocus すこし余韻を持たせるためにフェードさせます。

f:id:hiyokosabrey:20200225210942g:plain

f:id:hiyokosabrey:20200225210955p:plain

 

アニメーションは完成。

最後に確認。

f:id:hiyokosabrey:20200225211310p:plain

アニメーションを一つも選択していない状態で、キャンバスの見た目が Default と同じ状態 になっていることを確かめておかないと、表示を開始した時に意図しない状態で現れます。

 

 

これでキャンバスのエディットは終了。次はブループリントです。

 

Widget の仕事は3つ。

  • マウスカーソルの検出
  • 検出した際に親に通知する
  • 見た目の変化

 

まず下準備として 親Widget に通知するための数値を保持しておく変数を用意します。

この変数は 親Widget から渡されてくることになるので、 Expose on Spawn にチェック付けます。Compileした際に Warning が出るので Instance Editable にもチェックを付けます。名前は ButtonID としました。

f:id:hiyokosabrey:20200226110628p:plain

この預かった数値を、マウスカーソルを検出した際に 通知します。

その通知の仕組みを Event Dispatcher(イベントディスパッチャー) が担当。

 

Event Dispatcher を追加して適当な名前を付けます。

f:id:hiyokosabrey:20200226111709p:plain

できたら、値を受け取って渡すためのピンを追加します。

f:id:hiyokosabrey:20200226113008p:plain

ピンの名前は先の変数と同じ ButtonIDとしました。

 

次はマウスが乗った時に検出する関数が用意されているので、オーバーライドします。

f:id:hiyokosabrey:20200225214053p:plain

プルダウンで大量に出てくるので、On Mouse Enter を選択。

f:id:hiyokosabrey:20200226111935p:plain

この関数のオーバーライドは、アセットのクラスによって内容が変動します。さすがにWidgetはUIに関連するアセットなので、入力関連が多数用意されています。

 

選択するとグラフに赤いイベントノードが現れるので、先に用意した変数とイベントディスパッチャーをつなぎます。

f:id:hiyokosabrey:20200226112955p:plain


これで、3つあった仕事のうち、2つの準備が整いました。残りは1つ。

  • マウスカーソルの検出
  • 検出した際に親に通知する
  • 見た目の変化

 

作ったアニメーションを再生するイベントを用意します。

このイベントは、自分で再生するのではなく、親Widget から呼び出して再生するようにします。マウス以外にキー入力によっても再生する必要があるからです。

 

UMGのアニメーションって、再生中に別のアニメーションを再生すると、先に再生しているアニメーションを止めてくれるわけではないので、意図しない見た目になってバグか?と思えるような結果になることもしばしば。再生する際に事前にアニメーションしているものがあれば止める仕組みを用意する必要があります。わりと面倒なのでオススメの仕組みを紹介しておきます。

 

まずは Widget Animation の変数を一つ用意します。

f:id:hiyokosabrey:20200226135938p:plain

名前を CurrentAnim(現在のアニメーション)としました。

これを使ってマクロを2つ準備します。

 

forceAnimStop

f:id:hiyokosabrey:20200226140233p:plain

再生中だったら止める、再生していなかったらなにもしない、というマクロです。

 

startInteraction

f:id:hiyokosabrey:20200226140429p:plain

PlayAnimation という名前のノードがあるので、スタートインタラクションという名前にしました。Setノードの右にピン、地味に便利ですよね。

 

このマクロを使って、アニメーション再生用のイベントを用意します。

 

強制的にデフォルト

f:id:hiyokosabrey:20200226141012p:plain

 

強制的にフォーカス

f:id:hiyokosabrey:20200226141122p:plain

 

強制的にアンフォーカス

f:id:hiyokosabrey:20200226141135p:plain

 

 この方法だと、直前に何らかのアニメーションが再生中でも、確実に止めることができます。事前に用意しておいた WidgetAnimationの変数をハンドラーとして使用することで、状況に依存しないフローを組むことができます。後からアニメーションの尺が変わっても問題なく動作します。プロジェクトのマクロライブラリに置いておくといいかもしれません。

UIはユーザーが操作するので、タイミングなんて予測できないし、可能な限りタイムラグのないレスポンスが期待されます。

 

一応これで必要な機能は揃いましたが、仕上げにもう一つボタンのラベルを書き換える仕組みを用意します。

Text型の変数を追加します。

f:id:hiyokosabrey:20200226142634p:plain

 

この変数を Event Construct でTextBlockにつないでセットします。

f:id:hiyokosabrey:20200226142931p:plain

 このつなぎ方は最初の一回のみなので、途中でボタンのラベルが変更になる場合を想定するなら、関数を用意しておくのがベスト。

 

これで 子Widget 完成です。

 

 

ここからは 親Widget を準備していきます。

f:id:hiyokosabrey:20200226144301p:plain

さっそくキャンバスに WrapBox を配置します。

f:id:hiyokosabrey:20200227022555p:plain

今回 子Widget のサイズを 280x280にしたので、WrapBox のサイズは、 余白を2pxと想定して3個並ぶので、852x852としました。

キャンバスはこのくらいにして、グラフを編集していきます。

 

まず初期化のための関数を用意します。プロトタイプなら特に不要ですが配列のセットが大きくなってグラフが不格好になるのでここに格納する感じです。実際のゲームの場合メニューボタンを表示する際、セーブデータの内容や進行状況によってボタンの状態を変える必要があります。そのための場所としても使えます。

 

まずは一番大事な フォーカス位置を覚えておくInt型の変数と、ボタンをラベル用の配列をセットします。

f:id:hiyokosabrey:20200227021951p:plain

indexCurrent という名前にしました。

ラベルは Text型の変数を作って配列にします。

f:id:hiyokosabrey:20200226145509p:plain

f:id:hiyokosabrey:20200227022257p:plain

 

今回この関数は初期のフォーカス位置とボタンのラベルを持たせているだけです。

特に戻り値もないので、 Return ノードは置いていません。

 

まずはこの関数を Event Construct につないで、ボタンになる 子Widgetインスタンス化するための ForLoop ノードをつなぎます。ループ回数は 0 から 8 の 合計 9 回。

f:id:hiyokosabrey:20200226164313p:plain

続きはこんな感じ

f:id:hiyokosabrey:20200227015057p:plain

ForLoop で複数回すことで CreateWidget ノードが 子Widget を次々にインスタンス化するので、結果的に量産することになります。

f:id:hiyokosabrey:20200227114146p:plain

インスタンス化したらすぐに配列にお取り置きしています。これで 子Widgetたちを 番号で管理できるようになります。配列に取り置きしつつ、ReturnValueからの同じ青いラインで バインディング や パディング の設定をして最後に WrapBox Add Child します。順番については特に制約とかはなさそうですが、ひょっとしたらあるかもしれません。

 

バインドは 子Widget に作っておいたイベントディスパッチャーの名前を探します。

f:id:hiyokosabrey:20200227113124p:plain

このノードはイベントにつながないと、コンパイルした際にエラーになるけど、回避するために行ったり来たりすると、説明がややこしくなりそうなので、いったんループ処理の全貌を載せます。

 

で、エラーを消すためにバインドして受信したときに呼び出されるカスタムイベントを用意します。

f:id:hiyokosabrey:20200227115253p:plain

Force~ という関数が2つ登場しますが、これは繰り返し同じつなぎ方をすることになるので Collapse Function して作ったのですが、内容は以下になります。

f:id:hiyokosabrey:20200227115657p:plain

f:id:hiyokosabrey:20200227115707p:plain

配列に格納した 子Widget たちに対して、指定した番号の持つ関数を呼び出しているだけです。アニメーションの再生処理してるイベントですね。

 

これで、先ほどのエラーが出ていたバインドノードが解決できます。

f:id:hiyokosabrey:20200227120125p:plain


これでマウスを動かしたときに、フォーカスおよびハイライトの切り替えができたことになります。

 

レベルブループリントからビューポートに追加してテストしてみましょう。

f:id:hiyokosabrey:20200227120929p:plain

PlayerController が持っている Show Mouse Cursor を 有効にしておくと再生した時にマウスカーソルが表示されるようになります。

f:id:hiyokosabrey:20200227132727g:plain

 


残るは キーボードとゲームパッドの入力対応。

 

Widget を 0~8 の Index番号で扱うので、マウスの時と同様に CurrentIndex を増減すればいいだけです。

今回キレイに3x3のグリッド状に並んでいるので、計算でどうにかしたかったので、タテ(上下)方向とヨコ(左右)方向でそれぞれカスタムイベントを作りました。

 

moveUpDown

f:id:hiyokosabrey:20200227144902p:plain

moveLeftRight

f:id:hiyokosabrey:20200227144912p:plain


このイベントには、それぞれ -1+1 が渡されてきます。左方向は X-1、右方向は X+1、上方向は Y-1、下方向は Y+1 というふうにイメージしています。

WrapBox内の並びは左上が 0 で、右下が 8 です。タテヨコに移動するように計算してして、はみ出たらIndex番号を更新しないという仕組みです。

f:id:hiyokosabrey:20200224111533p:plain

ヒントは

タテ方向は、上下に移動するたびに ±3 なので、3を掛けて ±3で計算します。

ヨコ方向は、現在地に±1した結果に対して3で割ると余りは 0~2 なので %(剰余)を使うと便利。

ちょっと難しかったのが、マイナスになった時と、最大数を超えた時の判定です。

なるべくブランチノードを減らしたかったのもあって論理和  OR  ノードを使っています。

この辺の作りは並ぶ数や最大値が変わったり、Disable状態のものをスキップするかしないかなどで、処理の仕方が変わってくるので、臨機応変な作り方が求められます。いろんなシチュエーションで作り慣れておくしかないのかも。

 

最後の仕上げです。

このままだと表示開始した時、ハイライト表現が無くて、操作していいのかどうか伝わりません。ForLoop 処理が終ったところでハイライトするようにします。

f:id:hiyokosabrey:20200227150433p:plain

 

Widget は以上です。

 

 

プロトタイプということにして、レベルブループリントでキー入力の判定を取ります。

f:id:hiyokosabrey:20200227151055p:plain

f:id:hiyokosabrey:20200227151234p:plain

New Var 0 は ビューポートに Add to Viewport する際にPromote to Variable しておいたやつです。テストなんで横着しています。

 

ようやく完成です。

 


[UE4]ゲームパッドとマウスでカーソルを動かす

 

いかがでしょうか?

状況によってはカスタマイズが必要になると思いますが、ひとまず基本的な原理はおさえられていると思います。カーソルといいつつ正確にはフォーカスおよびハイライトの遷移なんですけどね。

まぁそれっぽいキャラを子Widgetに仕込んでおいて、表示をON/OFFするとか、以前に記事で紹介していますが、親Widget側に用意したカーソルキャラをフォーカスの場所をトレイルするようにすると、カーソル感がアップします。

 

ではでは

ステキなカーソルライフを!

 

 

 

今回の失敗

実は最初カーソルを上下左右にループするように作っていましたが、並んでいる数が少なすぎて見た目に混乱しそうだったので、ループをやめたという経緯があります。

作ってみて試さないと得られない貴重な知見でした。

あと、マウスカーソルの検出に、Enter(入ってきた)とLeave(出ていった)があるのですが、作りはじめのころ 子Widget の方で両方に対応してしまっていたので、マウスカーソルが9つの子Widgetから完全に離れてしまうと、見た目にハイライト表現が消えてしまうので、キー入力するときにわからん、となっていたのがなかなか解決できずに悩んでいました。結局、Leave の検出はやめて、完全に 遷移は親Widget に任せることにしたらスッキリと解決しました。

 

 

 

マウスカーソルの表示について(追記: 2020/3/18)

以下の案件をご相談いただいたので、さっそく試してみました。

 

  • マウスおよびキーボードを操作しているときはマウスカーソルを表示
  • ゲームパッドを操作しているときはマウスカーソルを非表示

 

PlayerController で あらかじめ Show Mouse Cursor を 有効にしていたので、これを やめればいいのでは? と思い試してみたのですが、ダメでした。

f:id:hiyokosabrey:20200318224134p:plain


で、次にやってみたのが、Input Mode 系のノード。

f:id:hiyokosabrey:20200318223808p:plain
Set Input Mode Game And UI でひとまず、思ったような挙動になりました。



入力を受け取るたびに、セットするのもなんかノードが勿体ない感じがします。

ふと、レベルブループリントで一度つないでおけばいいんじゃないの?と気が付いて試してみました。

f:id:hiyokosabrey:20200318215146p:plain

が、だめでした。残念。

公式のドキュメントで詳しく触れてそうな気配がなかったのですが、探し方が足りない気がするので、いずれもう少し踏み込んで調査したいと思います。とりあえず今回はこれでなんとかなると思うのですがいかがでしょうか?

 

ちなみに、マウスを操作しているかどうかは、マウスの移動量をみて判定しています。

Widgetの関数に OnMouseMove というのが用意されているので、今回はこれを オーバーライドして利用しました。

f:id:hiyokosabrey:20200318220827p:plain

MouseEventピンから、Get Cursor Delta でマウスの移動量を取り出せるので、これでマウスに触れているかどうかを判定します。

ブランチノードの右にある Switch~ ノードは、 操作しているデバイスをアイコンで説明するために用意したカスタムイベントです。今回の記事を動画にする際にコッソリ仕込んでいたものです。

 こんな感じです。

f:id:hiyokosabrey:20200318221538p:plain

画像をあらかじめ3つ用意しておいて、表示・非表示を切り替えるというイベントです。

f:id:hiyokosabrey:20200318221852g:plain

ゲームパッドとキーボードの方は、レベルブループリントで Switch~ を呼び出しています。

f:id:hiyokosabrey:20200318222152p:plain


まずはこんなところですけど、ちょっと自信がないので、ツッコミ等々あれば是非是非。