ブループリントで二次元UI的な画面（ウィジェット）を操作する（Unreal Engine 4.25）

# 前提

身内用に書いたものを、暫定的に公開しているものです。何かおこったら閉じるかもしれません。

[前の記事](https://qiita.com/mml/items/5273a7699cdeb9ca21da)に書いた知識（ブループリントの最低限の知識）は持っている、という前提で書いてます。特に説明のない限り、Unreal Engine 4.25 で FirstPerson テンプレートで作成したプロジェクトのレベルブループリントのイベントグラフ上で操作をする前提です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/7689a532-411d-5340-427e-4fc497f9fa06.png)

# UE4の2次元UI的なもの（UMG）を作る仕組みについて

UE4でユーザインタフェース的に使える、二次元のボタンとかテキスト、メニューみたいなものを作る仕組みがあります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/f2721517-9cb2-1618-443d-8e21c6c941d9.png)

上の画面の中央に出ている Resume とか Exit のボタンや、左上に出ているスコア表示、画面中央に見えている照準を示す赤い十字などがそうです。テキストだけでなく、画像やアニメーションを表示することもできます。

これらを実現する具体的な方法としては、ウィジットを使う方法と HUD （Head Up Display）クラスとを使う方法の二つがあります。HUD クラスを使う方法は UE3 までは標準だったようですが、UE4 からはウィジェットを使うのが標準になったようです（多分）。なので、以下ではウィジェットについてのみ書きます。

ちなみに、UE4では二次元 UI 的なものを作る仕組みのことを総称して UMG（Unreal Motion Graphics UI デザイナ）というようです。 

# 他の記事

- [Unreal Engine 4.25 でゲームの一時停止、再開、初期化、終了(Pause, Resume, Reset, Quit) を実現する](https://qiita.com/mml/items/33b7e24cb693e3875d48)

# ウィジットのざっくりとした使い方

二次元UI的なものを画面に出すには、おおむね以下の手順が必要です。

- ウィジェットのブループリントクラスを作る
- UIを作成するエディタでボタンやテキストなどを配置する
- ボタンを押したりしたときの動作をブループリントで記述する（必要ならば）
- レベルブループリントなどにウィジットを表示したり消したりする処理を書く

以下、順番に説明します。

## ウィジットのブループリントクラスを作る

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/f91ae0de-2d31-ead4-13ae-0ae046e411fa.png)

コンテンツブラウザで右クリックして、ユーザインタフェイスからウィジェットブループリントを選びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/dcda54f6-fe9a-b85a-9ec3-5c833af5f1a3.png)

こんな感じのアイコンができるので、名前を変更しておきます。ここでは SampleMenu にしました。以下、SampleMenu という名前にしたものとして説明します。

これでウィジェットのブループリントクラスが用意できました。

## UIエディタでテキストやボタンを配置する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/52e609ce-1b15-f633-5473-0560f7e558fb.png)

作成したウィジェットブループリントクラスをダブルクリックすると、こんな感じのエディタが開きます。中央の点線（というか緑）の枠が、画面の外枠のサイズをい示しています。この範囲内に、ボタンやテキストなどを配置していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/b1c2a32c-dd04-5ef7-f7e2-f57bf55d047b.png)

例えば、ボタンは「一般」の中の Button を選んで、画面内にドラッグドロップで配置します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/e2a1d2b6-b3e2-40f9-520f-8051223f09d0.png)

配置したら、ボタンの周囲の白い点をつかむことでリサイズできます。右側の「詳細」の中で、数値入力で位置やサイズを指定することもできます。ボタンに文字を表示させたい場合は「テキスト」という違う部品を使います。

![ue10.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/5c59156d-c325-8917-c331-e2856e13e6ca.gif)

「一般」の中にある Text の項目をドラッグドロップでボタンの上に配置すると、ボタンに表示する文字として使えるようになります。ボタンの部品とテキストの部品が親子関係になり、ボタンを移動させればテキストもついていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/6fb3269c-03bc-4fa4-95d2-884d98b25880.png)

