はじめに
UE記事一発目、UEでのMVVMについて触れていこうかと思います。 過去、お仕事でUnityにおいてC#でMVPパターンにて設計を行ったことがあるのですが、特に継続性のあるプロジェクトや複数デバイス/プラットフォームでの実装などを見越している場合では、かなりの効果を発揮すると実感しています。 今回、MVPではなくMVVMパターンに焦点を当てていきますが、UEでの設計の勉強の一つとして書いていきます。
参考
今回、alweiさんのMVVMの記事を前提とします。 - UE5 UMG ViewModelを利用してBPオンリーのMVVMをしてみる https://unrealengine.hatenablog.com/entry/2023/09/28/233801
MVVMについて
動機と方針
まず記事を書こうと思った動機としては、View-ViewModel側やModel側にバリエーションを持たせたとき、実際にどう作っていくのがよいかと疑問に思ったためです。
alweiさんの参考記事の場合、最終的には図のようにゲージが減少していくLevelが作成できます。
この場合、View, ViewModelおよびModelの関係性の概念図としては下図のようになります。
この応用として、まずModel層側のもう一つのパターンとして、ゲージを減少させるのではなく増加させるパターンを作ります。さらに、View層のもう一つのパターンとして、円形のゲージを扱うパターンを作成します。
すなわち、今回行いたいことを先ほどの概念図に追記すると、下図のようになります。
Model側:ゲージを増加させるパターンを作る
今回、Model側でのパターン作成はシンプルなものになります。まず、参考記事におけるThirdPersonCharactorをRenameし、「BP_ReduceLifeThirdPersonCharacter」とします。次に、「BP_ReduceLifeThirdPersonCharacter」をCopyし、「BP_IncreaseLifeThirdPersonCharacter」と名前を付けます。
BP_ReduceLifeThirdPersonCharacterにおけるEventTick内での処理は、図のようになっているはずです。
なので、増加させるパターンの場合、図のようにノードを組み替えれば「BP_IncreaseLifeThirdPersonCharacter」が作成できたことになります。
最後に、ContentsBrowserのModel層のファイルは図の二つになっています。これで、Model層での準備は完了です。
View側:円形ゲージのパターンを作る
次に、View側で円形ゲージのパターンを作ります。円形ゲージ用の「M_CircleGauge」を作成します。(下記記事を参考にしています。)
- 参考2: UE5 円型プログレスバーを作ってみる https://qiita.com/unknown_ds/items/233df69d11fc1c9f557b
また、「M_CircleGauge」のMaterialGraphはこのようになります。
そして、「M_CircleGauge」を親としたMaterialInstanceである「M_CircleGauge_Inst」を作成しておきます。
参考記事におけるWBP_LifeGaugeをRenameし、「WBP_LifeGauge_Straight」とします。次に、新しくWidgetBlueprintを作成し、「WBP_LifeGauge_Circle」と名前を付けます。そして、下図の状態になるように「WBP_LifeGauge_Circle」の状態を設定していきます。
- Canvas->SizeBox->ImageとなるようにHIerarchyを設定していきます。Imgaeを配置したのち、名前を「Image_Life」をしておきます。
- Appearanceビューにおいて、MaterialInstanceがBrush内のImageに割り当てられるようにします。 「WBP_LifeGauge_Circle」のEventGraphを図のようにし、PreConstructでMaterialDynamicInstanceが割り当てられるようにします。
最後に、ViewBindeingsビューにて、図のようにBP_ViewModelのSetLifePercent関数を、Image_LifeのSetOpacityにBindします。最終的に、ContentsBrowserのView層のファイルは図のようにになっています。これで、View層での準備は完了です。
まず動かしてみる
では、動かしてみましょう。まず、NewLevelからBasicを選択し、新しいLevel「SampleMap」を作成します。そして、原点位置にPlayerStartを配置します。
次に、このLevelのLevelBlueprintを編集していきます。編集結果として、図のようなLevelBluePrintを組んでください。
また、WorldSettingsビューにて、DesualtPawnClassに対し、LevelBlueprintでの実装と同じCharactorクラスを割り当ててください(この場合、「BP_ReduceLifeThirdPersonCharacter」を割り当ててください)
今回はLife量は減少、ゲージは円形、といった形になります。実行すると、図のような動きになるはずです。
Model, Viewの切り替えについて
ここで少しLevelBlueprintについて解説してきます。まず、Construct BPViewModelノードにて、BP_ViewModelのインスタンスを作成します。
次に、Model層のCharactorクラスの取得と、ViewModelへのアタッチを行います。この際、下記図のように減少させる場合と増加させる場合の両パターンについてノードを組んでおき、切り替えられるようにしておきます。
そして、WidgetBlueprintのインスタンスを作成することで、これをBP_ViewModelに紐づくViewとします。 ここで、下図のように、WidgetBlueprintを「WBP_LifeGauge_Straight」か「WBP_LifeGauge_Circle」かを切り替えることで、円形ゲージか直線ゲージかを切り替えられる形になります。
一つ別の組み合わせとして、Life量が増加していき、直線ゲージの場合、動かすと下図のようになります。
また、そのときのLelevBluePrintは下記のようになります。
おわりに
以上で、MVVMについて、UMG ViewModelによるViewとModelのパターンの拡張と切り替えを行ってみました。 この先としては、Charactor以外のModel層の例や、ViewやModelにInterfaceを使う形でDI (Dependency Injection)をする形について模索するところかなと思いますが、まぁ気が向いたらってことになりそうです。
また、サンプルのプロジェクトをgithubに公開しています。 github.com