みつまめ杏仁

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

マウスで操作できるミニマップ表示

 2019年が明けました。公私共にいろいろ節目的な年になりそうですが、アンリアルエンジンを使ったUIの研究だけは続けていこうと思うので、今年も引き続きよろしくお願いいたします。

 今回マウスで操作するミニマップを考えてみました。ふと、Pivotをちゃんと計算してやったらいけるんとちゃう? と思いついたのがきっかけです。考えたといってもごく普通のよく見かけるやつです。大事なのはUE4で作ってみること。UMGで作るとしたらどのように?という疑問に一つでも答えを見つけておきたいという使命感のようなものでやってます。需要があればいいのですが・・・。

 

 仕様としては、

  • 任意の長方形でクリッピング可能
  • 見えているのはマップの一部分
  • マップ上にマウスカーソルがあるときだけ操作可能
  • ドラッグでスクロール
  • ホイールで拡大縮小

です。

 

 

まずはキャンバスから。

新たに CanvasPanel を追加します。

クリッピングは この CanvasPanel で行うので、適当なサイズで用意。

f:id:hiyokosabrey:20190110235120p:plain

上のサンプルでは何となく 640x480にしてます。

このCanvasPanelの Clipping 設定を、 Clip to Bounds に変更します。

f:id:hiyokosabrey:20190427093015p:plain

これで、はみ出た部分を隠せます。

 

この CanvasPanel に2つの Image を子供として追加します。  下敷き と マップ画像 です。

 マップ画像用のImageパーツには、Size to Content にチェックを付けておきます。

f:id:hiyokosabrey:20190112075426p:plain
マップ画像によってサイズが変動しても対応できるようになります。

 

UMGは以上です。

 

Widgetブループリントを編集していきます。

まず用意した変数は以下。上段の4つは Vector2D型です。

f:id:hiyokosabrey:20190111234239p:plain

 

関数を用意します。

 

マップテクスチャを受け取ってセット、そしてそのサイズを変数に格納するお仕事。

f:id:hiyokosabrey:20190112075620p:plain

画像サイズを取得するために、 Match Size にチェックを付けます。

 

 次は、マップImage のポジションを受け取って変数に格納。さらにマイナスにしてから反映するお仕事。変数の値は プラスのままで扱います

f:id:hiyokosabrey:20190112075909p:plain


 マップの画像はCanvasPanelの下では、左上が(0, 0)なので、マイナスの値にしてやる必要があります。

f:id:hiyokosabrey:20190112060254p:plain

  マイナスにしないといけないのは、あくまでもUMG側の表示都合なので、変数には加工しない状態で扱っておく方が考えやすいし、デバッグしやすいと思います。

 

 

 次の関数は Pivot の計算と反映。

 CanvasPanel=見えている範囲 の中心を基準に拡大縮小するので、Pivot 位置が常に変動します。Map画像のポジションに、計算して求めておいた OffsetPivot を足すと、CanvasPanel の中心に相当する場所が決まります。それを Map画像のサイズで割ると、Map画像における Pivot位置が新しく決定します。

f:id:hiyokosabrey:20190112080040p:plain


OffsetPivot は下図のイメージ。

f:id:hiyokosabrey:20190112062948p:plain

 これとMap画像のポジションを足すと、 Map画像の Pivot 位置が 座標として判明します。

f:id:hiyokosabrey:20190112082546p:plain

  判明したPivot位置は 0.0 ~1.0 の範囲で扱わないといけないので、Map画像のサイズで割ってやります。テクスチャのUV値を求めるのと同じ考え方ですね。

 

 

 ここからは、マウスの挙動に関する関数。

 すでに用意されているので、Override して進めます。使うのはつ。

f:id:hiyokosabrey:20190112094609p:plain

長いリストメニューがポップするので、必要なやつを選択します。

 

OnMouseButtonDown

マウスのボタンが押下されると呼び出される関数。

f:id:hiyokosabrey:20190112095622p:plain

 ブランチノードで判定してるのは、押したのが 左ボタン で、且つキャンバスの上にカーソルが乗っているかどうかです。2つ以上の条件を同時に判定する場合は、AND Boolean ノードを使うとブランチノードが1つにまとめられるのでお得。

 無事条件をクリアしたら、フラグを立てて、ボタンを押したポジションを変数に取り置きします。

 

 is Mouse Button Down ノードを使うと、何のボタンが押されたかを調べることができます。 調べ方は、指定したボタン毎に問い合わせて確認することになります。今回は左ボタンのみの扱いなので、左ボタンについてだけ確認しています。

f:id:hiyokosabrey:20190112101512p:plain

 ちなみにマウスボタンは5つまで判定できるようです。最近のマウスは親指のあたりに 「進む」 と「 戻る」に相当する機能が振られたボタンが付いてることが多いです。

f:id:hiyokosabrey:20190112100559p:plain

 

 

OnMouseButtonUp

マウスのボタンから指が離れたときに呼び出される関数。

f:id:hiyokosabrey:20190112102524p:plain

マウスのボタンが解放されたということは、ドラッグが終わったということで、フラグを False に戻します。

 

 

OnMouseMove

 マウスの移動を検出すると呼び出されます。

 というのが一般的だと思うのですが、UE4の場合、この関数は常時走ってるようです。マウスから手を放しても呼び出されるので、人間が感知できないほどの微細な振動を拾ってるのか、マウスの仕様がそうなっているのか、UE4の仕様かはわかりません。

f:id:hiyokosabrey:20190112105600p:plain

 常時処理されるとそれなりに負荷になるので、前述の OnMouseButtonDown でフラグを立てた後、そのフラグが倒れるまでの間だけ処理するようにします。さらに、マウスが動いていないと判断された場合は何もしません。

 作ってみて気づいたのですが、スケールが掛かっていると、座標の計算がおかしくなります。UMGがレンダリングする際の計算順序に依存します。またマウス座標の取得はViewportの解像度は関係なくモニタの解像度に依存します。なので、ちょっとややこしい計算して誤差がなくなるようにしています。

補正が無い場合、マウスカーソルに追随しません。

f:id:hiyokosabrey:20190112133222g:plain

 Map画像の表示スケールによってはさらに誤差がでます。

f:id:hiyokosabrey:20190112133903g:plain

f:id:hiyokosabrey:20190112134118g:plain

 

 

OnMouseWheel

ホイールを転がすと呼び出されます。と思いきや、これもコロコロしていない時でも常時呼ばれ続けます。

f:id:hiyokosabrey:20190112135027p:plain

なので、CanvasPanelにマウスカーソルが乗っているのを条件にします。

 

 

最後の関数。

OnMouseLeave

これは先の4つとは違ってEventノードとして、グラフ上に置かれます。

マウスが自身(このWidget)から離れたら呼び出されます。

f:id:hiyokosabrey:20190112224843p:plain

 ドラッグ中に限り判定したいので、フラグが True の時だけ処理します。

このイベントは、ドラッグしたままMap画像をどこまでも持っていけてしまうのを防ぐのがお仕事です。

 

これで、関数&イベントの準備は終了です。

 

仕上げに、EventConstruct にPivot計算の関数をつないでおきます。

 

f:id:hiyokosabrey:20190113102007p:plain

 

これでWidgetは完成です。

テスト用にレベルブループリントを ごにょごにょします。

 

Level Blueprint

 まず、いつもの Create Widget ノードでWidgetブループリントを読み込んで、キャンバスに追加する流れですが、Add to Viewport する前に、マップのテクスチャと、初期座標をセットしておきます。今回用意したマップ画像は "1024x1024" と "2048x2048"  の2枚。

f:id:hiyokosabrey:20190113102735p:plain

 この時点では、まだWidgetは目覚めていません。寝ながら関数が画像と初期位置を受け取ってます。まだレンダリングが開始されていない状態です。

 

 なので、お目覚めの Add to Viewport を。

f:id:hiyokosabrey:20190113103916p:plain

 Viewport内のポジションを変更して、マウス操作用の設定をしておきます。

 

 準備はこれで完了。

 

