ゲームパッド操作のUIは今までたくさん作ってきた。最初からマウス操作で設計することはなく、まずゲームパッド用のUIで問題なく遊べるとこまで作りきる。そして後から少ない工数と期間で必要最小限の箇所にマウスのアタリを追加する。
そんな申し訳程度の対応ではさすがに最近は心が痛むので、UE4で一人もくもく会を開いているところです。
今回作ってみたのはリングメニュー。見た目に「パイメニュー感」が弱いのでリングメニューと呼んでます。パイメニューは最近だとRDR2で実装されてるのが確認できます。アナログスティックと相性がよかったり、タッチなどポインティング操作にも対応可能な便利なやつです。今回作った仕様としては、
- 画面の適当な場所でマウスの右ボタン押下で表示開始
- ボタンを離すと選択終了
- メニューの数を変更可能(※アイコンの密度と処理が許せば何個でも)
- キャンセルはない(※メニューのどれかを「閉じる」にして対応可)
といった内容。
マウスのアタリ(ヒットエリアと言った方がいいのかな)を長方形(UMGのパーツ形状に依存)でしか取れなさそうで、いろいろ試してみた結果、今の自分のスキルではUMGを使って作れるのは上のようなものが精いっぱいでした。
てなわけで、試行した結果を書いておきます。参考になれば幸いです。
マウスイベント
まずはマウスでポイントした位置情報が必要です。
適当なWidgetブループリントを一つ新しく作って、カスタムイベントを用意します。
↑とりあえず値を確認するためのものです。
マウスのポジションにはいろんな取得方法が用意されているようです。
ひとつはマウスイベントから取得する方法。
マウスボタンを押下したときのイベント、On Mouse Button Downという関数をオーバーライドすると、MouseEventというパラメータピンを利用できます。そこからPointerEventカテゴリのノード2種、Get Screen Space Position と Get Last Screen Space Position を使うとスクリーン上のマウスポジションを取得できます。使い分けについてはまたいずれ検証してみたいです。上図のような利用だと、両者は同じ値を返します。
ちなみにこの方法は、キャンバスに何かしら配置している部分のみでしか検出しないので、今回のリングメニューUIには向いてないようです。
つぎは、通常の関数で取得する方法。
On Mouse系のイベントと違って、好きなタイミングで場所を選ばずに検出できます。
キーワード get mouse でサーチすると3つほどヒット。
この3つのノードをカスタムイベントにつないだら、
テストのためにレベルブループリントからInputイベントで呼び出して確認してみます。
まずは上から Get Mouse Position on Platformの場合
これは、Viewportではなく物理的な画面内でのマウスのポジションを返してきます。画面解像度より小さいウィンドウ表示だとおかしな値に見えます。
↓下図は解像度1920x1080のモニタを使っている場合。
Get Mouse Position on Viewport の方は、
ウィンドウの大きさに関係なく、設定されているWidgetの解像度でマウスポジションを返してきます。デフォルトだと1920x1080。
3つ目の Get Mouse Position Scaled by DPI も 上図同様に Viewportの左上が(0,0)で、右下が設定した解像度になるようです。
Pure型でPlayerをつないでやる必要があるので、つなぎ方は↓このようになります。
2番目の ~ on Viewport と、この ~ DPI でウィンドウサイズをぐりぐりしながら値を比較してみたところ、同じ値が返ってきたので、とりあえず使いやすそうな2番目の Get Mouse Position on Viewport で作ることに決めました。
公式のドキュメントにも "Use GetMousePositionOnViewport() instead." (代わりに~を使いなさい)と書いてあるのでそうすることにします。
リングを作る
クリック位置が分かるようになったので、その場所に表示するWidgetを用意します。
リングはテクスチャを使わずにマテリアルで用意します。
VectorToRadialValueノード は距離と角度によった値を提供してくれます。
円系の画像は無駄が多いので容量の節約になるのと、ピクセルという制約が無いのでリングの太さやシャープさなんかが後からいくらでも調節できるので便利です。最終的に見た目が確定してしまえばグレースケールのテクスチャと差し替えてみて負荷の小さい方を取ればOKでしょう。
一応4つある Const ノードの説明を図にしておきます。
VectorToRadialValueの Linear Distanceピンは最初↓のような値が出ています。
白い四角は表示されたときのサイズとします。どんなサイズでもこの値は変わりません。外側の薄い青色は値の範囲を分かりやすくするために塗っています。
この値を、SmoothStepを使って、大小2つの円を作ります。
Minの値は、限りなくMaxに近い値で、ボケずにジャギらない程度になるよう加減します。
SmoothStepの Min と Max は Photoshopのグラデーションをイメージする分かりやすいと思います。近いとシャープになって、離れるとボケます。
で、大きい方の円を OneMinus で反転してから、2つの円を掛け算すれば完成。
白は1、黒は0です。
掛け算の基本。
1×0=0 または 0×0=0 または 0×1=0
1×1=1
です。
Final Color を白にしておくと、UMGで自由なカラーが付けられます。
これを、UMGでImageに張り付けます。
UMG
まずは一つにまとめるためのキャンバスパネルを用意します。
Anchorは中央。ここに サイズ 320x320 の Image と TextBlock をひとつづつ入れます。Image にはリングのマテリアルをセットします。
リングの中央がキャンバスのPivotになるように、Alignmentの値を調整します。
あとは適当に、出現と退出のアニメーションを作ります。
アニメーションの再生と停止
アニメーションができたら、ブループリントの編集です。
まず変数を2つ用意します。
重複再生(連打)防止と、クリックした位置を覚えておくためのものです。
isActive は 初期値を False にしておきます。
次に
アニメーションの再生中に別のアニメーションを再生するときに強制的に停止するマクロを用意します。
引数(Inputピン)を WidgetAnimation にしておくことで、汎用的に使えます。
再生中に限り強制的に停止させます。再生中でなかったら何もせず抜けます。
でこれらを使って、出現と退出のカスタムイベントを2つ用意します。
まずは出現イベント。
すでに出現していれば無視するようにBranchノードで判定します。
まだ出現していなければすぐにフラグを立てます。
クリックした位置を変数に入れて、その位置にキャンバスを持って行って出現アニメーションを再生する流れです。
つぎに退出イベント。
こちらはシンプル。
あまりモタつかせたくないので、アニメーション再生中でも強引に、出現と退出ができるようにしています。出現で完全に開ききる前に退出の動きになると、アニメーションの見た目に不都合が出る場合は、BPで対策を入れるか、尺を短めにすることをお勧めします。
この辺でひとまずテストします。
レベルブループリントで Widgetの描画と、入力の処理を行います。
まずViewportに表示して、さらにマウスカーソルの表示を有効にします。
Set Input Mode Game And UI ノードは マウスの有効範囲とかドラッグ操作をどうするか設定できるノードです。
基本 Do Not Lock ですが、ドラッグ中にカメラが動いてほしくないので、 Pressed の判定中は、 Lock on Capture にしています。
これで試してみると・・・
無事うまくいってるようです。
次回は、円状にアイコンを並べて、マウスの角度をみてハイライトする処理について書きます。
ではでは
すてきなマウスポジションライフを!
あとがき
大体動作する状態になったので、そろそろ記事を書こうとしていたらお気に入りのUSBファンが動かなくなった。PCの冷却ファンにファンガードと直接ゴム足がねじ止めされてるやつで、置き方を選ばないのが嬉しい。見たらケーブルが切れてた。ラップトップでUE4をいじってるとかなり発熱するので、USBファンを利用してるんだけど、この時期は扇風機を回すとさすがに寒い。唯一となった冷却装置が機能しなくなったのでUE4をいったん終了。もう一度結線したら直るかなと思ったけど、はんだゴテを持ってなかったのであきらめた。結局ヨドバシカメラで同じやつが売ってたので購入。構造が頼りないのでまた切れそうな気もするけど、間に合わせということにして、早めに代替機の情報を集めよう。