みつまめ杏仁

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

キャラセレを作ってみる カーソルを動かす

最近話題のハンドスピナーが売っていたので買ってみたのですが、振るとチリチリと何やら小さなパーツが跳ねる音がします。回すとシャーと音がして1分も持ちませんが雰囲気を楽しむ程度には堪能できた、気がします。

 

さてさて前回の続きです。まだまだ続いてます。

サムネイルが登場したところで、次はカーソルです。

マテリアルで回す

カーソルは動きがある方が分かりやすいのでアニメーションをつけます。くるくると回るだけです。今回はただ単純に回るだけなので、マテリアルで回すことにします。

 

カーソル用のマテリアルを用意します。作り方の基本はサムネイルのときと同じですがシンプルです。テクスチャを回すのは CustomRotator を使います。

f:id:hiyokosabrey:20170612235634p:plain

マテリアルができたら、メインのWidgetにカーソルを追加します。

 

メインのWidgetを開いたら、キャンバスに新しい Imageをドロップします。

f:id:hiyokosabrey:20170613000218j:plain

サイズはテクスチャのサイズ。サムネイルの上に乗るので、ZOrderの値はWrapBoxよりも大きい値にします。このImageパーツに作ったカーソルのマテリアルをセットします。Appearance > Brush > Image

f:id:hiyokosabrey:20170613001358j:plain

セットするといきなり回っている状態で挿し変わります。こういった単純なループアニメーションは、UMGのタイムラインよりもマテリアルで作った方がシンプルな作りになるのでオススメです。調整もマテリアルをいじるだけで済みます。

ポジションはサムネイルが見えていないので調整しにくいですが、とりあえず今回はWrapBoxと同じ座標に置いておきます。

 

カーソルが画面に存在するということは、ユーザーに「ぜひ操作してください」というアピールをしていることになります。登場演出がすべて終わって、操作を受け付けるよ、という状態になって初めてカーソルは登場するのが基本です。画面に最初から居るといろいろ不都合があるので、非表示にしておきます。Visibility をHiddenにします。

f:id:hiyokosabrey:20170613002355p:plain

 

 

カーソルを表示する

パーツの次はブループリントを準備していきます。

前回のサムネイルを並べる関数にあとひとつ要素を追加します。

f:id:hiyokosabrey:20170613003359p:plain

この変数は、サムネイルをCreate Widgetする度に一定の値が加算されていました。なのでこのタイミングでは最初のサムネイルが登場してから最後のサムネイルが登場するまでの時間が数値として入っています。これを戻り値(ReturnValue)として外に渡してやるのです。

ここでいったんコンパイルしましょう。後から関数を編集した場合で、引数や戻り値が変更になると、関数の外でもピンの状態が変わってしまうため、接続が切れたりします。そして大抵エラーが出ます。

その場合は慌てずに、エラー表示のついている関数ノードの上で右クリックして、Refresh Node を選んだ後でもう一度コンパイルすると大抵エラーが解消します。

 

EventGraphに戻って関数ノードを見てみると、右側にピンが追加されているはずなので、この値を Set Timer by Event ノードに渡します。

f:id:hiyokosabrey:20170615003235p:plain

そしてカスタムイベントを追加してこのSet Timer by Eventとつなぎます。

 f:id:hiyokosabrey:20170615003400p:plain

8個あるサムネイルの8個目の登場時間が戻り値ですので、タイマーを使ってすべてのサムネイルの登場を待ちます。ただ登場のアニメーションが終わったわけではないので、Delay ノードで登場のアニメーションの分だけ待ちます。

f:id:hiyokosabrey:20170615003524p:plain

充分待ったらようやくカーソルの表示を許可してやりたいのですが、もう少し準備が必要です。カーソルの居場所と選択中の場所を管理する変数を用意します。

居場所は Index番号で管理するのでInteger型にします。FocusIndex という名前にしてSetでつなぎます。値は最初にカーソルがあたっている場所になります。