テストしてみます。

f:id:hiyokosabrey:20190113105756g:plain

マシンスペックなのか、ちょっと遅れてついてきますが、ズレて止まらないので悪くはないと思う。

 

 2枚並べて操作するとこんな感じ。

f:id:hiyokosabrey:20190113110153g:plain

キャプチャのレートが 15fps なのと、リサイズしてるのでちょっと粗い動きですが。

 

2種類の全く違う絵柄で、”間違い” または ”同じとこ” 探しゲームが作れそう。

 

Angleを変更する関数も用意すれば、回転もできそうです。

Render Transform をいじくればOK。

f:id:hiyokosabrey:20190113120117p:plain

マウスの操作部分をなくせば、普通にHUDのミニマップ表示としても使えそうです。

丸く抜くのはちょっと大変?

 

 

今回試してみて気づいたこと

 Viewportに追加する前に、表示を開始する前にテクスチャを渡すことが可能なので、ダミーの超軽量テクスチャでWidgetブループリントを用意しておくことができます。これはアセットのサイズも小さくできるし、汎用性も上がります。また画像などの重いアセットで、ゲーム進行によって読み替えが起こるような場合も、ロード周りの管理をWidgetから切り離すことで安全性が上がります。ということでMap画像を外から渡してもらう仕様にしました。

 ところが、このViewport追加の前に画像を受け取る方式で、ちょっと問題がありました。

 Map画像を移動した後にPivot 位置を再計算しないと、拡縮の中心がおかしくなるので、移動用の関数の最後の方にPivot位置を再計算させていたのですが、Viewportに追加する前にPivotを計算しても、エンジンがテクスチャのサイズに対する認識を更新してくれていない様子。

 Viewportに追加して画面に表示された直後に拡縮を行うと、え?という場所を中心にズームします。Viewportに追加した後に Pivot位置の再計算を行うと正常になります。なので今回 Event Constructのところで Pivot位置を計算することで一旦解決しました。

f:id:hiyokosabrey:20190113102007p:plain


  Widgetブループリントは  Add to Viewport の前後で、処理の振る舞いが変わる感じなので、まだまだ研究の余地はありますね。

 

 

◇ ◇ ◇

 

今回は以上です。

 

ではでは

ステキなマップ操作ライフを!

 

クリックした場所にメニューを 3

 リングメニューの表示がそれなりにカタチになったと思うけど、もう少しだけ作り込んでみます。リングメニューの呼び出し時にメニュー項目を任意のセットにできるようにして、閉じた後の通知部分を作ってなかったので追加します。あと選択中の見た目を強調するためのハイライト表示も追加します。

f:id:hiyokosabrey:20181224160800p:plain

 

改造スタート

 まずハイライト表示用のパーツを準備。リングの大きさをマテリアルで調整できる作りなので、同じ要領で専用のマテリアルを作ります。

f:id:hiyokosabrey:20181224004744p:plain

マテリアルができたら、前回作ったWidgetのキャンバスに Image を追加して、このハイライト表示用のマテリアルをセット。

f:id:hiyokosabrey:20181224025854p:plain

 今回 表示のたびにアイコンを新しく生成して並べて、終わったら消すという作りを採用したので、メニューアイテム用(アイコンが並ぶ)の CanvasPanel を追加しておきます。これはアイコンをクリアして消す際に、Clear Children というノードを使うためです。

 処理的に不都合があれば、あらかじめ最初に最大個数のImageを生成しておいて、ずっと使い続ける方法もあります。

 

 キャンバスは以上です。

 

 リングメニューを呼び出す際に、必要なアイテムをチョイスして呼び出せるようにします。呼び出す際に必要な情報として Integer型の配列変数を使ったやり取りです。

 前回までのつくりは、Viewportに置いた時点で表示するメニューアイテムは固定になってて、あとは表示用のカスタムイベントが呼ばれるのを待っていました。メニューの数が表示のたびに変動する仕様にするので、定数を設定をやめます。なので、Event Pre Constructはシンプルになります。

f:id:hiyokosabrey:20181224001142p:plain

Event Construct もスッキリです。

f:id:hiyokosabrey:20181224001412p:plain

 

新しくセットアップするためのイベントを用意します。ここで各種変数の初期化を行います。前回 定数初期化としていた部分を持ってきます。

f:id:hiyokosabrey:20181224002714p:plain

 受け取った値を保持する配列変数と、ハイライト表示用の変数(Float)を追加してます。続きに前回 Event Construct につないでいた処理を持ってきますが、配列を扱うので、ForLoop ノード から ForEachLoopノードに切り替えてます。

f:id:hiyokosabrey:20181224033435p:plain

 Loop処理が終わったら、ハイライトをメニューアイテムの数に応じた大きさにするために、マテリアルに数値を渡します(囲み部分)。

f:id:hiyokosabrey:20181224161618p:plain

リング1周ぶん(360°)が  1.0なので、アイテムの個数をで割ると割合が出ます。

 

 上図の続き。

ForEachLoop ノードを使うと、配列の番号(Index)と中身(Element)が同時に取り出せるので便利です。

f:id:hiyokosabrey:20181224034728p:plain

 

随分大きくなりました。

f:id:hiyokosabrey:20181224034053p:plain

 

次は、Event Tick

f:id:hiyokosabrey:20181224035851p:plain

ハイライト表示パーツを回転させる処理を間に追加。

以下は続き。

f:id:hiyokosabrey:20181224040921p:plain

ここも ForEachLoop に差し替えました。

 

表示が終わると、次に備えて片付けが必要になります。

退出のイベントを編集していきます。出現と同じくBranchノードを追加します。

f:id:hiyokosabrey:20181224120239p:plain

前回忘れていたわけではないのですが、ボタンの入力検出が必ず

押した(Pressed) 離した(Release) になる前提で考えてました。 後でいろいろ実験しているうちに問題が発覚。コンテキストメニューWindowsの右クリック)みたいにしてみようと思って、メニューを出せる場所と出せない場所を意図的に作ったら、出現イベントはキャンセルできても退出のイベントが走ってしまったので、Branchノードで、リングが出現していたら、という条件をここに追加することで解決できました。

 

 リングを消したあとの処理を追加します。

 まず退出のアニメーションにイベントトラック追加して、最終フレームで関数を呼び出します。

f:id:hiyokosabrey:20181224123011p:plain

 まず、 +TrackボタンEvent Track してトラックを追加したら、再生ヘッダーを最終フレームに移動させて、Add key(←◎→ の◎をクリック)すると、あまり目立たない色でマーカーが打たれます。

 そこで  右クリックPropartiesEvent のプルダウンを Unbound から関数を選択しますが、まだ用意してないので Create New Endpoint に切り替えると、関数が作られます。キーマーカーをダブルクリックしても関数は作られます。

f:id:hiyokosabrey:20181224124123p:plain

f:id:hiyokosabrey:20181224124214p:plain

見た目に関数と同じ色なのに、イベントっぽい扱いのようです。

 

ここでフラグを戻して通知してから配列変数の後片付けと、キャンバスの掃除です。

f:id:hiyokosabrey:20181224123032p:plain

 「変数」は一つの《値》しか持てなくて、内容が変化してもメモリの使用量は変わりません。一方「配列変数」は複数の《値》を持つことができる上に、その数が変動するので、当然メモリの占有量が増減します。Addノードを使うと配列変数の《値》を動的に増やすことができるので、気をつけて扱わないとメモリを食いつぶしてしまっているということが起こります。使い終わったらクリアしておきます。あと、ForEachLoop とか、Length ノードで、配列変数の持っている《値》の数を利用するので、次に備えて適宜リセットしないと計算があわなくなります。

 

 イベントディスパッチャーは最後にハイライトしていたメニューアイテムのIndex番号を通知するのが目的です。Inputsに通知したい値の型を登録して使います。

f:id:hiyokosabrey:20181224114909p:plain

 