テキストを選択した状態にすると、右側の「詳細」で表示する文字やフォント、サイズ、色などを設定できます。ここでは文字（Text）を Resume として、表示色を黒に変更してみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/56848eae-ff87-14cd-9ba6-6545938b61b4.png)

変更後はこんな感じになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/4ba70df5-ab34-74f8-a5e7-189dc46d16c6.png)

ボタンを選んで、右側の「詳細」のところで ResumeButton に名前を変えておきます。デフォルトでは Button_001 のような番号のついた名前になっているのですが、これだとプログラムでこのボタンを使うときに、何の機能のボタンなのかが分かりにくいです。自分で見て、何のボタンかわかるように名前を変えておくと多分幸せになれます（多分）。

![ue11.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/b36e2c2f-a466-1cdd-2ea5-0bfeda912cbb.gif)

作成したボタンなどは、コピペで増やすこともできます。ペーストするときは、左の Canvas とかかれた項目を選択してから、ペーストの操作をします。そうしないと、コピーしたボタンが親になった状態で、新しい部品がペーストされてしまいます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/f040f91b-0d92-f6f8-12ae-139ec7269a28.png)

ここでは、Resume ボタンと Exit ボタンを作ってみました。Exit のボタンも、選択して「詳細」のところで ExitButton という名前に変えてきます。「詳細」で名前を変えると、左の「階層」のところの部品名の表示も変わります。

ちなみに、ボタンやテキストの部品と、このエディット画面全体（Canvas）のことをUE4ではまとめて「ウィジェット」と呼ぶようです。つまり、ボタンもウィジェットであり、2次元UI的な画面そのものもウィジェットです。ただし、分かりにくいのでここでは

## ボタンを押したときの動作をブループリントで書く

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/b86a03db-27c5-e374-527d-2c08f95fb372.png)

ボタンなど、押したときに何かさせるプログラムを書くときは、ウィジェットのエディット画面の右上にある「グラフ」を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/358d1068-4b36-fd65-bcc6-e0228440bd97.png)

こんな画面になって、配置したウィジェットの動作をブループリントで記述できます。左側の「変数」のところで、プログラムを追加したいウィジェット（部品）を選び、下の「イベント」で具体的に何のイベントが起こった時の動作を書くかを選びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/cccb0c79-f405-8fb6-9708-eb844190a5db.png)

たとえば、ResumeButton がクリックしたときの処理を書きたければ、ResumeButton を選択した状態で、下の「イベント」で On Clicks の横にある＋ボタンを押します。すると、画面内に On Cliked (ResumeButton) というイベントノードが配置されます。これを使って、ボタンが押された時の処理を記述します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/1142c9f4-b190-3ef2-a995-2c5808efea90.png)

例えば、このように記述すると、ResumeButton を押したらウィジェット全体が画面から消えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/a4e5fa5a-7b6c-b49a-0139-41b4d35bc6b5.png)

もし、グラフのエディタ内でボタンが変数として表示されていない場合は、デザイナに戻って、ボタンの「詳細」のところの is variable にチェックを入れてください。ここにチェックを入れた部品だけが、グラフのほうで変数として使えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/ef1aba9e-d24e-9a09-f22f-94dbca707976.png)

ExitButton のほうにも上のようにノードを追加しました。これで、ExitButton を押すとアプリが終了します。

## ウィジットを表示したり消したりする処理を書く

作成したウィジェットを画面に表示するためには、レベルプループリントなどにウィジェットを画面に出すプログラムを追加する必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/b644f4c8-64ff-f8bf-964a-ca4246825ff8.png)

ウィジェットを画面に出すには、CreateWidget ノードでウィジェットクラスからぅジェットを作成し、AddToViewport ノードで作成したウィジェットを画面に表示するという手順が必要になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/979b45da-8c2d-fdfb-5439-4bca7d59becd.png)

