前回の更新からちょっと間が空きましたが、ぷちコン作品を作ってました。
で、先日とあるツイートに目がとまったのです。
Howdy, gamedevs! Ever wondered, how to prevent widgets in #UE4 from going off screen like this? I think this is a common issue and here's how to do it! #indiegame #indiedev #UnrealEngine #roguelike #Tutorial pic.twitter.com/OR7hEyisgq
— Ghostic Games (@ghosticgames) March 3, 2019
これは、Worldに存在する Actor の上に Widget を表示するという内容で、さらに対象の Actor が画面外に出ても、画面の端に張り付くようにするという 素晴らしいものです。
3Dオブジェクトの上にWidgetを表示するという方法は、すでにブログ等で公開されているので、目新しさは無いですが、画面の外に出ないようにする工夫に心惹かれました。
ツイ主の @ghosticgames さんにブログで説明してもよいかコメントで確認したら、"Sure! "(もちろん!)という返事を頂けたので、さっそく当ブログで説明させていただくことにしたわけです。質問箱からもブログのネタにどうかという提案(いい感じに解釈)もあってこれはもうやってやるしかないなと。
まずは、さっそくツイート画像を見ながら全く同じように試してみました。
その結果がこちら。
@ghosticgames さんのツイートされてた BPをそのまま組んでみて、距離を出せるようにしてみた。#UE4 #UE4Study #ue4UMG pic.twitter.com/eiKXqYxbev
— みつまめ杏仁 (@MMAn_nin) March 4, 2019
(このツイート、「メディア」にはログとして残ってるけど「ツイート」には出てこなくなった・・・)
基本的な原理はとてもシンプルです。さっそく他の方法はないかとアレコレ実験してみたんだけど、扱い方次第になると思うので、今回は手を加えずに教材として説明してみます。
ツイートされてる画像とレイアウトや変数名などが若干違うものがありますが、基本のロジックは同じ構造です。基本的な動作を作った後でアレンジとして対象までの距離を表示するとこまでやろうと思います。
今回はUMGやブループリント編集に慣れていない方でも作れるように操作方法をなるべく細かく書くようにしてみました。(作業の能率がいいので英語環境をオススメします)
用意するWidgetは2つ。
まずは1つ目のWidgetから
キャンバスだけのWidget
コンテンツブラウザから、 右クリック > User Interface > Widget Blueprint
で作成できます。
できたらダブルクリックしてエディタを開きます。
エディタウィンドウ左にある、Hierarchy タブの中を確認すると、あらかじめ Canvas Panel_0 というのが置かれているので、クリックして選択。
続いて右上の Is Variable というチェックボックスにチェックを付けます。
付けることで、ブループリントからこの CanvasPanel_0 に対して直接いじることができるようになります。キャンバスパネル は UIパーツを描画するため使われます。
念のためキャンバスの描画サイズを確認。
これはこのWidgetを、画面いっぱいまで使って描くよ、という意味合いです。
このWidgetブループリントはこれで完成です。
コンパイルして保存したら閉じてもOK.
次は2つ目のWidget
Actorをフォロー(追随)するWidget
今度はキャンバスのサイズを指定します。
Fill Screen と書かれている部分は、プルダウンメニューになっているので、Custom を選択します。
幅(Width)と高さ(Height)を指定できるようになるので表示したい適当なサイズ(単位はピクセル)を入力します。今回は100x100で作りました。
次にキャンバスに、2つのパーツ Image と TextBlock を配置します。まずは Hierarchyパネル で ドラッグ&ドロップします。
ドロップしたら、エディタウィンドウ右のDetails パネルで設定をいじっていきます。
Image の方から。
Anchor(アンカー)はそのままでOK。
キャンバスの中めいっぱいになるように、Sizeをキャンバスの大きさと同じ値を入力します。
あとは Detailsタブの Appearance > Tint で好きなカラーをセットしたら下敷きは完成。
次にTextBlock。
こちらもAnchorを設定します。
中央揃えにしたいので、『Top-Center(勝手に命名)』 を選択。
パラメータは以下。
テキストのカラーと文字サイズを決めて、レイアウトをセンタリングにします。
あと、これもブループリントから内容を書き換えるので、Is Variable にチェックを付けておきます。
キャンバスがこんな感じになればOK。
いよいよブループリントを編集します。
編集モードを切り替えます。
切り替わったら、グラフの何もない所で、右クリックします。
Add Custom Event を探します。Search のところで検索ワードを入力するか、一番上にAdd Event というカテゴリを開くと見つかります。(←英語環境)
見つけて選択するとノードが現れるので、
InitFancyFollowingWidget と名付けておきます。
ここにパラメータを受け取るための 入力ピン を追加します。
このノードを選択したまま(オレンジの枠線をつけた状態)Detailsタブを見てみるとInputs という項目があるので、右にある + ボタンを 静かに2回クリック。
適当な型の設定項目が2つ並ぶので、上からプルダウンを開けて、
Actor を探します。
2つ目は
Canvas Panel Slotを探して選択します。
下のようになればOK。
PINの目的(役割)を後から思い出せるように、それぞれに名前を付けておきます。
これでグラフに置いたノードが変化します。
次は変数を用意します。この青いピンからドラッグして作成します。
Promote to variable(変数へ昇格)を選ぶと、ピンと同じ型の変数が、SETノードの形で現れます。
名前を付け直したら、2つ目のピンも同じようにして変数化します。
これで、ターゲットとなる Actor と配置用の キャンバスパネルスロット にアクセスするための準備ができました。まだこの時点では中身はなくて空っぽな状態です。型が決まった器があるだけです。
変数化できたので、エディタウィンドウ左にある、Variables のリストに追加されているのが確認できます。そこから、グラフにノードとして取り出します。
Ctrlキーを押しながらドロップすると、いきなり GET のカタチで取り出せます。
ちなみに Altキーだと、SETのカタチで取り出せます。
このノードの中身が入っているか確認してから処理するので、確認用のノード is Valid をつなぎます。
なんらかの不具合や処理順などで受け取りを失敗することがあります。その時は中身が無効になることがあるので、このノードでチェックするのが安全です。今回は 両方が有効の場合のみ 処理したいのでAND(Boorean)を使います。これは論理式というやつで『 A かつ B 』という意味になります。両方のIsValid ノードの結果が true になってようやく true になります。
これらを、グラフに最初から置かれている赤いイベントノード、Event Tick ノードにつなぎます。
右端は、表示位置をセットするノードです。変数CanvasSlotをもう一つGETで取り出すと、そこから安全に取り出せます。
仕上げに、ポジションを計算する関数を用意します。
今回の要の部分です。
座標計算用の関数
MyBlueprintタブの Functions のところにある +ボタンをクリックして新しい関数を準備していきます。
名前を ProjectWorldToScreenClamed としておきます。
グラフが切り替わっているので、ノードをつないでいきます。ちょっと長めです。
まず、①Get Player Controller 、そのReturn Value からは、②Get Viewport Size
右にある2つの変数は ローカル変数です。この関数内だけという限定された範囲で使うためのものです。関数の外では利用できない変数です。別の見方をするとこの関数以外での利用価値が無い、とも言えます。今回一時的な計算のためだけに存在するのでローカル変数を使用します。
作り方は、Get Viewport Size ノードの SizeX と SizeY のピンからドラッグ&ドロップするとポップアップメニューが出るので Promote to Local variable を選択すると簡単です。
エディタ左の MyBlueprintタブに、関数を編集している時にだけ現れる Local Variables であらかじめ用意しておくこともできます。
ドラッグして作る方が、VariableType(変数の型)が確実に設定されるのでオススメ。
まず Viewport の大きさ(画面表示サイズ)を変数に入れておきます。
今度はこのWidgetのキャンバスに置いたパーツのサイズを取得して、これもローカル変数に取り置きします。
まず①Get a reference to self ノードから始めます。
②Get Desired Size は表示しようとしているサイズを返します。Self(自身)に対してつないでいるので、キャンバスに配置したすべてのパーツを含めた最終的な表示サイズのことを指します。
③は vector2d と Float を掛け算するノードです。ドラッグ&ドロップして *(アスタリスク)で検索すると見つかります。
④Get Viewport Scale は、ディスプレイ解像度を 1 として、UE4実行時に実際に表示されているウィンドウの表示倍率を返してくれます。
ディスプレイの解像度設定は公式ドキュメントにあるように設定ファイル "GameUserSettings.ini" に書かれています。
⑥は 複数の型で構成されているVector系のデータを分解してくれるノードです。Vector2D 型 は Float型のデータを2つ束ねています。主に XY座標を扱う場合などで使います。
⑦と⑧は これも ローカル変数に格納します。
まだまだ続きます。
これはワールドにあるアクターの座標をScreen座標に変換します。
用意しておいたカスタムイベント InitFancyFollowWidgetで受け取っている変数オブジェクトを変数リストからドラッグ&ドロップして始めます。
そこから② Get Actor Location でアクターのワールド座標を取得 → ちょっとだけ位置をずらします。
ConvertWorldLocationToScreenLocation というノードで、3次元のワールド座標を画面で見ている2次元のスクリーン座標に変換します。このノードは、先にGet Player Controller ノードを取り出しておくとスムーズに取り出せます。④→⑤
ここにアクターのポジションを渡してやります。
これをまたローカル変数に取り置きします。⑧、⑨
右端のは Returnノードです。 関数には基本このリターンノードを置きます。関数というからには何かしら計算や処理した結果があるので、この Return ノードから、外に出してやるのです。ノード検索キーワードに ピリオド 「 . 」 を入力するとすぐ見つかります。
このリターンノードには アウトプット用のピンを追加できます。リターンノードを選択して、Detailsタブから追加します。
このリターンノードで返す値がこちら。
取り置きしてきた座標やサイズの値をここで一気に計算します。
上段が X座標、下段がY座標用です。それぞれ Clampノードではみだしをカットしています。今回の一番重要な処理だと思います。
Integer型 と Float型 の引き算はそのままではできないので、Integer型をFloat型にキャスト(型変換)してやります。
ノード検索で「 to 」と入力すると ToFloat(Integer) が選択できます。
こんなノードが出てきます。
全体図はこんな感じ。
@ghosticgamesさんのと同じ内容ですが、説明用にコンパクトにしました。
これで関数は完成ですが、最後の仕上げとして、この関数をPure型にします。
Pureにチェックを付けると・・・
グラフにノードとして取り出した際に、実行ピン(白色)がない状態になります。
これを途中だった EventTick処理につないだら完成です。
コンパイルして保存したらと閉じてもOK。
表示を確認してみる
ワールドに適当なオブジェクトを配置します。
実験では、Transformを持ってさえいれば動作すると思います。
配置できたら、手っ取り早いのでレベルブループリントを使って確認します。
レベルブループリントの編集方法はこちらから。
グラフ編集エディタが開きます。
①Create Widget からつないでいきます。
アクターに追随するWidgetは、Viewort に追加せずに、先にAdd to Viewport したキャンバスだけのWidget内に持っている CanvasPanel の子供にします。
さらに続き、
スムーズにつなげるコツは、白い線は気にせずに、Return Value からドラッグして検索することです。つながるのが約束されているノードが取り出せるので安心です。
⑧Set Auto Sizeノードは Inb Auto Size にチェックを付けておきます。
⑩はワールドに置いたオブジェクトをドラッグします。
この方法は可能なブループリントが限られます。
これですべての準備は整いました。
再生してみましょう。適当にカメラを動かしてみてください。
対象のオブジェクトが画面から消えても残り続けます。
これが Clampを使った移動制限です。
一応動画のURLを貼っておきます。
→ @ghosticgames さん版
細かくオペレーションを書いていたら思ったより長くなってしまいましたが、今回はこの辺まででいったん筆を置こうと思います。
次回、計算の仕組みを軽く解説して、オマケの「距離」を表示する部分を書きます。
@ghosticgames さん ステキな教材をありがとう。
ではでは
ステキなターゲット表示ライフを!