これで一通り改造終了です。

動かしてみます。

f:id:hiyokosabrey:20181224155528g:plain

いい感じになってきました。

あとは選択結果を目立たせる演出を入れるか、Disable状態なアイテムの対応といったところでしょうか。

 

 今回ここまで作ってきて、マウスのようなポインティングデバイスのUIは難しい、と改めて思いました。

 ゲームパッドのような方向キーを使ってカーソルを移動させるタイプUIは、カーソルの表示されている位置が、そのままモードや状態を明示していて内部的にもモード遷移のコントロールがしやすい。それに比べてマウスなどタッチオペレーションとなると、モード遷移が一方向ではないので、状態の維持管理や連携の難易度がはね上がる印象。タスクベースな作りに逃げたくなる気持ちはすごく解る、気がする。今時のUIプログラマさんはスゴイなぁという話を皆でしていけば、UI開発のモチベーションも上がるし、UI開発のコスト意識も変わるといいなぁ、と夢想してみたり。

 

 マウスのボタンを押すときに、Viewportに置かれているパーツによってリングメニューの内容を変えるというのを実際作ってみたのですが、ちゃんと動作させるとこまでできなかったのが心残りです。仕様次第なところもあるので、これ以上作りこむのは汎用性を考えると、まだまだ検証に時間がかかりそうというということで、ひとまずここまでにします。急にひらめいたら記事にするかもしれません。

 

ではでは

ステキなリングメニューライフを!

 

 

おまけ

 テスト用に、3種の配列をを用意して、ローテーションするように組んでいます。

f:id:hiyokosabrey:20181224155945p:plain

  関数の中ということで、ローカル変数(上図の New Local Var 0)を利用しています。配列をクリアしなくても、この関数の外に出たら中身は破棄されるので安心。適当な名前ですが上図の New Var 1 についてはカウンタとして利用したいので普通の変数です。値が常に保持されています。

 

 

マテリアルをちょこっといじるとハイライト表現の見た目を変えることができます。

f:id:hiyokosabrey:20181224162543p:plain

 

 f:id:hiyokosabrey:20181224162641p:plain

 エッジがシャープになりました。



クリックした場所にメニューを 2

前回の続きです。

マウス座標を取得して、そこにWidgetを表示することができました。次はアイコンを並べていきます。

f:id:hiyokosabrey:20181212235226g:plain

 

 アイコンのテクスチャとマテリアル

↓このようなアイコンのテクスチャを用意。サイズは256x256で、1つが 64x64なので16個分持つことができます。

f:id:hiyokosabrey:20181213214603p:plain

これをマテリアルを使って切り出せるようにします。

f:id:hiyokosabrey:20181213215229p:plain

アイコンの並びは、開発途中で、入れ替わったり使わなくなることがよくあるので、Index番号で管理できると便利です。その番号をマテリアルが受け取って計算するようにしています。Fmodは 剰余(割った余り)Divide は割り算、Floor は小数以下切り捨てをするノードです。

 

マテリアルができたら、ブループリントの編集です。

 

Widgetブループリント

Event Pre Construction で変数の初期化をしています。

新しく、Integer型が1つ、Float型が2つ、Text型が1つ、計4つの変数を用意。

f:id:hiyokosabrey:20181213223323p:plain

「定数」は、一度定義したら最初から最後まで値の変わらない値です。メニューアイテムの数が決まっている前提で、アイコン同士の間隔(角度)と、マウス判定用の範囲(角度)をここで事前に計算させています。「角度」と「ラジアン」混在してたりするので頭悪い感じですが、その辺はいったん棚に上げておいていただけると助かりまする。

 

 前回の記事で書いていませんが、今回のリングメニューはいつでも呼び出せるので、Viewportに常駐させる想定で作っています。出現と退出は Visible ←→ Collapse を使っているので、この初期化処理は1回だけ走ることになります。コンテキストメニューのように動的にアイテム数が変わるようにするのであれば、関数化すれば対応できます。

 

次に、Event Construct でアイコンを並べます。

ちょっと大きいので画像を分割して、前半部分から。

f:id:hiyokosabrey:20181213231048p:plain

 For Loop でループさせます。 ループ回数の指定が Last Index なので、アイテムの個数から -1 しないといけないのが面倒です・・・。First Index を1にするという手もあるのですが、Index番号を 計算に使用するので、 0 始まりの方が都合がいいのです。

 

 画像の右端にあるのは、Create Dynamic Material Instance ノードで、指定したマテリアル アセットから、マテリアルインスタンスダイナミックを生成します。WIdgetブループリントでよく使う Get Dynamic Material ノードとは、引数にマテリアルのついたUMGのImageパーツを渡して使うところが異なっています。今回は、Imageパーツをキャンバスに置かずにブループリントから生成しているので、このようなマテリアルの扱い方になっています。

 

後半部分。

f:id:hiyokosabrey:20181213233023p:plain

今回 アイコンの番号は素直にForLoop の Index を使っていますが、別途配列を用意すればアイコンの並びを管理できます。例えば下図のような感じになります。

f:id:hiyokosabrey:20181213234046p:plain

 

マクロの中身はこんな感じ。三角関数のサインとコサインが登場します。

f:id:hiyokosabrey:20181213235350p:plain

Sinθ と Cosθ の θに角度(ラジアン)を入れてXとYの値にすると円周上のポイントになります。Sin、Cos はそのままだと  -1 ~ 1 の値を返すので、半径を掛けて円を大きくしています。

いろいろ補正しているので、ややこしい感じですが、補正を無しだと ↓ こうなります。スッキリ。

f:id:hiyokosabrey:20181214000506p:plain

 

これをキャンバスで確認すると、

f:id:hiyokosabrey:20181214000641p:plain

キャンバスの左上が (0, 0)原点なので当然ズレます。

リングの大きさを 320 としているので、その半分のサイズがズレ幅になります。ここも最初に定数化しておく方がいいと思います。右下に +160 足せばポジションは収まりそうです。メニューの開始位置を上にしたいので、90度 = π/2 = π*0.5 を足しています。

 最後に、好みですが、逆時計回りにするために、Y座標を求める Sin にマイナスの半径を掛けています。

f:id:hiyokosabrey:20181214002038p:plain

 

 

ここで一旦確認してみます。

f:id:hiyokosabrey:20181213234832g:plain

よしよし。

 

 

最後の仕上げ

マウスの座用からハイライトさせる

EventTick で処理します。常時という訳にはいかないので、フラグでコントロール

リングメニューが出ている間だけ動くようにして、結果を新しく用意した変数に取り置き。

f:id:hiyokosabrey:20181214004250p:plain

 

関数の中身はこうなってます。

f:id:hiyokosabrey:20181214005328p:plain

最初にマウスのボタンを押した場所をVector2D型の変数に保存しているので、今の場所から引き算することで差分が出ます。つまり出現場所からどれだけマウスが動いたかが分かります。その場所から、 atan2 という演算ノードを使うことで、角度を求めることができるのです。

f:id:hiyokosabrey:20181214234029p:plain

 戻り値(ReturnValue)は Radians(θ)とDegrees(°)の2タイプあります。

ラジアンだとうまく説明できる気がしないので、Degreesの方を選択しています。

 

基本的な扱い方なら、 A と B の軸と角度の関係は↓のようになります。

f:id:hiyokosabrey:20181214233050p:plain

 

Viewportの座標空間は左上が原点で、見えているのはプラスの領域になります。なのでここで差分をとると↓のようになります。

f:id:hiyokosabrey:20181215075153p:plain

Y軸の符号が逆になります。(現在位置からスタート位置を引いているのでX軸は問題なし)

今回リングのてっぺんをIndexの0にしたりしてるので、いろいろ試してみた結果、アレンジして atan2 ノードに渡すことにしました。アレンジの内容はY軸の符号を逆にして、A=Y / B=X のところを、 A=X / B=Y に変えています。出てきた角度は下図のようになってます。

 

f:id:hiyokosabrey:20181214233141p:plain