画面内に「ウィジェッを作成」と書かれているノードは、CreateWidget で検索して出すことができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/4539cafb-df36-85c1-214f-2b9784d36c9d.png)

Class のプルダウンメニューから SampleMenu を選ぶと、ノード名が「Sample Menu ウィジェットを作成」に変わります。作成したウィジェットを AddToViewport ノードに引数として渡すと、画面に表示させることができます。

最後の「セット」のノードでは、ウィジェットを表示したあとにマウスカーソルの表示を有効にしています。FirstPersonProject ではデフォルトでカーソルが表示されていないので、ボタン操作のためにカーソル表示を強制的に有効にしています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/a8beef14-7397-eaaa-f606-0abd66ca52ef.png)

「セット」のノードは、GetPlayerController のノードから線を引き出して「ShowMouse」と入力して SetShowMouseCursor を選ぶことで配置できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/1b46f51e-1a93-c5ff-7b70-822ecfdb230b.png)

GetPlayerController のノードは、PlayerController と入力すれば検索できます。

## 実行

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/95ccbaa4-e1e2-6143-2767-d53017455d77.png)

ここまで作って「プレイ」すると、Zキーを押したらメニューが表示され、ボタンを押したらゲームの再開、終了ができるようになっていると思います。

しかし、メニューが出てもそのままゲームの操作ができてしまいます。ゲームの実行を止めてメニューを出すには、もう少し手順が必要になりますが、長くなるので下記を参照してください。

- [Unreal Engine 4.25 でゲームの一時停止、再開、初期化、終了(Pause, Resume, Reset, Quit) を実現する](https://qiita.com/mml/items/33b7e24cb693e3875d48)

# ウィジェットのアニメーション

概略だけ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/6dd6466f-c9ca-6b10-fc5b-fbefbd1d717c.png)

＋アニメーションというボタンを押します。アニメーションの項目が追加されるので、それを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/729bf0a7-e54a-c925-10c6-7ccc3a3b58ed.png)

0.00 のところにカーソルを合わせておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/1ef88fee-cba3-f751-0362-89d2b4138f34.png)

アニメーションを追加したいウィジットを選択して、詳細のところで変更したいパラメータを変更します。上の例では、アピアランスの Color and Opacity のところで色を赤っぽくしてます。変更したら、**その横にある＋のついたボタンを押します**。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/fb78580e-08c0-0a98-ca9f-c5157bdc0ed1.png)

これで 0.00 の時刻にキーフレームが追加されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/d8860168-8f0f-5c2b-3f4a-e8b533ae0e66.png)

時刻を 1.00 とかに変更して、色を変えてまた＋を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/25aafbf2-8489-bafc-6db4-16e4143dd777.png)

こんな感じでキーフレームが追加されていきます。キーフレームに設定したところのパラメータを変えたい場合は、キーフレームを選択した状態でパラメータを変更し、再度＋を押します。＋を押さないと変更されません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/cbadfb23-885f-b3ae-2685-178632f01b38.png)

同じ時刻に複数のパラメータのキーフレームを追加することもできますし、単独のキーフレームを設定することもできます。このあたりは、他のアニメーションツールと同じ感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/a5334f2b-ec1b-2dcc-70a8-e67357acbde8.png)

カーブエディタでパラメータの変化量のカーブを編集することもできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/5fa717d1-9672-45e2-c1dd-f3980168ab66.png)

アニメーションの表示は、ブループリントを書いて明示的に再生する必要があります。逆再生とかもできるようです。

このくらいヒントがあればあとはできるはず（適当）。

# カスタムウィジットを作る

自分でオリジナルのウィジェットを作成して、別のウィジェットの中に配置することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/bcf64c31-54ab-dcde-445a-a1b4f30b480c.png)

こんな感じで Text と TextBlock を配置して、MyWidget みたいな名前でブループリントクラスを作ってビルドしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/265f6a15-53a1-feeb-0725-373562703b12.png)

すると、こんな感じで別のウィジェットのデザイナーの「パレット」の中に、作成したウィジェットの項目ができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/cf799546-2d5f-adc1-7fd7-120d234ce7df.png)

