続きです。
トグルスイッチのUIパーツを作り始めて、今はスライダーを作っています。デザイナとプログラマが作業するアセットで被らないようにできればいいなと思って模索していましたが、さっそく諦めました。てへぺろ。
コンパクトにしたいというデザインを優先したのも理由の一つですが、スライダーの操作がクリック処理だけじゃダメなのもあって、やっぱりイベントディスパッチャーとバインドを使った方がいいということに至りました。むしろ積極的に使っていった方がいいUIが作れそうだと感じました。このあたりの振る舞いって、作ってみないと気づけない事があるなぁ、と改めてUE4のありがたみを実感しています。
ということで今までの記事のリンク
前回の記事までで、一応分業体制は維持できていると思います。
関数名や、パラメータ名などの多少の情報共有はありますが、アセットを双方が編集する機会はなくても大丈夫なはず。
・・・
壁壁壁
今回スライダーUIを実装するにあたって、いくつかの壁にぶつかりました。
まず一つ目の壁が、このちっちゃいスライダーをどうやって扱いやすい大きさにするかでした。そこに出した答えがポップアップウィンドウです。設定ウィンドウ的なものが開いたら、まずはクリックするところから始まるのは変えられないので、トグルスイッチ同様クリックしたら何かしらの応答が必要。レイアウトを崩さずに操作可能であることをユーザーに伝えるには最適、という判断です。
次にぶつかった壁があります。
ユーザーが必ず変更の意思を持ってクリックするとは限らないので、うっかりの場合などはキャンセルできないと、ストレスになります。
ポップアップウィンドウを閉じるときに、「決定して閉じる」と「キャンセルして閉じる」という2拓の壁が立ちはだかったのです。「はだかる」って漢字表記があるのか気になったので調べたら「開かる」って書くみたいですね。すみません余談でした。
ユーザー操作のフォーカスがポップアップウィンドウに移っている以上、メインだったwb_main は待機状態。ポップアップウィンドウが閉じられれば、何らかの結果を受け入れる必要があります。変更するのか、しないのか。その意思が判明した時点で処理が分岐することになります。この解決方法がイベントディスパッチャー。
最初ポップアップウィンドウ以外をクリックしたら決定して閉じて、右クリックしたらキャンセルして閉じる。という仕様だったのですが、伝わらないし、ポップアップが出ているときに他の項目をクリックしたら閉じてすぐに開くのか、はたまた無視するのか、どちらがいいのか判断がつかない。ベタに決定とキャンセルのボタンを配置することにしました。
そして最後の壁、それはドラッグ操作です。
最初自前でやってやるぜ!って意気込んでみたものの、ちょっとブログに乗せるにはしんどいものが出来上がりました。これでは読むのもしんどいし、参考にするにもちょっと面倒な感じ。そこで既存のスライダーコンポーネントを使うことにしました。素直な使い方はしていませんけど。
できたのがこれです。
前回使用していたテクスチャにパーツを追加しました。
サイズはそのままで、OK、Cancel ボタンが加わって、ウィンドウの下敷きが縦長になってます。
(↑実データを原寸でPNGにしてるのでPhotoshopで再構成すれば使えると思います)
前回の方法で、マテリアルインスタンスを作って、パーツを切り出します。
ポップアップウィンドウ用に新しくWidgetブループリントを用意します。
キャンバスに、ポップアップウィンドウの大きさのキャンバスを置いて、中にImageを置きます。この Image の Anchor は 縦横にストレッチするやつにします。親キャンバスのサイズを変更すると追随するようにします。ポップアップウィンドウの下敷きになります。
前回の記事で用意したマテリアルからインスタンスを作成して、UVを切り出します。
上から、Tiling U、V、Offset U、V の順です。
UV切り出しの計算が合っていれば↓このようなプレビューなります。無理やり正方形に引き延ばされます。
この切り出したパーツを、キャンバスのImageパーツにセットします。
Draw As の設定を BOX にすると、ちゃんと9スライスのグリッドとして扱ってくれます。
この上にパーツを置いていきます。
値を確認するための TextBlock、スライダーのバー、Min と Max を表すアイコン、まずはこれらを配置。中央揃えにするのでAnchorは
がオススメ。さらに それぞれのパーツの Pivot X を 0.5 にすると、左右対称に配置しやすくなるので超オススメします。 X座標が単にプラスかマイナスかになる。
Min と Max のアイコンは飾りです。これもマテリアルインスタンスでUVを切り出します。
TextBlock は Is Variable にチェックを付けて中央に置きます。
スライダーのバーは、専用のマテリアルを用意。
テクスチャを使わずに、2色のゲージです。Threshold (閾値)という名前の ScalarParameter を用意して、ブループリントからいじれるようにしています。
UMGのスライダーコンポーネントに適用が可能ですが、ブループリントからマテリアルにアクセスさせてくれないので、このような構造にしました。
ここに、スライダーコンポーネントを重ねてるように配置します。
置いてみるとものすごく小さいのでびっくりです。
設定は Detailsタブから行います。とりあえず長さ(Slot > Size X)を先のImageパーツに合わせたら、StyleにあるBar Imageの描画(Draw As)を None にします。
スライダーのハンドル(つまみ)のデザインもマテリアルインスタンスでUVを切り出せば差し替えることができます。Style の Thumb Image。
"Style" では "Thumb Image" だけど、 "Appearance" では "Slider Handle" って言ってるので、最初見たとき 「???」ってなった。
結局スライダーのバーを色分けしたかったので、UMGのスライダーコンポーネントをカスタマイズしました。いじったのは2か所。
・バーは、別のImageパーツで代替するために非表示にした。
・ハンドル(つまみ)をオリジナルのデザインに差し替えた。
あとは、
決定ボタンとキャンセルボタンを追加すればUMGは終了です。
ちょっと楽するために、これもUMGのボタンコンポーネントを使います。
2つ並べてマテリアルインスタンスで切り出したテクスチャをセットします。
マウスイベントがあらかじめ用意されているので、それに合わせてセットします。
通常で白、マウスオーバーで、うっすらカラーが入って、クリックで暗めのカラー、という設定です。
UMGの各種コンポーネントには専用のイベントがいくつか用意されています。利用方法についてもいくつかあって、今回は2つのタイプを使います。
まず1つめ。
ボタンコンポーネントの詳細設定の一番下に、イベントを追加する部分があるので、
On Clicked イベントを追加します。
クリックすると、自動的にGraph編集に切り替わります。赤いイベントノードが置かれているので、ここにイベントディスパッチャーを用意してつなぎます。
イベントディスパッチャーは、エディタの左側、My Blueprintタブの一番下から作ります。
クリックしたら、適当に名前をつけて、下のDetailsタブから、Inputsピンをひとつ追加します。
スライダーの値を管理するために、Float型の変数をひとつ作ります。
この変数と、イベントディスパッチャーを、On Clickedイベントノードにつなぎます。
これで、ポップアップウィンドウから、決定 か キャンセル の通知を発行する仕組み(イベントディスパッチ)が用意できました。
ポップアップウィンドを閉じたり開いたりという動作を、ポップアップウィンドウ側が行う場合は、ここに閉じるアニメーションをつなぎます。今回の記事では、説明が長くなりそうなので、ポップアップウィンドウを呼び出す側で表示の制御をします。
次にスライダーバーのマテリアルをいじるために、MID(ダイナミックマテリアル)を用意します。
イベント利用タイプ2つめ。
スライダーにもボタン同様にUMGのコンポーネントとして、いろいろとイベントが用意されていますが、ここはイベントノードではなく、バインドの形で On Changed Value イベントを利用します。
上記2つのイベント処理は実際には処理の順番を基準に考えて使い分けるといいです。バインドの場合、イベントを受け付けるタイミングが制御できるのがウリで、入退場イベントがある場合に最適。バインドの解除には Unbind ノードを使います。
赤いイベントノードを使う方(緑色のボタンから作る)は、対象のパーツが表示されていれば、いつでもどこでもイベントの受付ができるのがウリですが、演出アニメーションの再生中など、操作してほしくないタイミングの場合があると注意が必要です。
一通りイベントが用意できたので、スライダーからの値を受け取って見た目に反映する関数を用意します。
setValue という名前の関数で作り始めたら、スライダー自身も同じ名前の関数を最初から持ってました。
スライダーの値を受け取って、 最初の方に用意した Float型の変数に格納しておきます。Lerpノードに入っている値は、スライダーのハンドル(つまみ)を端っこに動かした際の、TextBlock の Render > Tlanslation 値です。ここで変数 Value が更新されます。スライダーが変更されるたびに 0.0~1.0 の値が入ってきます。
この関数を、スライダーの On Changed Value のバインドノードにつなぎますが、ちょっと手順が大事です。
Custom Eventノードは、先に用意しておいてもいいですが、パラメータのピンが一致していないとつながらないので、上記の手順をオススメします。
以上でポップアップウィンドウ完成です。
仕上げに、呼び出し側でポップアップウィンドウを呼び出して値を受け取れるようにします。
User Createdのカテゴリに作ったポップアップウィンドウが増えているのでキャンバスに配置します。
最初からは表示しないので、Visivility は Collapse にしておきます。
Graphに移って、変数を2つ追加します。
Boolean型は、ポップアップウィンドウが出ている間、他のクリックを封印するためです。初期値は falseにしておきます。
wb_sliderg型は、アクティブになっているスライダーを特定するための器です。
変数が用意できたので、ポップアップウィンドウが出ているかどうかで処理を分けます。前回の On Mouse Button Down 関数の一番最初にブランチを挿入します。
これでポップアップが出ている間、クリック判定を無視します。
スライダーがクリックされたら、の処理を追加します。
ノードが増えてきたので、ポップアップを出す処理をマクロにします。
startSlider という名前にしました。
これをつないで、↓のようになります。
右端上のノードは ポップアップウィンドウの 閉じる通知を受け取るための Bind ノードです。ポップアップウィンドウを開いた直後にBindしておきます。開くと同時に閉じるための準備をしておくイメージ。
右端下の Create Event ノードは、通常のGraph内だと、Custom Event がつなげられるのですが、ここは関数内で Add Custom Event ができないためにの ノードです。
Select Function のままでコンパイルすると、エラーになりますが、まずは先に進めます。
この一式をスライダーの数ぶんつなぎます。↓全体図
次に、BindしたCreate Event のためのイベントを用意します。
これはポップアップウィンドウから 値を受け取って反映したあと、閉じる処理になります。
カスタムイベントを用意して下図のようにつなぎます。
イベントディスパッチャーから渡されてくるパラメーターは、Float型がひとつだけ。0以上の値なら、ユーザーが変更して決定ボタンを押した証拠。-1ならキャンセルボタンを押したことになります。値を反映するかしないかの分岐のあと、ポップアップウィンドウが閉じられるので、Bindを解除→非表示→ フラグを元に戻す という処理になります。
これを並べたスライダーの数ぶん(=CreateEventの数)用意します。
Unbindノード以降は共通なのでまとめると以下のようになりました。
カスタムイベントが用意できたので、ようやく Create Event ノードのSelect Function を変更できます。プルダウンリストになってるので探します。
リストに表示されている内容は、プログラム的な表記になってます。
イベント名(パラメータ名)
これでコンパイルするとエラーが出なくなります。
以上でポップアップウィンドウの実装完了です。
再生してみると、
結構いい感じだと思うのですがいかがでしょうか?
キャンセルという仕様を無くしてしまえれば、ポップアップウィンドウの外をクリックして反映→閉じる。という振る舞いをさせることもできます。設定をリセットするボタンを置くのもありですが、タッチ操作を考えると不用意に触ってしまうのを防ぐのは難しいので、キャンセルボタンの存在は意外に重要かも、と思ったりしてます。
入力デバイスによって期待される振る舞いを、空気を読むように実装するのは難しいですね。
作業分担的なチャレンジのつもりが、イベントの発生タイミングと、階層のあるUIを効率よく扱うには、イベントディスパッチャーを使うと便利ですよ、というサンプルになりました。
慣れてくればそれほどややこしいものでもないと思うので、
変動するUIの主従と、その瞬間に誰(プレイヤーおよびUIパーツ)がどこで何をしたらどうなるのか?
を想像しながら少しづつ振る舞いを試していけば、ブループリントだけでプロトタイプ的なものは作れると思います。作ってみて触ってみてを繰り返して、例外の存在に気づけたり、予期しない動きが新しいUIのヒントになったり、とにかく経験あるのみです。もっともっと面白いUIが出てきてほしいので、UIデザインをされている方にはぜひアンリアルエンジンでUIを遊んでみてほしいです。
というわけで今回はこの辺で。
ではでは
ステキなスライダーライフを!
あとがき1
UVの切り出しでは結構細かい小数が出てきます。割り切れるようなサイズでUVの範囲を調整しているので、256を掛けるとピクセル単位の値が出てきます。でもUE4の仕様で、表示桁数が多いと見た目に丸めて表示してくれることがあるので、このページに載せている画像の値を掛け算すると、小数のままになるものがあったりします。
あとがき2
この記事を書いている途中で、ぷちコンの参加賞が届きました。
よく見たらヒストリアスタッフさんによる「ぷち賞」を受賞していました!
ぱちぱちぱちぱち~
ステキな賞品ありがとうございます。大事に使わせてもらいます。
また次回頑張ろう。