角度をIndex番号にする際に、マイナスの値は使いづらいので、+180します。

f:id:hiyokosabrey:20181215000650p:plain

これを、最初に定数化しておいた値で計算していくのですが、簡単な例で図にしていきます。

 

 まずメニューアイコンが 8個の場合だと、45という数値が得られます。

 

360° ÷ 8個 = 45°  

f:id:hiyokosabrey:20181215081637p:plain

この範囲にマウスカーソルの角度がある場合、そのアイコンを選択していることにすればいいのです。なのでカーソルのいる場所を角度にして、45で割って小数点以下切り捨てれば、メニューのIndexにマッチした数が得られそうです。

 

でも、上の図だとアイコンのまん中に区切り線がきています。

本当は下図のようにしたいのです。

f:id:hiyokosabrey:20181215082501p:plain

さぁ、困ったぞ。判定用に角度を調節するか、と考えたけど、これ以上角度をいじるのは灰色の頭脳がとろけるチーズになりそうだったので、そこは諦めることにして、最終的に「もう半分にしたらどうか?」という発想にたどり着きました。

 

45を得てから2で割るのもノードが増えて処理がもったいないので、360ではなく180を割ることにします。

180° ÷ 8個 = 22.5°

 

半分なるとどうなるかというと、

f:id:hiyokosabrey:20181215084301p:plain

この考え方で、角度を 22.5で割って、小数点以下切り捨てると下図のようになります。

f:id:hiyokosabrey:20181215085625p:plain

例えば、 atan2 が 30°という値を返してきたら、30 ÷ 22.5=1.3333・・・

なので、小数以下切り捨てると 1 という数字になります。

 

ここまでの説明はマクロの中身では下の明るくなっている部分にあたります。

f:id:hiyokosabrey:20181215090416p:plain

 

まだ 0~15 なので、これをどうにかして 0~7 にしないといけません。これがマクロの右上の部分。

f:id:hiyokosabrey:20181215090736p:plain

0~15 を半分にして、小数点以下を切り上げ、それをアイテムの個数で割った余りが答えになります。

まず半分(BPでは 0.5を掛けてます)にして・・・

f:id:hiyokosabrey:20181215091150p:plain

小数点以下切り上げます。Ceil(シール)を使います。

f:id:hiyokosabrey:20181215091352p:plain

だいぶいい感じです。

あとは一番上の 8 が 0 になってくれれば完璧。

8はアイテムの個数と同じ、ここは 剰余先生の出番です。

f:id:hiyokosabrey:20181215091935p:plain

ようやく、角度がいい感じにIndex値になりました。

 

この値をフォーカスの判定に使います。イベントグラフに戻って続き。

左端にマクロがあります。

f:id:hiyokosabrey:20181215092721p:plain

マクロで得た値は一旦変数に保存します。

 

ForLoopで何度も判定するので、マクロから戻り値を直接つなげてしまうと、何度も無駄にマクロの計算が走ってしまいます。

f:id:hiyokosabrey:20181215093306p:plain

ForLoopで0から順番にチェックしていって、同じ値のものだけハイライト処理をします。

 

ではさっそく確認。

f:id:hiyokosabrey:20181215094125g:plain

 

5個のとき。

f:id:hiyokosabrey:20181215094410g:plain

 

16個!

f:id:hiyokosabrey:20181215094716g:plain

 

なんとか完成です。

アイコンにマウスイベントをセットすると、四角形でしか判定できないし、アイコンの無い部分の判定が難しくなります。また重なると優先の問題が発生します。

ちょっと計算が多くて大変そうに見えますが、角度で判定すればマウスイベントは不要にできます。さらに外側にサブメニューなんかのリングを出す場合は、中心からの距離を判定に加えればよさそうです。なんかリングメニューが攻略できた気になってきた。

 

もっと頭のいい方法があると思うけど、いったんこれで満足しておこう。

ふぅ。

 

いつものことだけど、作ってからいろいろ思いついたりするので、次回もう少しだけ続きを書こうと思います。

 

ラジオからはクリスマスソングが流れてます。

ではでは

ステキな リングメニューライフを!

 

 

 

 

クリックした場所にメニューを

 ゲームパッド操作のUIは今までたくさん作ってきた。最初からマウス操作で設計することはなく、まずゲームパッド用のUIで問題なく遊べるとこまで作りきる。そして後から少ない工数と期間で必要最小限の箇所にマウスのアタリを追加する。

 そんな申し訳程度の対応ではさすがに最近は心が痛むので、UE4で一人もくもく会を開いているところです。

 今回作ってみたのはリングメニュー。見た目に「パイメニュー感」が弱いのでリングメニューと呼んでます。パイメニューは最近だとRDR2で実装されてるのが確認できます。アナログスティックと相性がよかったり、タッチなどポインティング操作にも対応可能な便利なやつです。今回作った仕様としては、

  • 画面の適当な場所でマウスの右ボタン押下で表示開始
  • ボタンを離すと選択終了
  • メニューの数を変更可能(※アイコンの密度と処理が許せば何個でも)
  • キャンセルはない(※メニューのどれかを「閉じる」にして対応可)

といった内容。

f:id:hiyokosabrey:20181209205852g:plain

 マウスのアタリ(ヒットエリアと言った方がいいのかな)を長方形(UMGのパーツ形状に依存)でしか取れなさそうで、いろいろ試してみた結果、今の自分のスキルではUMGを使って作れるのは上のようなものが精いっぱいでした。

 

 てなわけで、試行した結果を書いておきます。参考になれば幸いです。

 

 

マウスイベント

まずはマウスでポイントした位置情報が必要です。

適当なWidgetブループリントを一つ新しく作って、カスタムイベントを用意します。

f:id:hiyokosabrey:20181209213631p:plain

↑とりあえず値を確認するためのものです。

 

マウスのポジションにはいろんな取得方法が用意されているようです。

ひとつはマウスイベントから取得する方法。

f:id:hiyokosabrey:20181209223721p:plain

マウスボタンを押下したときのイベント、On Mouse Button Downという関数をオーバーライドすると、MouseEventというパラメータピンを利用できます。そこからPointerEventカテゴリのノード2種、Get Screen Space PositionGet Last Screen Space Position を使うとスクリーン上のマウスポジションを取得できます。使い分けについてはまたいずれ検証してみたいです。上図のような利用だと、両者は同じ値を返します。

ちなみにこの方法は、キャンバスに何かしら配置している部分のみでしか検出しないので、今回のリングメニューUIには向いてないようです。

 

 

つぎは、通常の関数で取得する方法。

On Mouse系のイベントと違って、好きなタイミングで場所を選ばずに検出できます。

キーワード get mouse でサーチすると3つほどヒット。

f:id:hiyokosabrey:20181209213314p:plain

 この3つのノードをカスタムイベントにつないだら、

テストのためにレベルブループリントからInputイベントで呼び出して確認してみます。

f:id:hiyokosabrey:20181212204456p:plain

 

 

まずは上から Get Mouse Position on Platformの場合

f:id:hiyokosabrey:20181209225329p:plain

これは、Viewportではなく物理的な画面内でのマウスのポジションを返してきます。画面解像度より小さいウィンドウ表示だとおかしな値に見えます。

↓下図は解像度1920x1080のモニタを使っている場合。

f:id:hiyokosabrey:20181210212017p:plain

 

Get Mouse Position on Viewport の方は、

f:id:hiyokosabrey:20181209231330p:plain

ウィンドウの大きさに関係なく、設定されているWidgetの解像度でマウスポジションを返してきます。デフォルトだと1920x1080。

f:id:hiyokosabrey:20181210214028p:plain

 

3つ目の Get Mouse Position Scaled by DPI も 上図同様に Viewportの左上が(0,0)で、右下が設定した解像度になるようです。

Pure型でPlayerをつないでやる必要があるので、つなぎ方は↓このようになります。

f:id:hiyokosabrey:20181210221619p:plain

