UE5 円型プログレスバーを作ってみる

# 概要
UnrealEngine5 にて円型のプログレスバーを作ってみたメモです。


## 更新履歴
| 日付 | 内容 |
|:-----|:----|
|2023/08/07| 初版 |

## 参考
以下の記事を参考にいたしました、ありがとうございます。
[UE公式：UMG UIデザイナ](https://docs.unrealengine.com/5.2/ja/umg-ui-designer-for-unreal-engine/)
[UE公式：グラディエント（勾配） マテリアル関数](https://docs.unrealengine.com/5.2/ja/gradient-material-functions-in-unreal-engine/)
[UE公式：Math マテリアル関数](https://docs.unrealengine.com/5.2/ja/math-material-functions-in-unreal-engine/)
[Round / Radial Progress Bar - Unreal Engine 5 Tutorial [UE5]](https://www.youtube.com/watch?v=BgOAbAdi8f0)
[[UE4]マテリアル表現の幅が広がる！おもしろいノード＆マテリアル関数集 1](https://historia.co.jp/archives/4559/)


## 環境
Windows10
Visual Studio 2022
UnrealEngine 5.1.1


# マテリアル準備
## アセット作成
コンテンツブラウザからマテリアルを作成、名前を付けます。
次にマテリアルアセットを開いて詳細から[マテリアル]->[Material Domain] を `User Interface`に、[Blend Mode]を `Translucent` にします。

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/7b89862e-7371-af61-cc91-dfae120693a8.png">


## 円をつくる
テクスチャを使わずに円をつくるには `Radial Gradient Exponential` を利用します。これを使うと放射状のグラディエントが生成されます。
![RadialGradientExponential.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/a943f2d2-52b7-f53c-e8ad-30ea7023cd71.png)

この円を大きさ違いで2つ用意し、差分を計算すると円型の出力が得られます。
![Circle.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/0022f4d1-afee-f5fd-7257-3d3127f323de.png)

`Subtract`が差分の計算、`Thickness`が円の厚みで0.1をデフォルト値としてパラメータ変換で持っておきます。このパラメータは後にWidgetBP側で変更することができます。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/0a9c4b25-806e-cfe5-a5ec-fa8f785f3341.png">


## 進行度を表すためのマスクをつくる
まずはUV座標を`VectorToRadialValue`を使ってラジアル座標（基準点を中心にした角度と距離の座標系）に変換します。そのままだと左上が中心になるので 0.5 を減算し、中央を基準点にします。
![VectorToRadialValue.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/1b9dd919-5f37-d78a-28ce-9f0b856ef0b3.png)

このままだと右が回転の0度となるため、`Custom Rorator`で1/4(0.25)だけ回転させて上を0度となるようにします。
![VectorToRadialVelue2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/ee5b3337-3815-6624-d5e7-312a52598af8.png)

上記の結果を踏まえて、`if`ノードにてマスクを作成します。`Percent`は変数として外部から変更できるようにパラメータ化しておきます。このPercent が 0.5だと以下のようになり、上をスタートとして回転の半分だけ白部分となります。
![Mask0_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/d951305c-79b0-f477-54cc-fbd661938aaf.png)

0.7の場合は以下のようになります。
![Mask0_7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/69a7dbad-88ca-dc8a-9504-f4cd2ad231f7.png)

白の部分が有効で黒の部分が無効になるマスクがつくられます。


## 円とマスクの合成
先の円とマスクを`Multiply`にて掛け合わせると以下のような結果となります。
![Result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/c6a66712-3a23-801a-c70d-f639b3c439ce.png)


# ウィジェットをつくる

## マテリアルインスタンスの作成
カラーを `BaseColor`としてパラメータ化し、以下のようにします。
![Material.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/9559a59d-205a-a150-bce4-1f3cc9bfdaee.png)

コンテンツブラウザからマテリアルインスタンスを作成します。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/5693092a-bbac-dd43-3827-8fab69a125ad.png">

マテリアルインスタンスからパラメータ化した値を変更することができます。（外部から変更するパラメータはチェックを入れる必要があります。）
![MaterialInstance.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/b2abd59b-7caa-c658-04bd-512b747345ee.png)


## ウィジェットの準備
コンテンツブラウザから[ユーザーインターフェース]->[ウィジェットブループリント]を選択し、ウィジェットアセットを作成します。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/04ebb2eb-744c-c535-81a8-702ca1bf0698.png">

[デザイナー]タブからレイアウトを作成します。
`SizeBox` を用意し、その下へ`image`を置きます。
![WidgetDesign.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/261533c5-8cd7-b547-2767-ba943c365295.png)

次に設定用関数を作成します。
[グラフ]タブから`SetPercent`関数を作成し、`Create Dynamic Material Instance`でダイナミックマテリアルインスタンスを作成します。作成したダイナミックマテリアルインスタンスは変数として保持します。次に`Set Brash from Material` でイメージを設定し、`Set Scalar Parameter Value`でマテリアル変数の`Percent`に対して値を入れるようにします。2回目以降は設定不要で`Set Scalar Parameter Value`で値を入れることができるようにします。

![WidgetGraph.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/755df303-8779-12ca-c148-339b56ac76f1.png)


## ウィジェットを配置
作成したウィジェットを配置します。
![CreateWidget.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/9db3b554-fcf1-24bb-3a1c-092a79ff2590.png)

配置されたウィジェットの`SetPercent`関数を通じて値が入るように細工します。
![SetPercent.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/5b877ce3-f21c-1ed0-da2e-f43d7141b4cc.png)

結果は以下のようになりました。
![result.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/7471847a-582e-3db9-b193-e1884adc359e.gif)


# 追加機能

## ゲージ分割
以下のようなコードを追加し、Multiplyをします。
![Section.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/40894e0b-5f54-eab7-1750-31e4462c3638.png)

`SectionNum`が分割数です。`Vector To Radial Value`から取得した `Vector Converted To Angle` とSin関数を掛けることで-1.0～+1.0までの変化を分割数分繰り返します。`HoleWidth`は黒部分のサイズを決める値で、2倍して1を引くことで0～1への正規化を行っています。これを比較することで放射状のマスクを得ることができます。

結果は以下のようになります。（分割数32）
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/3ccb88cf-2323-4200-1235-37b157168cca.png">

分割数を8にすると以下のようになります。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/c429ede0-5df3-a5d6-2671-07fe7a70c48f.png">


## グラデーション
以下のようなコードを追加し、出力カラーにいれます。
![GradationColor.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/c7308612-337c-35ac-5d0c-077e574597f6.png)
`Vector To Radial Value`から取得した `Vector Converted To Angle` を使って開始カラー`BaseColor`と変更カラー`ToColor`を加算しています。`IsGradation`はStaticBoolParameterとしてグラデーション機能のOn/Offに使用しています。

結果は以下のようになります。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303806/ec9c5171-a57d-ec52-799f-80462e3fd1c0.png">



# まとめ
UEのマテリアルはいろいろできていいですね。あとはきれいに書くことができれば。。