COVID-19のなんやかんやで緊張しながら日々過ごしていたらもうお盆の時期。心なしかセミの声も元気がないような気もする。近所の田んぼでは稲の穂もチラホラ見えはじめたのでまた花粉に苦しむことになる予感・・・。
先日 Twitterで、シアーで傾けたUIでもマウスオーバーを検出できるのか気になって試してみた、というのを呟きました。
動画はここ ↓ で確認できます
Shearを適用した状態で、マウスの検出が気になったのでサンプルを作って試してみた。#UE4 #ue4UMG pic.twitter.com/rIaKYV2Hpq
— みつまめ杏仁 (@MMAn_nin) 2020年8月10日
試してみようと思ったきっかけは、今までの体験が元になっています。
こういったナナメのデザインを作ると、
矩形の範囲で検出する方がコストが安く時間がかからないのに、なんてデザインしてくれるんだ、とプログラマに渋い顔をされたものです。
インハウスツールの場合、ツール開発者がよほど暇にならない限りデザイナーの気まぐれに先回りするような人はいないのが普通だとは思います。
さてUE4はどうかな~? フォートナイトでも見たし、大丈夫だろうきっと。
というわけで試して出来上がったものを記事にします。
用意したWidgetは2つ。
まずは子のWidgetから用意します。
UMGのキャンバスに 下敷きとなる Image を置いて、手前にアイコン用の Image とラベル用の TextBlock を置きます。
ヒエラルキーは以下の状態
ここで下敷きの Image に Shear(シアー)を適用します。
アイコンとテキストの位置がおかしくなるので整えます。
Hierarchyに最初から置かれている CanvasPanel を SizeBox でラッピングします。
CanvasPanel で右クリックして
Wrap With ... > SizeBox を選択
この操作で、CanvasPanel 以下を SizeBox の子供にします。一つ上の階層を増やして包むイメージで「Wrap」。
この階層構造をやめたいときは、SizeBox のところで右クリックして
Replace with ... > Replace With Child を選択 するとSizeBoxはいなくなります。
最上階のものはドラッグしても入れ替えができないようです。
ここからSizeBoxの調整をします。
UMGの編集用エリア(ビジュアルデザイナ)右上にあるボタンを切り替えます。
Desired か Desired on Screen にします。
これは描画の際のアスペクト比、解像度によるスケーリングをシミュレーションしたりしていろんなデバイスによる差を確認しながら、レイアウト作業をするためのスイッチです。
この 子Widgetの場合、並べるのは別の 親Widgetになるので、レイアウト作業としては画面に対してではなく、パーツ同士の位置関係まで、ということになります。
わかりやすくするために SizeBox(またはCanvasPanel) を選んだ状態で切り替えてみます。
「Desired」 は 「望まれた」 という意味合いなので、ここでの使い方としては
《 UE4は、SizeBoxの大きさとしては、このように認識しているよ 》
という程度の理解で大丈夫だと思います。
この子Widgetを 並べる際に、SizeBox の持つサイズが、 利用されることになります。
( ↑ 表示イメージ )
今の状態のままだと、左右にスキマがないのでくっついてしまいます。
後からでも調整できるのですが、編集の流れで行ったり来たりすると説明がややこしくなりそうなので、スキマの空け方を書いてしまいます。
ひとまず左右に 5 ずつ 空けることにします。
まず、SizeBoxはポジション等のSlot(Canvas Panel Slot) を持たないので、位置を変えることはできません。なので、中のImageパーツたちを移動させます。今回右に(X方向) +5 移動させました。フォームの値に +5 を追記してEnterキーを押すと加算してくれるので便利。
CanvasPanel の Render Transform で 移動させるとまとめて移動できて簡単ですが、移動アニメーションを考えるときにややこしくなるので、できるだけRender Transformは触らないでおきます。編集時のバウンディングボックス(点線の四角)が増えてしまうのも回避できます。後々の修正・調整のためにできる限りシンプルなデータ作成を心掛けたいものです。
中のパーツが右に移動した分、SizeBoxの幅が少し大きくなりました。
左に余白ができたので、今度は右側にも余白を足します。
ここで、SizeBoxの出番です。
今のSizeBox の幅を調べて、そこに +5 すればOK。
さて困りました。どうやってSizeBoxの幅を調べたものか・・・
いくつか試した方法があるのでメモとして残しておきます。
調べ方その1 Get Desired Size ノードで調べる
一旦 画面に表示して get Desired Size ノードを使うと精確な値が取れます。
SizeBoxの is Variable にチェックをつけておいて、グラフに下のようなノードをつなぎます。
Delayノードは確実に値を取るための保険です。値が取れればこのノードたちは不要になります。
適当に新規レベルを作成してViewportに描画。
PrintString が教えてくれます。
ブループリントに慣れててもちょっと面倒に感じます。
次はちょっと特殊な方法。
調べ方その2 マテリアルで調べる
先日ツイートしたネタを使います。
確かに!これは面白いですね~
— みつまめ杏仁 (@MMAn_nin) 2020年7月30日
Debugノードつないだらリアルタイムにサイズが取れるので、レイアウト考えるときに便利かもです pic.twitter.com/QtSeh4z5uO
このマテリアルは プロジェクトに1個作っておいて損はないやつです。
TexCoordノードのIndex を3 にして使います。
このマテリアルを、CanvasPanelの下に チェック用の Image を一つ追加して適用します。確認出来たら捨てるか非表示にしておくといいです。
表示順は一番手前。
アンカーをストレッチにしてスキマなく広げます。
この Image パーツに マテリアルをセットすればOK。
テクスチャをセットするところにマテリアルをセットできます。
ただ、この方法で気を付けないといけないのが、表示倍率。
Zoom が 1:1 (等倍) = 本来のサイズ になります。
この方法のいいところは、 マテリアルをセットしたImageパーツを追加するだけで、すぐに確認できるというところです。ブループリントを編集しないのでコンパイルの必要がありません。
調べ方その3 地道に探る
SizeBox の持つパラメータに、Max Desired Width というのがあります。
ここに値を入れていって、これ以上変化しなくなる最大値をみつけます。
最初に100とか200っみたいな値を入れてから、マウスで増減させます。
キャンバスの緑のラインが動くので、それが止まるところが最大値になります。
ラインの変化が止まったらマウスをすぐに止めます。ここからは手入力で少しずつ探っていきます。
・・・
208 を 207 に変更。 ラインは変化しない。
207 を 206 に変更。 ラインは変化しない。
206 を 205 に変更。 ラインは変化しない。
205 を 204 に変更。 ラインが動いた。
ということは 204 を 205 に変更。 ラインが動いた。
さらに 205 を 206 に変更。 ラインは変化しない。
ということでめでたく 205 という値をゲット! なかなか気の長い話です。
正直頭のいい方法かどうかは気になるところですが、余計なパーツを追加したりしないので、アセットはピュアな状態のままです。
とりあえずこの3つくらいしか思いつかなかったです。
他にいい方法があるかもなので、教えていただけると嬉しいです。
で、この 205 に +5 した値が最終の表示サイズになります。
これを SizeBox の 持つパラメータ Width Override にセットします。
これで左右に余白が生まれました。
( ↑ 表示イメージ )
キャンバスの役者が揃いました。
ここからはマウスカーソル(ポインタ)が乗った時の処理を作っていきます。
まず、どういった見せ方にするか考えます。
ふるまいとして求められるのは、
- マウスカーソル(ポインタ)が乗っていることをわからせたい
- 決定時の演出とは違う見せ方をしたい
このあたりで十分だと思います。
アクションゲームなどは、プレイ中にノンビリできないので、できるだけ目立つ動きで誤操作を起こさないように表示するのが大事です。またマウスカーソル(ポインタ)やゲームパッド操作の場合、選択肢をどれにしようかな~って迷うことができるのも特徴になるので、カーソル的なハイライト表現も重要な表示要素です。もちろん決定時の表現も大事なフィードバックなので、決定前と決定後のメリハリを考えることになります。
いろんな作り方があるのですが、今回はマテリアルを使ったカーソル表現をやってみます。
ヒントになったのはこちらのツイート。
#UE4Study
— 恒吉星光 (@seiko_dev) 2020年7月30日
UI用Materialで描画領域のpixel sizeが欲しい場合、TexCoord[3]で取れるとの事。https://t.co/3Su6Q1Wo1E
スゴイ裏技みを感ずるが、確かに「Imageの縦横幅や画面解像度に依存せず見た目のPixel幅が一定の枠」みたいな事ができた。 pic.twitter.com/4CEDlp3rLN
枠線を点滅させることにします。
( ↑ イメージ )
マテリアルはこんな感じ
部分を拡大しつつ説明していきます
まずは枠線を描く部分。
任意の太さの枠線を描画サイズから割り出しておいて、カスタムノードで判定し塗分けています。
左端のTexCoordノード(Uを押しながらクリック)は Detailsタブから Indexの値を 3に変更します。
次(右側)の TexCoordノードは デフォルトのまま使います。
赤いサムネイルのノードは カスタムノードです。
HLSLのシェーダーコードを自前で用意して使うことができます。
詳しくは公式ドキュメント↓
Custom 表現式 | Unreal Engine Documentation
と、こちら↓のエントリー
カスタムノードは Detailsタブをいじっていきます。
入力を3つに増やします。
Code のところに シェーダーコードを書きます。今回書いたのはこれです。
float f = (float)(Value>=Min)? 0 : 1;
float s = (float)(Value<=Max)? 0 : 1;
return (f+s);
? は 文字化けではなく 三項演算子ってやつです。Excelの VBAだったら Then に相当します。 : コロンはさしずめ Else ってとこですね。
オレンジ色は 入力ピンの名前です。fとsはなんとなく First とSecond です(適当)
UE4のフォーム内で改行する場合は、 Shift + Enter です。
もっと良い書き方がありそう・・・どなたかご教示いただけると嬉しいです。
で次は点滅部分
Time に 大きい数値を掛けると点滅が速くなって、0.5 など小さい値を掛けると ゆっくりになります。
ConstantBiasScaleノードは、一定の幅を持った値に対して、調整するときに便利なノードです。
計算結果から想像するにたぶん中身はこんな感じだと思います。
サインカーブは基本的に -1.0 ~ +1.0 の幅で変移するので、これを 0 ~ 1.0 の範囲に収めておかないと、マイナスの値の時に見た目がおかしくなりなります。(正規化とかNormalize とか言います)
Detailsタブから値を調整できますが、取り出したままつなぐだけで OK。
最後は、枠線の合成と着色する部分。
Addの後の Clampノードは 値が 1.0をオーバーしないようにしています。
真ん中あたりの BlinkFlag というScalarパラメータを掛け算しているのは、ブループリントから枠線の有り無しをコントロールするためです。
ピクセルのカラーやアルファを扱う場合、OnとOff をコントロールするのに簡単なのが ゼロとイチ(= 1.0)を掛けることです。
ゼロを掛けるとカラーの場合は黒く、アルファの場合は透明にできます。今回カラーを Lerp(Linear Interpolate)で着色するので、枠線の部分がゼロになることで、見た目に枠線は消失します。
一方 1.0 をかけると何も変化しないので、スイッチの代わりになるということです。
0~1.0 の範囲 の 値が流れてくるのを、 Lerpノードが着色します。
Alphaのピンから入ってきた値が、0なら A のカラー。 1.0 なら B のカラー。
0.5 なら AとB の中間のカラー。 という風に、AとB2つのカラーを線形補間した値が出力されます。今どきのお風呂場にはたいていついてると思いますがお湯と水をブレンドできる混合栓みたいなやつのイメージ。Photoshopだとグラデーションマップが近いです。
このマテリアルを、UMGのキャンバスに戻ってベースのImageパーツにセットします。
この辺でWidgetブループリントを編集します。
まず、マテリアルにアクセスするために、ダイナミックインスタンスマテリアルを用意します。
ReturnValueピンから ドラッグして、 Promote to Variable(変数に昇格) を選択します。
MID_Base と命名。
次に この ダイナミックインスタンスマテリアルを使ってマテリアルのパラメータをいじる関数を新しく用意します。
Select系のノードは、 型ごとにいろいろ用意されています。マテリアルのパラーメータは Scalarを選んだので、Select Float ノードを選択。Bool値(TrueかFalseのどちらか)によって分岐するように値を出し分けることができます。
その結果を Set Scalar Parameter Value ノードに渡します。
Selectノードを使わない場合はこうなります↓
個人的にSelectノード使う方が、調整と修正がラクに感じるのでよく利用します。
関数名は switchBorderBlink と命名。
次に、マウス用のイベントをオーバーライドします。
使うのはこの2つ。
Enter はマウスカーソル(ポインタ)が乗った時、Leave は離れたときに呼び出されるイベントになります。
最初から用意(ビルトイン)されている便利なやつですが中身がないので、上書き(オーバーライド)して使います。
2通りの取り出し方があります。
ひとつめ
Functions の Override ボタン(隠されてる)から探す方法。
ふたつめ
右クリックして検索する方法。
Add Event > Mouse の中に カテゴライズされています。
取り出せたら、用意しておいた関数をつなぎます。
片方のBool型のピン にチェックを付けたら準備完了です。
ひとまずテスト用のレベルで確認してみます。
適当に New Level を作って、Widgetを表示します。
Create Widgetノードをとりだして、表示したいWidget をセット。ReturnValue ピンを Add to Viewport ノードにつなげば表示されます。
マウスカーソルを常時表示したいので、まず Player Controller ノード を取り出します。そこから Set Show Mouse Cursor を取り出して、True にします。
コンパイルして問題なければ再生してみます。
表示されている位置が左上になってしまいますが、ちゃんと動作しているのがわかります。
素晴らしいですね。どういう作りになってるのかわかりませんが、それなりに頑張ってくれていると思います。
せっかくなので、セレクトメニューとして見せられるとこまでを書いていこうと思うのですが、結構長くなったので今回はこの辺までにします。
ではでは
ステキなマウスオーバーライフを!