2番目の ~ on Viewport と、この ~ DPI でウィンドウサイズをぐりぐりしながら値を比較してみたところ、同じ値が返ってきたので、とりあえず使いやすそうな2番目の Get Mouse Position on Viewport で作ることに決めました。

公式のドキュメントにも "Use GetMousePositionOnViewport() instead."  (代わりに~を使いなさい)と書いてあるのでそうすることにします。

 

リングを作る

クリック位置が分かるようになったので、その場所に表示するWidgetを用意します。

リングはテクスチャを使わずにマテリアルで用意します。

f:id:hiyokosabrey:20181212212130p:plain

VectorToRadialValueノード は距離と角度によった値を提供してくれます。 

円系の画像は無駄が多いので容量の節約になるのと、ピクセルという制約が無いのでリングの太さやシャープさなんかが後からいくらでも調節できるので便利です。最終的に見た目が確定してしまえばグレースケールのテクスチャと差し替えてみて負荷の小さい方を取ればOKでしょう。

一応4つある Const ノードの説明を図にしておきます。

VectorToRadialValueの Linear Distanceピンは最初↓のような値が出ています。

f:id:hiyokosabrey:20181212213858p:plain

白い四角は表示されたときのサイズとします。どんなサイズでもこの値は変わりません。外側の薄い青色は値の範囲を分かりやすくするために塗っています。

この値を、SmoothStepを使って、大小2つの円を作ります。

f:id:hiyokosabrey:20181212222044p:plain

Minの値は、限りなくMaxに近い値で、ボケずにジャギらない程度になるよう加減します。

SmoothStepの Min と Max は Photoshopのグラデーションをイメージする分かりやすいと思います。近いとシャープになって、離れるとボケます。

f:id:hiyokosabrey:20181212222625p:plain

で、大きい方の円を OneMinus で反転してから、2つの円を掛け算すれば完成。

f:id:hiyokosabrey:20181212223145p:plain

白は1、黒は0です。

掛け算の基本。

1×0=0 または 0×0=0 または 0×1=0

1×1=1

です。

f:id:hiyokosabrey:20181212223841p:plain

Final Color を白にしておくと、UMGで自由なカラーが付けられます。

これを、UMGでImageに張り付けます。

 

UMG

まずは一つにまとめるためのキャンバスパネルを用意します。

f:id:hiyokosabrey:20181212224507p:plain

Anchorは中央。ここに サイズ 320x320 の Image と TextBlock をひとつづつ入れます。Image にはリングのマテリアルをセットします。

リングの中央がキャンバスのPivotになるように、Alignmentの値を調整します。

f:id:hiyokosabrey:20181212224750p:plain

あとは適当に、出現と退出のアニメーションを作ります。

 

 

アニメーションの再生と停止

アニメーションができたら、ブループリントの編集です。

まず変数を2つ用意します。

f:id:hiyokosabrey:20181212225750p:plain

重複再生(連打)防止と、クリックした位置を覚えておくためのものです。

isActive は 初期値を False にしておきます。

 

次に

アニメーションの再生中に別のアニメーションを再生するときに強制的に停止するマクロを用意します。

f:id:hiyokosabrey:20181212230128p:plain

引数(Inputピン)を WidgetAnimation にしておくことで、汎用的に使えます。

再生中に限り強制的に停止させます。再生中でなかったら何もせず抜けます。

 

でこれらを使って、出現と退出のカスタムイベントを2つ用意します。

まずは出現イベント。

f:id:hiyokosabrey:20181212230751p:plain

 すでに出現していれば無視するようにBranchノードで判定します。

まだ出現していなければすぐにフラグを立てます。

クリックした位置を変数に入れて、その位置にキャンバスを持って行って出現アニメーションを再生する流れです。

 

つぎに退出イベント。

 こちらはシンプル。f:id:hiyokosabrey:20181212231241p:plain

 

あまりモタつかせたくないので、アニメーション再生中でも強引に、出現と退出ができるようにしています。出現で完全に開ききる前に退出の動きになると、アニメーションの見た目に不都合が出る場合は、BPで対策を入れるか、尺を短めにすることをお勧めします。

 

この辺でひとまずテストします。

レベルブループリントで Widgetの描画と、入力の処理を行います。

まずViewportに表示して、さらにマウスカーソルの表示を有効にします。

f:id:hiyokosabrey:20181212232601p:plain

Set Input Mode Game And UI ノードは マウスの有効範囲とかドラッグ操作をどうするか設定できるノードです。

 基本 Do Not Lock ですが、ドラッグ中にカメラが動いてほしくないので、 Pressed の判定中は、 Lock on Capture にしています。f:id:hiyokosabrey:20181212233304p:plain

これで試してみると・・・

f:id:hiyokosabrey:20181212235226g:plain

 無事うまくいってるようです。

 

 次回は、円状にアイコンを並べて、マウスの角度をみてハイライトする処理について書きます。

 

ではでは

すてきなマウスポジションライフを!

 

 

あとがき

 大体動作する状態になったので、そろそろ記事を書こうとしていたらお気に入りのUSBファンが動かなくなった。PCの冷却ファンにファンガードと直接ゴム足がねじ止めされてるやつで、置き方を選ばないのが嬉しい。見たらケーブルが切れてた。ラップトップでUE4をいじってるとかなり発熱するので、USBファンを利用してるんだけど、この時期は扇風機を回すとさすがに寒い。唯一となった冷却装置が機能しなくなったのでUE4をいったん終了。もう一度結線したら直るかなと思ったけど、はんだゴテを持ってなかったのであきらめた。結局ヨドバシカメラで同じやつが売ってたので購入。構造が頼りないのでまた切れそうな気もするけど、間に合わせということにして、早めに代替機の情報を集めよう。

 

 

 

スライダーを作る《本編》

 続きです。

 トグルスイッチのUIパーツを作り始めて、今はスライダーを作っています。デザイナとプログラマが作業するアセットで被らないようにできればいいなと思って模索していましたが、さっそく諦めました。てへぺろ

 コンパクトにしたいというデザインを優先したのも理由の一つですが、スライダーの操作がクリック処理だけじゃダメなのもあって、やっぱりイベントディスパッチャーとバインドを使った方がいいということに至りました。むしろ積極的に使っていった方がいいUIが作れそうだと感じました。このあたりの振る舞いって、作ってみないと気づけない事があるなぁ、と改めてUE4のありがたみを実感しています。

 

ということで今までの記事のリンク

前回の記事までで、一応分業体制は維持できていると思います。

f:id:hiyokosabrey:20181119001938p:plain

関数名や、パラメータ名などの多少の情報共有はありますが、アセットを双方が編集する機会はなくても大丈夫なはず。

 

・・・

 壁壁壁

今回スライダーUIを実装するにあたって、いくつかの壁にぶつかりました。

まず一つ目の壁が、このちっちゃいスライダーをどうやって扱いやすい大きさにするかでした。そこに出した答えがポップアップウィンドウです。設定ウィンドウ的なものが開いたら、まずはクリックするところから始まるのは変えられないので、トグルスイッチ同様クリックしたら何かしらの応答が必要。レイアウトを崩さずに操作可能であることをユーザーに伝えるには最適、という判断です。

 

 次にぶつかった壁があります。

 ユーザーが必ず変更の意思を持ってクリックするとは限らないので、うっかりの場合などはキャンセルできないと、ストレスになります。 

 ポップアップウィンドウを閉じるときに、「決定して閉じる」と「キャンセルして閉じる」という2拓の壁が立ちはだかったのです。「はだかる」って漢字表記があるのか気になったので調べたら「開かる」って書くみたいですね。すみません余談でした。

 ユーザー操作のフォーカスがポップアップウィンドウに移っている以上、メインだったwb_main は待機状態。ポップアップウィンドウが閉じられれば、何らかの結果を受け入れる必要があります。変更するのか、しないのか。その意思が判明した時点で処理が分岐することになります。この解決方法がイベントディスパッチャー。

 最初ポップアップウィンドウ以外をクリックしたら決定して閉じて、右クリックしたらキャンセルして閉じる。という仕様だったのですが、伝わらないし、ポップアップが出ているときに他の項目をクリックしたら閉じてすぐに開くのか、はたまた無視するのか、どちらがいいのか判断がつかない。ベタに決定とキャンセルのボタンを配置することにしました。

 

 そして最後の壁、それはドラッグ操作です。

 最初自前でやってやるぜ!って意気込んでみたものの、ちょっとブログに乗せるにはしんどいものが出来上がりました。これでは読むのもしんどいし、参考にするにもちょっと面倒な感じ。そこで既存のスライダーコンポーネントを使うことにしました。素直な使い方はしていませんけど。

 

 できたのがこれです。