項目を選んでドラッグドロップすれば、作成したウィジェットをひとまとまりの部品のようにして配置できます。

## ウィジェットに無駄な余白ができないようにする

ポイントとしては、自作したウィジェットは「左上を原点として、右下がクリップされた状態で」配置されるということです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/35ceda8f-d404-4aad-feeb-d4ae0d353b2a.png)

こんなふうに、左上にぴったり合わせてカスタムウィジェットを作っておけば、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/c5e2d557-dce9-741f-b291-83e58ea48c95.png)

他のウィジェットに配置したときに、無駄なスペースなく（きれいに？）配置できますが、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/a2681c3f-e388-0391-9a54-6dd67d007671.png)

こんなふうに、左上から離れた位置にテキストなどを配置していると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/c96caf5f-a9c4-7d1a-f58e-1f6cc600b197.png)

他のウィジェット内に配置したときに、このように左上から部品を配置した場所までのスペースが、マージンのように（何もない空白として）開いた状態で配置されます。

## カスタムウィジェットにイベントを追加する

カスタムウィジェットで発生したイベントを外部に通知したり、外からイベントを受け取れるようにするには、イベントディスパッチャーを使います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/ce98b7a1-30ce-e4ec-c829-ef1b861148d4.png)

TextBlock を内部に含むカスタムウィジェットを作ったとき、内部にある TextBlock のイベントを拾う例について書きます。TextBlock のイベントに対応するイベントディスパッチャーをまず用意します。ここでは TextInputCommited と TextInputChanged を追加してます。（名前はイベントに合わせる必要はないです）

イベントディスパッチャーを追加した時点で、他のウィジェットにカスタムウィジェットを配置したときに、イベントのタブ内にイベント配置のための＋ボタンが追加されます。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/6021f95a-08b0-3f08-f2b9-ed445fe79bd0.png)

必要に応じてイベントディスパッチャーに引数を追加しておきます。ここでは、OnTextChanged イベントにあわせて Text 型の引数を追加してます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/c85d3fbe-cd5d-5dd1-9bac-a5131a02dc58.png)

こんな感じになります。しかし、イベントディスパッチャーを追加しただけでは、TextBlock で発生するイベントは拾えません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/50e191dd-3333-fdda-5919-887d1aa26d88.png)

このように、内部のウィジェットのイベントに対して、対応するイベントディスパッチャーの呼び出しノードを配置する必要があります。これで、実際にテキスト入力がなされた（または完了した）ときに、MyWidget の TextInputChanged や TextInputCommited のイベントノードが呼び出されて、実行されるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/b96b8bf4-012a-1297-d9d4-63313b9fd08b.png)

MyWidget を配置した側でこんなコードを書けば、MyWidget 内部の TextBlock の入力完了イベントを拾えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/bf36afdf-3ce4-5049-307c-f7635127ebe3.png)

カスタムウィジェットの内部にアクセスできるようにするには、普通にパブリックな関数を作ればOKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/2922d5bf-e0fc-0064-dc3f-f382589f3ef0.png)

こんな感じのコードを書いておけば、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/04064149-4751-97e1-ae1f-994d897d62cf.png)

こんな感じでふつうに呼び出せます。

## ListView みたいなウィジェットの要素として使う

ListView や TileView のような、他のウィジェットを要素のように使うウィジェットの要素として、カスタムウィジェットを使うことができます。ただし、それぞれ親になるウィジェットにあわせたインタフェースを実装する必要があります。

- 要素のクラスはウィジェット（ブループリント）クラスとして作成すること
- デザイナで左上を原点として内部ウィジェットを配置すること

くらいを知ったうえで、以下の記事を読めば多分わかるはずです（きっと）

- [[UE4]UE4.20で追加されたListViewウィジェットについて](https://historia.co.jp/archives/10389/)

# 結論

後半雑になったので、UE5 でも変更がなければいつか追記します。