f:id:hiyokosabrey:20170615004056p:plain

 

ここで関数を用意します。この関数には Index番号からカーソルの座標を計算するという仕事をさせます。

f:id:hiyokosabrey:20170615004904p:plain

 横に4つ並ぶので、演算記号 %(剰余)を使って X座標を。縦方向は4つ毎に1段下に進むので単純に4で割り算してY座標となります。Int型同士で割り算をすると答えは整数のままで小数にはなりません。

 この % と ÷ を使った座標計算については過去記事を貼っておきます。

Widgetでタイル型のボタンをグリッド状に並べる

 

f:id:hiyokosabrey:20170617234201p:plain

XとYに サムネイルの表示間隔を掛け算してやると、サムネイル毎のカーソル位置がバッチリ出てきます。

右の方の青いのは、Vector2D型というタイプの値を扱っています。これは2Dという名前でなんとなく想像がつくかと思いますが、XとYやUとVのような関連する2つのFloat型が1つにまとめられているというものです。単に2つのFloat型の変数がまとめれれているだけなので、まったく関連の無くても問題は無いです。

f:id:hiyokosabrey:20170618000350p:plain

CursorOffsetPosという変数(ローカル変数ではない)を追加しています。これは

サムネイルたちのポジションは画面の右下にあります。画面は左上が0原点なので、いくらかずらしてやらないといけません。そのズレを加算して補正するための変数です。

f:id:hiyokosabrey:20170618002309j:plain

この変数はこの関数内でGetでつないでいるので、当然どこかで値を入れておく必要があります。変数を作ったときに初期値入れておくことができますが、せっかくなんでInitialize関数でSetしておきます。

サムネイルたちはWrapBoxの中に並びます。WrapBoxが親でサムネイルは子供なのでWrapBoxのポジションを起点にするのが良さそうです。下図のようにつなぐと、後からWrapBoxの位置を変えてもちゃんとついてきてくれます。

f:id:hiyokosabrey:20170618004231p:plain

 

編集タブを座標計算用の関数に戻します。

今回この関数はピュア型にしてみようと思います。Pure=純粋ということでプログラマ的に純粋関数とか言われたりもするようです。方法は、グラフの紫のノードをクリックすると左のDetailsタブの中身が下図のように変わるので、Pure のチェックボックスにチェックを付けるだけです。

f:id:hiyokosabrey:20170617235453p:plain

これでこの関数は完成です。早速この関数をEventGraphの方に並べてみます。

ドロップするといつもと様子が違います。白いピンがありません。

f:id:hiyokosabrey:20170617235802p:plain

ここで、計算した座標を受け取る変数 CursorDestinationPos を追加してつなぐと下のようになります。この変数は当然Vector2D型です。

f:id:hiyokosabrey:20170618004400p:plain

Destinationは目的地という意味で名付けました。

カーソルの座標は計算することができたので、次にその座標をカーソルに反映させないとカーソルの位置は変わりません。そこでカーソルを指定の位置に表示する関数を用意します。中身はいたってシンプル。SetCursorPos関数。実際にはスペースは入れていませんが、エンジンが勝手に、単語の間にスペースを入れてくれます。

f:id:hiyokosabrey:20170618131829p:plain

これもつないでいきます。ポジションも決まったので、表示しちゃいましょう。

f:id:hiyokosabrey:20170618132524p:plain

ここでいったん画面を確認してみましょう。

f:id:hiyokosabrey:20170618132748j:plain

出現の演出が終わってからカーソルが表示されて入ればOK。

上の画像はFocusIndexの値に5を入れています。

カーソルが無事に指定した場所に出るようになったので、次にサムネイルのフォーカスを切り替える関数を用意します。

f:id:hiyokosabrey:20170618223940p:plain

 Integer型の引数を一つ受け取って、サムネイルの配列からGETノードで取り出して、サムネイルのWidgetに用意しておいたフォーカス状態にする関数をつなぎます。

これをEventGraphの方につなぎます。引数にはFocusIndex変数をつないでやります。