f:id:hiyokosabrey:20181121011923p:plain

 

前回使用していたテクスチャにパーツを追加しました。

サイズはそのままで、OK、Cancel ボタンが加わって、ウィンドウの下敷きが縦長になってます。

f:id:hiyokosabrey:20181121014938p:plain  f:id:hiyokosabrey:20181121014950p:plain

(↑実データを原寸でPNGにしてるのでPhotoshopで再構成すれば使えると思います)

前回の方法で、マテリアルインスタンスを作って、パーツを切り出します。

 

 ポップアップウィンドウ用に新しくWidgetブループリントを用意します。

 キャンバスに、ポップアップウィンドウの大きさのキャンバスを置いて、中にImageを置きます。この Image の Anchor は 縦横にストレッチするやつにします。親キャンバスのサイズを変更すると追随するようにします。ポップアップウィンドウの下敷きになります。

前回の記事で用意したマテリアルからインスタンスを作成して、UVを切り出します。

f:id:hiyokosabrey:20181121231045p:plain

上から、Tiling U、V、Offset U、V の順です。

UV切り出しの計算が合っていれば↓このようなプレビューなります。無理やり正方形に引き延ばされます。

f:id:hiyokosabrey:20181121231427p:plain

この切り出したパーツを、キャンバスのImageパーツにセットします。

f:id:hiyokosabrey:20181121231501p:plain

 Draw As の設定を BOX にすると、ちゃんと9スライスのグリッドとして扱ってくれます。

f:id:hiyokosabrey:20181121231616p:plain

この上にパーツを置いていきます。

値を確認するための TextBlock、スライダーのバー、Min と Max を表すアイコン、まずはこれらを配置。中央揃えにするのでAnchorは

f:id:hiyokosabrey:20181122000546p:plain がオススメ。さらに それぞれのパーツの Pivot X を 0.5 にすると、左右対称に配置しやすくなるので超オススメします。 X座標が単にプラスかマイナスかになる。

f:id:hiyokosabrey:20181122000241p:plain

 Min と Max のアイコンは飾りです。これもマテリアルインスタンスでUVを切り出します。

f:id:hiyokosabrey:20181122001446p:plain

TextBlock は Is Variable にチェックを付けて中央に置きます。

 

スライダーのバーは、専用のマテリアルを用意。

f:id:hiyokosabrey:20181122001630p:plain

テクスチャを使わずに、2色のゲージです。Threshold (閾値)という名前の ScalarParameter を用意して、ブループリントからいじれるようにしています。

UMGのスライダーコンポーネントに適用が可能ですが、ブループリントからマテリアルにアクセスさせてくれないので、このような構造にしました。

f:id:hiyokosabrey:20181122002155p:plain

ここに、スライダーコンポーネントを重ねてるように配置します。

f:id:hiyokosabrey:20181122002346p:plain

置いてみるとものすごく小さいのでびっくりです。

f:id:hiyokosabrey:20181122002554p:plain

設定は Detailsタブから行います。とりあえず長さ(Slot > Size X)を先のImageパーツに合わせたら、StyleにあるBar Imageの描画(Draw As)を None にします。

f:id:hiyokosabrey:20181122003326p:plain

 

スライダーのハンドル(つまみ)のデザインもマテリアルインスタンスでUVを切り出せば差し替えることができます。Style の Thumb Image。

f:id:hiyokosabrey:20181122004332p:plain

f:id:hiyokosabrey:20181122004718p:plain

"Style" では "Thumb Image" だけど、 "Appearance" では "Slider Handle" って言ってるので、最初見たとき 「???」ってなった。

 

結局スライダーのバーを色分けしたかったので、UMGのスライダーコンポーネントをカスタマイズしました。いじったのは2か所。

・バーは、別のImageパーツで代替するために非表示にした。

・ハンドル(つまみ)をオリジナルのデザインに差し替えた。

 

 あとは、

決定ボタンとキャンセルボタンを追加すればUMGは終了です。

ちょっと楽するために、これもUMGのボタンコンポーネントを使います。

f:id:hiyokosabrey:20181123011950p:plain

2つ並べてマテリアルインスタンスで切り出したテクスチャをセットします。

f:id:hiyokosabrey:20181123014952p:plain

マウスイベントがあらかじめ用意されているので、それに合わせてセットします。

f:id:hiyokosabrey:20181123015116p:plain

通常で白、マウスオーバーで、うっすらカラーが入って、クリックで暗めのカラー、という設定です。

 

 

 UMGの各種コンポーネントには専用のイベントがいくつか用意されています。利用方法についてもいくつかあって、今回は2つのタイプを使います。

 

まず1つめ。

ボタンコンポーネントの詳細設定の一番下に、イベントを追加する部分があるので、

On Clicked イベントを追加します。

f:id:hiyokosabrey:20181123020335p:plain

クリックすると、自動的にGraph編集に切り替わります。赤いイベントノードが置かれているので、ここにイベントディスパッチャーを用意してつなぎます。

f:id:hiyokosabrey:20181123020601p:plain

イベントディスパッチャーは、エディタの左側、My Blueprintタブの一番下から作ります。

f:id:hiyokosabrey:20181123214355p:plain

クリックしたら、適当に名前をつけて、下のDetailsタブから、Inputsピンをひとつ追加します。

f:id:hiyokosabrey:20181123215602p:plain

 

 スライダーの値を管理するために、Float型の変数をひとつ作ります。

f:id:hiyokosabrey:20181123215925p:plain

この変数と、イベントディスパッチャーを、On Clickedイベントノードにつなぎます。

f:id:hiyokosabrey:20181124063909p:plain

 

f:id:hiyokosabrey:20181124064125p:plain

これで、ポップアップウィンドウから、決定 か キャンセル の通知を発行する仕組み(イベントディスパッチ)が用意できました。

 

 ポップアップウィンドを閉じたり開いたりという動作を、ポップアップウィンドウ側が行う場合は、ここに閉じるアニメーションをつなぎます。今回の記事では、説明が長くなりそうなので、ポップアップウィンドウを呼び出す側で表示の制御をします。

 

次にスライダーバーのマテリアルをいじるために、MID(ダイナミックマテリアル)を用意します。

f:id:hiyokosabrey:20181124070439p:plain

 

 イベント利用タイプ2つめ。

 スライダーにもボタン同様にUMGのコンポーネントとして、いろいろとイベントが用意されていますが、ここはイベントノードではなく、バインドの形で On Changed Value イベントを利用します。

f:id:hiyokosabrey:20181124071409p:plain

 

 上記2つのイベント処理は実際には処理の順番を基準に考えて使い分けるといいです。バインドの場合、イベントを受け付けるタイミングが制御できるのがウリで、入退場イベントがある場合に最適。バインドの解除には Unbind ノードを使います。

赤いイベントノードを使う方(緑色のボタンから作る)は、対象のパーツが表示されていれば、いつでもどこでもイベントの受付ができるのがウリですが、演出アニメーションの再生中など、操作してほしくないタイミングの場合があると注意が必要です。

 

 

一通りイベントが用意できたので、スライダーからの値を受け取って見た目に反映する関数を用意します。

