👾

【UE5/C++】UMG ViewModelのインスタンスをC++からセットする

に公開

はじめに

UMG ViewModelというUEのプラグイン機能に関するお話です。
確認環境はUE5.6
ベータ機能のため、今後内容が変わる可能性があります。
https://dev.epicgames.com/documentation/en-us/unreal-engine/umg-viewmodel-for-unreal-engine

やりたいこと

UMG ViewModelというのは、ウィジェットBPにViewModelを設定し、
データバインディング設定を行うことで内部データと表示の連携が可能になる仕組みです。

このViewModelはUMGエディタ上で編集しますが、このときCreation Typeにより、

  • 外部でインスタンス化して参照をViewに渡すか、
  • あるいはViewの生成とともにViewModelのインスタンス化を行い、View自身でそれを管理するか。

といった選択をとることができます。

このCreation TypeをManualとして、
C++側で生成したインスタンスをWBPに作成したViewModelにセットするのが目的です。

前置き

結論のまえに、調査内容を前置きとして書いておきます。
このMVVM関連の機能は UMVVMViewUMVVMViewClassといった
拡張機能(Extension)として、 UUserWidgetに対して提供されています。

もう少し踏み込むと、 UMVVMViewUUserWidgetExtensionなのですが、
UMVVMViewClassUWidgetBlueprintGeneratedClassExtensionです。(長い!)
前者は UUserWidgetAddExtension()することができますが、上手く動きません。
原因は、 UMVVMViewが内部で UMVVMViewClassの存在を前提としているためです。
UMVVMViewがメンバー変数として保持する UMVVMViewClassの参照がnullptrとなる)

そこで、 UMVVMViewClassAddExtension()してやりたいところなのですが、
こちらは UWidgetBlueprintGeneratedClassExtensionなので、上手くいきません。(長い!)
BlueprintGeneratedClassExtension(BPGC)というのは、
BPをコンパイルした結果として得られるクラスオブジェクトのことのようで、
つまり、 UMVVMViewClassはそのBPGCに対してしか AddExtension()できないわけです。

ではお手上げかというとそうではなくて、
この UMVVMViewClassはBPコンパイル処理時に、自動的に AddExtension()されます。
常にAddされるわけではなく、UMGエディタ上でデータバインディング設定がある場合のみ、
BPコンパイル時にAddされる仕様となっているようです。

この UMVVMViewClassの方はバインディング設定を管理する機能のようで、
実際にViewModelをセットする機能は UMVVMViewの方に実装されているんですが、
UMVVMViewClass::Initialize()にて UMVVMViewAddExtension()され、
そのインスタンスに自身( UMVVMViewClass)が渡される、という作りなので、
BPコンパイル時に UMVVMViewClassが付与されれば、勝ちというわけです。(何に?)

結論

先ほどの画像のように、まずはUMGエディタ上でViewModelの定義を作成します。
Creation TypeをManualにするのと、
Viewmodel Nameをきちんと設定しておきましょう。(C++での実装時に合わせる必要があります)

次に、データバインディング設定も作っておく必要があります。
こうしなければ、後ほどViewModelをセットするのに必要なExtensionが付与されません。

この状態でWBPをコンパイルすると、
UMVVMViewClassをExtensionとして保持するBPGCが生成されます。
ここからはC++での実装になります。

m_MvvmView = CreateWidget<ULoadingWidget>(this, m_MvvmViewClass);
m_MvvmViewModel = NewObject<ULoadingViewModel>(this, m_MvvmViewModelClass);
if (UMVVMView* mvvmView = m_MvvmView->GetExtension<UMVVMView>())
{
	mvvmView->SetViewModel(TEXT("MainViewModel"), m_MvvmViewModel);	
}

m_MvvmViewClassが、先ほど作成したWBPクラスですね。
無事に MVVMViewClassがBPコンパイル時に付与されていれば、
GetExtension<UMVVMView>()によって UMVVMViewが取得できるかと思いますので、
あとは SetViewModel()にWBPに定義したViewModelの名称と、
ViewModelのインスタンスを渡してやればOKです!

おわりに

UMVVMSubsystemというものがあり、
UMVVMSubsystem::GetViewFromUserWidget()を呼び出すことで、
GetExtension<UMVVMView>()の代わりとすることもできます。
内部的には同じ処理ですが、ベータ機能であることを考えるとこちらの方が素直かもしれないですね。

UMVVMGameSubsystemというものもあるんですが、
こちらはグローバルなViewModelを管理するためのもので、まったく別物のようです。
Creation TypeをGlobal View Model Collectionとした場合に使うようですね。

Discussion