f:id:hiyokosabrey:20170618224244p:plain

 これでようやく指定した場所にカーソルが表示されて、さらにその指定のサムネイルがフォーカス状態になるというところまで来ました。

 

いよいよキー入力に合わせて移動させます。

 

 

カーソルを動かす

ここまでは、作り手が決めた通りに表示の段取りが進んできましたが、カーソルを動かすとなると、ユーザーの好き勝手なキー入力に応えるしくみが必要です。そのためには常に待機していてユーザーのキー入力を見張っておくことになります。それを実現するのがEventTickノードです。

f:id:hiyokosabrey:20170618225527p:plain

Widgetブループリントに最初から置かれているノードで、ここにつないだノードは再生すると、再生を止めるまでずーっと処理され続けます。なので、ここでユーザーのキー入力を見張ることにします。見張ると言っても、最初からではなくサムネイルの入場演出が終わってカーソルが出現してからにしたいので、フラグを使います。

新しくBoolean(ブーリアン)型の変数を一つ追加して、さっきのカーソルのフォーカスをセットする関数の次につなぎます。isActive という名前にしました。チェックを付けてます。

f:id:hiyokosabrey:20170618230518p:plain

ここチェックを付けるということは、それ以前はチェックが付いていないということなので、初期値は付けないでおきます。

f:id:hiyokosabrey:20170618230952p:plain

Boolean型の変数は True(真) と False(偽)の2つの状態を扱います。スイッチみたいなものです。

チェックが付いている  → True(=スイッチON、または「フラグが立った」)

チェックが外れている → False(=スイッチOFF、または「フラグが寝た」)

 

再生開始時は外れていて、カーソルが出現したらチェックが付くことになりました。

この変数の変化をさきほどのEventTickで見張るのです。

f:id:hiyokosabrey:20170618231655p:plain

Banch(ブランチ)ノードをつないでそのCondtionのピンに変数をつなぎます。ブランチは枝(→分岐)という意味で、True ときとFalse の時で流れを変えることができます。今回はTrueに続きのノードをつないで、Falseには何もつながないでおけばいいのです。

 さて、このTrueのときにつなぐのが、キーを押したか押してないかをチェックする処理です。以前の記事で紹介しているので、今回は説明を省きます。

 limesode.hatenablog.com

 このマクロライブラリを用意しておくと便利ですよ~ということで、始めたキャラセレを作ってみるという企画だったのを改めて再認識したところで次に進みましょう。

このマクロをTrueのピンにつなぎます。

f:id:hiyokosabrey:20170618233216p:plain

GetPlayerControllerノードが必要なので引数としてつなぎます。

 ユーザーがキーを押した瞬間に押したキーのところにつないだノードが処理されます。カーソルの位置とフォーカスの位置はFocusIndexという変数で指定できます。つまりこのFocusIndexという変数の値をどうにかすればよいのです。ここで改めてサムネイルの並びを確認してみます。

f:id:hiyokosabrey:20170619222944p:plain

カーソルを右に動かす場合、Indexの値は +1すれば良さそうです。逆に左に動かす場合は -1 すれば良さそうです。問題になるのはそれぞれ端っこにいる場合です。たとえば右に動かす場合、3の位置にいるとき、+1 するとカーソルは 左下の位置に移動して、7の位置にいるときは 8 になってサムネイルの数をオーバーしてしまいます。逆も同じです。

この辺りの問題を念頭に入れつつ、Indexの値を増やしたり減らしたりする関数を用意します。

まずは単純に右に移動する関数から。

その前に、サムネイルのボタンをアンフォーカスする関数を用意しておきます。

f:id:hiyokosabrey:20170619224025p:plain

先に用意した関数と同じ構造です。指定したサムネイルの配列からGETで取り出してアンフォーカスのイベントを呼び出すだけです。

 

で、右に移動したときの関数は以下のようになります。

f:id:hiyokosabrey:20170619225120p:plain