f:id:hiyokosabrey:20181124080739p:plain

 setValue という名前の関数で作り始めたら、スライダー自身も同じ名前の関数を最初から持ってました。

 スライダーの値を受け取って、 最初の方に用意した Float型の変数に格納しておきます。Lerpノードに入っている値は、スライダーのハンドル(つまみ)を端っこに動かした際の、TextBlock の Render > Tlanslation 値です。ここで変数 Value が更新されます。スライダーが変更されるたびに 0.01.0 の値が入ってきます。

 

この関数を、スライダーの On Changed Value のバインドノードにつなぎますが、ちょっと手順が大事です。

f:id:hiyokosabrey:20181124082406p:plain

Custom Eventノードは、先に用意しておいてもいいですが、パラメータのピンが一致していないとつながらないので、上記の手順をオススメします。

 

以上でポップアップウィンドウ完成です。

f:id:hiyokosabrey:20181124084249p:plain

 

仕上げに、呼び出し側でポップアップウィンドウを呼び出して値を受け取れるようにします。

 

User Createdのカテゴリに作ったポップアップウィンドウが増えているのでキャンバスに配置します。

f:id:hiyokosabrey:20181124083842p:plain

最初からは表示しないので、Visivility は Collapse にしておきます。

 

Graphに移って、変数を2つ追加します。

f:id:hiyokosabrey:20181124094855p:plain

 Boolean型は、ポップアップウィンドウが出ている間、他のクリックを封印するためです。初期値は falseにしておきます。

 wb_sliderg型は、アクティブになっているスライダーを特定するための器です。

 

 変数が用意できたので、ポップアップウィンドウが出ているかどうかで処理を分けます。前回の On Mouse Button Down 関数の一番最初にブランチを挿入します。

f:id:hiyokosabrey:20181124111617p:plain

これでポップアップが出ている間、クリック判定を無視します。

スライダーがクリックされたら、の処理を追加します。

f:id:hiyokosabrey:20181124112703p:plain

 

ノードが増えてきたので、ポップアップを出す処理をマクロにします。

f:id:hiyokosabrey:20181124101509p:plain

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

これをつないで、↓のようになります。

f:id:hiyokosabrey:20181124112857p:plain

右端上のノードは ポップアップウィンドウの 閉じる通知を受け取るための Bind ノードです。ポップアップウィンドウを開いた直後にBindしておきます。開くと同時に閉じるための準備をしておくイメージ。

右端下の Create Event ノードは、通常のGraph内だと、Custom Event がつなげられるのですが、ここは関数内で Add Custom Event ができないためにの ノードです。

Select Function のままでコンパイルすると、エラーになりますが、まずは先に進めます。

 f:id:hiyokosabrey:20181124132747p:plain

 

この一式をスライダーの数ぶんつなぎます。↓全体図

f:id:hiyokosabrey:20181124113813p:plain

次に、BindしたCreate Event のためのイベントを用意します。

 

これはポップアップウィンドウから 値を受け取って反映したあと、閉じる処理になります。

カスタムイベントを用意して下図のようにつなぎます。

f:id:hiyokosabrey:20181124133430p:plain

イベントディスパッチャーから渡されてくるパラメーターは、Float型がひとつだけ。0以上の値なら、ユーザーが変更して決定ボタンを押した証拠。-1ならキャンセルボタンを押したことになります。値を反映するかしないかの分岐のあと、ポップアップウィンドウが閉じられるので、Bindを解除→非表示→ フラグを元に戻す という処理になります。

 

これを並べたスライダーの数ぶん(=CreateEventの数)用意します。

Unbindノード以降は共通なのでまとめると以下のようになりました。

f:id:hiyokosabrey:20181124133957p:plain

カスタムイベントが用意できたので、ようやく Create Event ノードのSelect Function を変更できます。プルダウンリストになってるので探します。

リストに表示されている内容は、プログラム的な表記になってます。

イベント名(パラメータ名)

f:id:hiyokosabrey:20181124135542p:plain

これでコンパイルするとエラーが出なくなります。

 

以上でポップアップウィンドウの実装完了です。

再生してみると、

f:id:hiyokosabrey:20181124190844g:plain

結構いい感じだと思うのですがいかがでしょうか?

 

 キャンセルという仕様を無くしてしまえれば、ポップアップウィンドウの外をクリックして反映→閉じる。という振る舞いをさせることもできます。設定をリセットするボタンを置くのもありですが、タッチ操作を考えると不用意に触ってしまうのを防ぐのは難しいので、キャンセルボタンの存在は意外に重要かも、と思ったりしてます。

 入力デバイスによって期待される振る舞いを、空気を読むように実装するのは難しいですね。

 

 作業分担的なチャレンジのつもりが、イベントの発生タイミングと、階層のあるUIを効率よく扱うには、イベントディスパッチャーを使うと便利ですよ、というサンプルになりました。

慣れてくればそれほどややこしいものでもないと思うので、

 変動するUIの主従と、その瞬間に誰(プレイヤーおよびUIパーツ)がどこで何をしたらどうなるのか?

を想像しながら少しづつ振る舞いを試していけば、ブループリントだけでプロトタイプ的なものは作れると思います。作ってみて触ってみてを繰り返して、例外の存在に気づけたり、予期しない動きが新しいUIのヒントになったり、とにかく経験あるのみです。もっともっと面白いUIが出てきてほしいので、UIデザインをされている方にはぜひアンリアルエンジンでUIを遊んでみてほしいです。

 

というわけで今回はこの辺で。

 

ではでは

ステキなスライダーライフを!

 

 

あとがき1

UVの切り出しでは結構細かい小数が出てきます。割り切れるようなサイズでUVの範囲を調整しているので、256を掛けるとピクセル単位の値が出てきます。でもUE4の仕様で、表示桁数が多いと見た目に丸めて表示してくれることがあるので、このページに載せている画像の値を掛け算すると、小数のままになるものがあったりします。

 

 

あとがき2

この記事を書いている途中で、ぷちコンの参加賞が届きました。

よく見たらヒストリアスタッフさんによる「ぷち賞」を受賞していました!

ぱちぱちぱちぱち~

ステキな賞品ありがとうございます。大事に使わせてもらいます。

また次回頑張ろう。

 

 

 

 

 

 

 

次はスライダーを作る《準備》

以前にもスライダーを作って記事にしましたが、今回はマウス操作専用です。

前回のトグルスイッチと同じところに並べて使う想定のUIパーツなので、引き続き同じアセットもいじりつつアップデートしていこうと思います。

 

で、さっそく

テクスチャをひとまとめにしてしまおう

スライダーのパーツもひっくるめて一枚のテクスチャに収めることにします。

どうせ同じタイミングで読み込まれるんだったら、枚数が少ない方がいろいろ効率的です。

256x256のテクスチャにまとめました。左がRGB、右がアルファチャンネルです。

f:id:hiyokosabrey:20181115225148p:plain f:id:hiyokosabrey:20181115225138p:plain

まだ余裕がある状態ですが、追加要素のためにキープ、ということにしておきます。

テクスチャの切り出しが面倒なのが残念ではありますが、ゲームのロードが少しでも早く終わるなら、ユーザーの利益につながります。必要経費として頑張ります。

 

UVの範囲はレイヤーで管理しています。 

f:id:hiyokosabrey:20181117005108p:plain f:id:hiyokosabrey:20181117013615p:plain

 パーツごとに、適当なカラーで四角く塗り分けたレイヤーにパーツ名をしっかり付けて、レイヤーグループにまとめておきます。中のレイヤーは透明度100%でもレイヤーグループを半透明にすると上図のような状態にできます。

テクスチャとして書き出す際は、レイヤーグループをまるっと非表示にすればOK。

この方法だと、パーツの用途がはっきりするし、場所を入れ替えるときも重なりを回避できるし、アルファチャンネルの移動も確実に行えます。部分的に塗りつぶしたりするときもこの範囲レイヤーからの選択範囲が重宝するし、光彩なんかのボケ足の確認もしやすいので、私はいつもこの方法で管理しています。

 

さてさて

