2019年が明けました。公私共にいろいろ節目的な年になりそうですが、アンリアルエンジンを使ったUIの研究だけは続けていこうと思うので、今年も引き続きよろしくお願いいたします。
今回マウスで操作するミニマップを考えてみました。ふと、Pivotをちゃんと計算してやったらいけるんとちゃう? と思いついたのがきっかけです。考えたといってもごく普通のよく見かけるやつです。大事なのはUE4で作ってみること。UMGで作るとしたらどのように?という疑問に一つでも答えを見つけておきたいという使命感のようなものでやってます。需要があればいいのですが・・・。
仕様としては、
- 任意の長方形でクリッピング可能
- 見えているのはマップの一部分
- マップ上にマウスカーソルがあるときだけ操作可能
- ドラッグでスクロール
- ホイールで拡大縮小
です。
まずはキャンバスから。
新たに CanvasPanel を追加します。
クリッピングは この CanvasPanel で行うので、適当なサイズで用意。
上のサンプルでは何となく 640x480にしてます。
このCanvasPanelの Clipping 設定を、 Clip to Bounds に変更します。
これで、はみ出た部分を隠せます。
この CanvasPanel に2つの Image を子供として追加します。 下敷き と マップ画像 です。
マップ画像用のImageパーツには、Size to Content にチェックを付けておきます。
マップ画像によってサイズが変動しても対応できるようになります。
UMGは以上です。
Widgetブループリントを編集していきます。
まず用意した変数は以下。上段の4つは Vector2D型です。
関数を用意します。
マップテクスチャを受け取ってセット、そしてそのサイズを変数に格納するお仕事。
画像サイズを取得するために、 Match Size にチェックを付けます。
次は、マップImage のポジションを受け取って変数に格納。さらにマイナスにしてから反映するお仕事。変数の値は プラスのままで扱います
マップの画像はCanvasPanelの下では、左上が(0, 0)なので、マイナスの値にしてやる必要があります。
マイナスにしないといけないのは、あくまでもUMG側の表示都合なので、変数には加工しない状態で扱っておく方が考えやすいし、デバッグしやすいと思います。
次の関数は Pivot の計算と反映。
CanvasPanel=見えている範囲 の中心を基準に拡大縮小するので、Pivot 位置が常に変動します。Map画像のポジションに、計算して求めておいた OffsetPivot を足すと、CanvasPanel の中心に相当する場所が決まります。それを Map画像のサイズで割ると、Map画像における Pivot位置が新しく決定します。
OffsetPivot は下図のイメージ。
これとMap画像のポジションを足すと、 Map画像の Pivot 位置が 座標として判明します。
判明したPivot位置は 0.0 ~1.0 の範囲で扱わないといけないので、Map画像のサイズで割ってやります。テクスチャのUV値を求めるのと同じ考え方ですね。
ここからは、マウスの挙動に関する関数。
すでに用意されているので、Override して進めます。使うのは5つ。
長いリストメニューがポップするので、必要なやつを選択します。
OnMouseButtonDown
マウスのボタンが押下されると呼び出される関数。
ブランチノードで判定してるのは、押したのが 左ボタン で、且つキャンバスの上にカーソルが乗っているかどうかです。2つ以上の条件を同時に判定する場合は、AND Boolean ノードを使うとブランチノードが1つにまとめられるのでお得。
無事条件をクリアしたら、フラグを立てて、ボタンを押したポジションを変数に取り置きします。
is Mouse Button Down ノードを使うと、何のボタンが押されたかを調べることができます。 調べ方は、指定したボタン毎に問い合わせて確認することになります。今回は左ボタンのみの扱いなので、左ボタンについてだけ確認しています。
ちなみにマウスボタンは5つまで判定できるようです。最近のマウスは親指のあたりに 「進む」 と「 戻る」に相当する機能が振られたボタンが付いてることが多いです。
OnMouseButtonUp
マウスのボタンから指が離れたときに呼び出される関数。
マウスのボタンが解放されたということは、ドラッグが終わったということで、フラグを False に戻します。
OnMouseMove
マウスの移動を検出すると呼び出されます。
というのが一般的だと思うのですが、UE4の場合、この関数は常時走ってるようです。マウスから手を放しても呼び出されるので、人間が感知できないほどの微細な振動を拾ってるのか、マウスの仕様がそうなっているのか、UE4の仕様かはわかりません。
常時処理されるとそれなりに負荷になるので、前述の OnMouseButtonDown でフラグを立てた後、そのフラグが倒れるまでの間だけ処理するようにします。さらに、マウスが動いていないと判断された場合は何もしません。
作ってみて気づいたのですが、スケールが掛かっていると、座標の計算がおかしくなります。UMGがレンダリングする際の計算順序に依存します。またマウス座標の取得はViewportの解像度は関係なくモニタの解像度に依存します。なので、ちょっとややこしい計算して誤差がなくなるようにしています。
補正が無い場合、マウスカーソルに追随しません。
Map画像の表示スケールによってはさらに誤差がでます。
OnMouseWheel
ホイールを転がすと呼び出されます。と思いきや、これもコロコロしていない時でも常時呼ばれ続けます。
なので、CanvasPanelにマウスカーソルが乗っているのを条件にします。
最後の関数。
OnMouseLeave
これは先の4つとは違ってEventノードとして、グラフ上に置かれます。
マウスが自身(このWidget)から離れたら呼び出されます。
ドラッグ中に限り判定したいので、フラグが True の時だけ処理します。
このイベントは、ドラッグしたままMap画像をどこまでも持っていけてしまうのを防ぐのがお仕事です。
これで、関数&イベントの準備は終了です。
仕上げに、EventConstruct にPivot計算の関数をつないでおきます。
これでWidgetは完成です。
テスト用にレベルブループリントを ごにょごにょします。
Level Blueprint
まず、いつもの Create Widget ノードでWidgetブループリントを読み込んで、キャンバスに追加する流れですが、Add to Viewport する前に、マップのテクスチャと、初期座標をセットしておきます。今回用意したマップ画像は "1024x1024" と "2048x2048" の2枚。
この時点では、まだWidgetは目覚めていません。寝ながら関数が画像と初期位置を受け取ってます。まだレンダリングが開始されていない状態です。
なので、お目覚めの Add to Viewport を。
Viewport内のポジションを変更して、マウス操作用の設定をしておきます。
準備はこれで完了。
テストしてみます。
マシンスペックなのか、ちょっと遅れてついてきますが、ズレて止まらないので悪くはないと思う。
2枚並べて操作するとこんな感じ。
キャプチャのレートが 15fps なのと、リサイズしてるのでちょっと粗い動きですが。
2種類の全く違う絵柄で、”間違い” または ”同じとこ” 探しゲームが作れそう。
Angleを変更する関数も用意すれば、回転もできそうです。
Render Transform をいじくればOK。
マウスの操作部分をなくせば、普通にHUDのミニマップ表示としても使えそうです。
丸く抜くのはちょっと大変?
今回試してみて気づいたこと
Viewportに追加する前に、表示を開始する前にテクスチャを渡すことが可能なので、ダミーの超軽量テクスチャでWidgetブループリントを用意しておくことができます。これはアセットのサイズも小さくできるし、汎用性も上がります。また画像などの重いアセットで、ゲーム進行によって読み替えが起こるような場合も、ロード周りの管理をWidgetから切り離すことで安全性が上がります。ということでMap画像を外から渡してもらう仕様にしました。
ところが、このViewport追加の前に画像を受け取る方式で、ちょっと問題がありました。
Map画像を移動した後にPivot 位置を再計算しないと、拡縮の中心がおかしくなるので、移動用の関数の最後の方にPivot位置を再計算させていたのですが、Viewportに追加する前にPivotを計算しても、エンジンがテクスチャのサイズに対する認識を更新してくれていない様子。
Viewportに追加して画面に表示された直後に拡縮を行うと、え?という場所を中心にズームします。Viewportに追加した後に Pivot位置の再計算を行うと正常になります。なので今回 Event Constructのところで Pivot位置を計算することで一旦解決しました。
Widgetブループリントは Add to Viewport の前後で、処理の振る舞いが変わる感じなので、まだまだ研究の余地はありますね。
◇ ◇ ◇
今回は以上です。
ではでは
ステキなマップ操作ライフを!