計算で解決する方法もあるのですが、説明がややこしくなりそうなので、ベタな方法ですがSwitchノードを使いました。最初にフォーカスしていたサムネイルを元にアンフォーカスし、その直後に数値を見て分岐する Switch on Int ノードで切り分けています。切り分けた結果は大きく3つに分かれます。素直に+1 するものと 右端で+1下ときのものです。その結果を一旦ローカル変数TempIndexに入れます。Switchノードで道が分かれるからこそできる手法です。どのルートを通ったかで、TempIndexの内容が変わっています。それを再び FousIndex変数の入れて値を書き換えます。新しくなったFocusIndex変数の値でサムネイルボタンをフォーカスして終了です。

 

同じように左に移動したときの関数を用意します。複製するとラクちんです。

f:id:hiyokosabrey:20170619230427p:plain

違いは、Switchノードの先の分かれ道ですが理屈は同じなので、サムネイルのIndex番号がどのように並んでいるかを確認しながらつなげばそんなに難しくはないはずです。

上の足し算ノードですが、ここに -1 が入っていることに注目です。引き算ノードにして1を入れれば結果は同じですが、関数を複製したのであればノードを入れ替えなくていいので楽です。1を引くのも、-1を足すのも同じだからです。

 

ちょっと話はズレますが、普通の変数とローカル変数に違いについて。

FocusIndex変数はどの場所からでも使用できて、しかも中の値は共有します。変な例えかもしれないですが、アチコチにあるATMからお金出し入れする感じに似てると思います。結果的に変動するのは一つしかない口座の残高。それに引き換えローカル変数の場合は、関数毎に小さな口座を開設することになるので同じ名前の口座名だけど関数が違うと中の残高も違うという使い方ができます。

 

この2つの関数をさっそくEventGraphの方につないでみましょう。

f:id:hiyokosabrey:20170619232340p:plain

再生してみると、サムネイルのフォーカスが切り替わっていくのが分かると思います。

端まで行ったら反対側に移動していればSwitchノードが正しく機能している証拠。

 

この調子で今度は上下の分を用意します。今回のキャラセレは2段しかありません。言ってしまうと、上下どちらにいようとも、上下のキーどちらを押そうとも上下の場所を入れかえればいいだけです。

f:id:hiyokosabrey:20170619233133p:plain

それぞれの段で ±4 すればいいだけです。

というわけで、EventGraphでつなぐときはUpとDownをまとめてつなぎます。

f:id:hiyokosabrey:20170619233337p:plain

これでフォーカスの切り替えは完璧です。

 

最後に要のカーソルを移動させましょう。

カーソルを指定の場所に表示する関数は用意してあります。さらに目的地を保持する変数も用意済みです。FocusIndexの値はすでにキー入力に合わせて変動しています。

カーソル移動のための関数を用意します。

f:id:hiyokosabrey:20170619234321p:plain

しくみについては過去記事を貼っておきます。

 

limesode.hatenablog.com

 この関数をEventGraphのマクロにつなぎます。

 f:id:hiyokosabrey:20170619234701p:plain

最期のNo Input は何も入力がなかった場合に流れてくるところで、ユーザーがキーを押さなければ常に実行されます。

FocusIndexが更新されてフォーカス位置が変わっても、CursorDestinationPosの値が更新されていません。目的地の更新がないとカーソルは動かないので、更新するようにします。

f:id:hiyokosabrey:20170619235312p:plain

これでカーソルの動きは完成です。

EventGraphはだいたいこんな感じになりました。

f:id:hiyokosabrey:20170619235644p:plain

再生して触ってみましょう。

f:id:hiyokosabrey:20170619235958j:plain

f:id:hiyokosabrey:20170620000041j:plain

f:id:hiyokosabrey:20170620000051j:plain

フォーカスしたサムネイルを追いかけてカーソルがするすると動きます。

 

 

今回は以上です。

次回は、フォーカスに合わせて大きいイラストを表示するところを作っていきます。

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