このテクスチャをUE4にインポートしたら、マテリアルを用意します。

f:id:hiyokosabrey:20181117010807p:plain

切り出しサイズを決める『Tiling』はMultiplyで、切り出す場所を決める『Offset』はAddノードで計算します。それぞれ U と V があるので、Scalar(スカラー)Parameter を4つつないだ方が、パラメーターの名前も付けられて分かりやすくなるのですが、Vector Parameterにすると、ブループリントから一気に4つの値が渡せるのでノードが少なくなります。

 

下のようにしても問題は無いです。

f:id:hiyokosabrey:20181117011902p:plain

 

マテリアルはこれで完成です。

 

 マテリアルインスタンスでパーツを切り出そう

次にこのマテリアルを親にして、インスタンスを作成します。

コンテンツブラウザのマテリアルアセットのアイコンの上で右クリックします。

f:id:hiyokosabrey:20181117012347p:plain

コンテキストメニューってやつですね。選択しているアイテムに合わせてメニュー内容が変わるという。ここからマテリアルインスタンスを作ります。

作ったらパラメータを編集します。

f:id:hiyokosabrey:20181117013106p:plain

値の計算方法は前回の記事で説明しているので省きます。

とにかくテクスチャサイズで割ると求められます。

f:id:hiyokosabrey:20181117015505p:plain

UMGで使用する パーツとして切り出したい数ぶんマテリアルインスタンスを用意していきます。

細かいパーツごとのパラメータは、次回の記事で書いていくとして、前の記事のトグルスイッチのマテリアルが修正になります。

セットされているテクスチャを今回のやつに変更したら、

f:id:hiyokosabrey:20181117104359p:plain

場所と横幅はそのままでテクスチャの高さだけが変更になったので、TexCoord ノードのパラメータを変更するだけです。

f:id:hiyokosabrey:20181117103914p:plain

 

トグルスイッチのマテリアルが変更できたら、

スライダー用のマテリアルを作ろう

いよいよスライダーのマテリアル。作り方はテクスチャアセットアイコンの上で右クリックです。Create Material して、マテリアルドメインを UserInterfaceに変更するとこまでは、目を瞑っててもできるくらいじゃないといけませんね。

私はできませんけどね。

で、内容はこんな感じ。

f:id:hiyokosabrey:20181117105001p:plain

テクスチャを部分的に切り出すので、TexCoordノードのパラメータは、

f:id:hiyokosabrey:20181117105149p:plain

場所が少しずれてるので Add ノードを入れて、OffsetV に 0.25 を固定値としてつないでいます。各パラメータをテクスチャに重ねるとこんなかんじ。

f:id:hiyokosabrey:20181117111143p:plain

スライダーは、ハンドル(つまみ)が左右に移動します。

それをトグルスイッチの時と同様にUVを動かして再現します。

テクスチャ的には、細かい小数点で管理された、完全にデザイナー都合のUV配置なので、ここで大活躍するのが Lerp ノードです。

Lerpノードの、ピンAとB にスライダー両端のOffsetU。AlphaピンをScalarParameterにして、ブループリントから値を受け取れるようにしています。

 

正式名称は Linear Interpolate といって線形補間してくれる便利なやつです。ちょっとわかりにくいかもしれないですが、 UV移動で スライダーの 0 から 満タン まで移動する場合の値の変化を図にしてみました。

f:id:hiyokosabrey:20181117133622p:plainf:id:hiyokosabrey:20181117134129p:plain

プログラマ的には、テクスチャ内のレイアウトに依存したUVアニメーションの調整なんてマッピラゴメンだと思います。Lerp ノードを使うことで、その辺の責任をしっかり取れるUIデザイナーになれるという訳です。

スライダーで扱う値はたいてい 0~100% のような『割合』が多いので、そのまま 0~1で制御できた方が分かりやすいし扱いやすいです。プログラマからは、0~1で値を受け取って、マテリアルの中で、いい感じに補正してやるのです。あとからテクスチャ内のレイアウトを変更しても、変更するのはマテリアルアセットのみです。UIデザイナーだけで完結できます。

 

スライダーのマテリアルができたので、Widgetを作っていきます。

 

スライダーのWidgetをつくろう

 まずはTextBlockと共にキャンバスに配置します。

f:id:hiyokosabrey:20181117211619p:plain

 スライダーの部分はトグルスイッチ同様に、マテリアルをセットします。

TextBlockはラベルと数値の2つ。それぞれ Is Variable にチェックを付けておきます。

 

キャンバスはこれで完成。

Graphに移動してさっそくGet Dynamic Material ノードで、スライダーとしてセットしたImageパーツから、Material Instance Dynamic を作ってお変数化しておきます。

f:id:hiyokosabrey:20181117225007p:plain

まずはスライダーのハンドルと値を変更するための関数。

f:id:hiyokosabrey:20181117212425p:plain

 Float型の値を、直接Text型にキャストするノードが ToTextノードです。小数部分はいらないので設定を少しいじります。▽をクリックするとこのノードの設定が変更できます。

f:id:hiyokosabrey:20181117223559p:plain

Rounding Mode の Half to Even は、端数を丸める処理をいくつかある中の一つです。

端数は小数部分で、 0.5 以下は切り捨てで 0 、0.51 以上は切り上げて 1 にしてくれます。

 

次に、スライダの初期設定をする関数を用意します。

f:id:hiyokosabrey:20181117224232p:plain

 テキストラベルを書き換えて、先に作っておいた、値を更新する関数を取り出してつなぎます。

 

これでWidgetは完成です。

前回のトグルスイッチと同じところに並べます。

f:id:hiyokosabrey:20181118014110p:plain

ストラクチャにも専用のパラメータを追加します。

f:id:hiyokosabrey:20181118014637p:plain

 

ストラクチャの更新ができたら、スライダーにも初期値をセットしてやります。

f:id:hiyokosabrey:20181118015330p:plain

ノードの位置を変えていますが、前回作った wb_main のイベントです。

 

今回はここまでにします。

表示を確認してみます。問題なければ値が反映されているはず。

f:id:hiyokosabrey:20181118015957j:plain

 

レイアウトと見た目の都合で、ミニスライダーになってしまいました。

とうわけで

クリックする部分は次回《解決編》で。

 

ではでは

ステキなスライダーライフを!

 

ストラクチャの書き換えについて

トグルスイッチをいじってて気づいたメモ。

前回の記事でストラクチャの内容を、一部だけ書き換える際に以下のようにしていたんだけど、

f:id:hiyokosabrey:20181107235718p:plain

部分だけを更新するノードがありました。

下図のこの部分を、

f:id:hiyokosabrey:20181114203308p:plain

Set Member in xxxx ノードに置き換えます。

ストラクチャ型の変数ノード(GET)から探すと見つかります。

f:id:hiyokosabrey:20181114203530p:plain

f:id:hiyokosabrey:20181114203735p:plain

このままだと値を渡せないので、Set Member ~ノードをフォーカスした状態で、Detailsタブを確認。

チェックボックスが並んでるので変更したい値のチェックボックスをONにします。

f:id:hiyokosabrey:20181114204214p:plain

f:id:hiyokosabrey:20181114204459p:plain

ノードに入力ピンが追加されるので、ここに接続すればOK。

 

Breakノード側は行き場をなくした出力ピンが寂しい状態なのですが、これを隠すことができます。

Breakノードをフォーカスした状態で、Detailsタブを見ると、Hide Unconnected Pins というボタンがあるのでクリック。

f:id:hiyokosabrey:20181114204924p:plain

これでかなりすっきりします。

f:id:hiyokosabrey:20181114205139p:plain

 

パラメータが増えるたびに、面倒な感じになるなと思ってたけど、ちゃんと用意されてました。てへぺろ

 

 

腰を痛めてしまい安静にするしかない状況で、長く座ってると辛いのですが、いくぶん楽になってきたので、記事更新。

スライダーを作っているので、近日公開します。

 

ではでは

すてきなストラクチャライフを!

イテテ