Yusuke Saitoの雑記

センサー類, Python, Unity(C#), Unreal Engine, 機械学習とかについてのメモ

UEのMVVMについて触ってみる

はじめに

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」を作成します。(下記記事を参考にしています。)

また、「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