【UE】UMG Viewmodel 入門

Unreal Engine

はじめに

Unreal Engine (UE) Advent Calendar 2025 の7日目の記事です!
この記事では Unreal Engine の「UMG Viewmodel」という機能について解説します。

この記事での環境は以下の通りです。

  • Windows 11
  • UE 5.7.0(ランチャー版)
  • UMG Viewmodel 1.0(Beta)

UMG Viewmodel はこの記事の執筆時点ではまだベータ機能です。
(Experimental と Beta、Production-Ready については以下の通りです。)

UMG Viewmodel とは

「UMG Viewmodel」とは何でしょうか?
UMG Viewmodel とは UMG ウィジェット(View)とゲームのデータやロジック(Model)を分離させて、View と Model の双方向のやり取りを疎結合な状態で効率的に行えるようにするための機能です。

この記事では「UMG(Unreal Motion Graphics UI Designer)」という言葉が何度か出てきますが、UMG については公式ドキュメントで以下のように説明されています。

Unreal Motion Graphics UI デザイナ (UMG) は、ビジュアル UI オーサリングツールです。ユーザー向けに表示するインゲームの HUD、メニューやその他のインターフェース関連のグラフィックスを作成するために使用できます。

UMG UI デザイナ

MVVM(Model-View-ViewModel)とは

UMG Viewmodel を使用すると UMG ウィジェットと、それとやり取りするロジック部分の実装において MVVM(Model-View-ViewModel)というものを簡単に実現できるのですが、この MVVM とは何でしょうか?

MVVM とは「Model-View-ViewModel」の略であり、その名の通り Model、View、ViewModel という3つの要素から成り立っています。
(「ViewModel」は「VM」と略されることがあります。)

各要素の役割は以下の通りです。

要素役割
Modelデータを取得してそのデータを読み取ったり、書き込んだりする。
ビジネスロジックを担当する。)
この記事では ViewModel で定義した変数を扱っている。
Viewユーザーに情報を表示したり、ユーザーからの入力を受け取ったりする。
この記事ではウィジェットブループリントが View。
ViewModelModel と View の仲介役。
データバインディングという仕組みを使って Model と View の値を同期させる。
この記事では MVVMViewModelBase というクラスを継承したクラスが ViewModel。

これら3つの要素を図で表すとこの画像のようになります。
この画像では「UMG Widget」が View で、「Object in Application」が Model になっています。

引用元:UMG ビューモデル

UI 部分の実装において MVVM を利用するメリットはいくつかありますが、UMG Viewmodel を使用すると MVVM の役割ごとに完全に別のアセットになるので、プログラマーとデザイナーが同時に作業しやすくなったり、データを扱って何か処理を行うロジックの部分をあまり意識することなく比較的自由に UI を編集できるようになったりといった点が最も大きなメリットかと思います。

後に解説する手順通りに進めると Model、View、ViewModel の Reference Viewer はそれぞれ以下のようになります。

Model(BP_Model)の Reference Viewer
View(WBP_View)の Reference Viewer
ViewModel(BP_ViewModel)の Reference Viewer

これらの画像はつまり、「Model と View は ViewModel のことは知っているけど、お互いのことは全く知らず、ViewModel は誰のことも知らない」という状態です。
なので ViewModel さえ存在していて、必要な変数や関数などが ViewModel で定義されていれば仮に Model と View の片方しか存在していない状態でもエラーが発生することなく開発を進めることができます。

先ほどの画像の例では「L_Sample」というレベルが Model のことも View のことも参照してしまっているので Model と View のどちらか一方 or 両方がなくなるとこのレベルではエラーが発生するのですが、Model がなくなっても View ではエラーは発生せず、その逆でもエラーは発生しないという意味です。

公式チュートリアルをやってみる

ここからはとりあえず公式ドキュメントのチュートリアルを進めてみます。
(後半はオリジナルの手順ですが)

プラグインの有効化

まずは「UMG Viewmodel」というプラグインを有効化します。

「Edit > Plugins > ALL PLUGINS」で全てのプラグインが表示されている状態で「viewmodel」などと検索して「UMG Viewmodel」にチェックを付けます。

UMG Viewmodel はこの記事の執筆時点ではまだベータ機能なのでこの画像のような警告が表示されるかと思いますが、「Yes」を押します。

この画像のようなメッセージが下に表示されるので「Restart Now」を押してエディタを再起動します。

ViewModel の作成

次は ViewModel となるブループリントを作成して Field Notify 変数と Field Notify 関数を追加します。

コンテンツブラウザで右クリックして「Blueprint Class」を選択します。
親クラスを選択するウィンドウが開くので「ALL CLASSES」で「viewmodel」などと検索して「MVVMViewModelBase (MVVM Base Viewmodel)」を選択し、「Select」を押します。

ここではわかりやすいように「BP_ViewModel」という名前にしました。

作成したブループリントをダブルクリックして開き、「CurrentHealth」と「MaxHealth」という Float 型の変数を追加して初期値を設定します。
今回はどちらも初期値を10にしました。

これらの変数の右側にあるベルアイコンのボタンを押して Field Notify にします。

その変数を選択している状態で「Details > Variables > Field Notify」にチェックを付けるという方法で Field Notify にしても OK です。

以下のような「GetHealthPercent」という関数を追加します。

この関数も Field Notify にする必要があるのですが、上の画像の状態だと My Blueprint でも Details でもそのプロパティがグレーアウトして有効化できない状態になってしまっています。

My Blueprint
Details

この場合はその関数を選択している状態で Details から「Pure」と「Const」にチェックを付けるとその関数を Field Notify にできるようになります。

関数を Field Notify にするための条件は以下の4つです。

  • ピュア関数であること
  • Const であること
  • アウトプット(戻り値)が1つのみあること
  • インプット(引数)がないこと

変数のときと同様にして GetHealthPercent() も Field Notify にします。

CurrentHealth を選択している状態で「Details > Variables > Field Notify」の右の「v」のボタンを押して「GetHealthPercent」を選択します。

同様の操作を MaxHealth に対しても行います。
これで CurrentHealth や MaxHealth の値が変更された(ブロードキャストされた)時に GetHealthPercent() も自動的にブロードキャストされるようになりました。

View の作成

次は View となるウィジェットブループリントを作成します。

コンテンツブラウザで右クリックして「User Interface > Widget Blueprint」を選択します。
親クラスを選択するウィンドウが開くので「User Widget」を選択します。

ここではわかりやすいように「WBP_View」という名前にしました。

Window から「View Bindings」と「Viewmodels」を選択してこれらのウィンドウを画面に追加します。

「Viewmodels > + Viewmodel」を押して先ほど作成した ViewModel を選択し、「Select」を押します。

Viewmodels で ViewModel を追加すると、そのウィジェットブループリントの「My Blueprint > VARIABLES」に「Viewmodel」というカテゴリが追加されて、その ViewModel 型の変数がそのカテゴリの中に追加されます。

この ViewModel 型の変数はそのウィジェットブループリントの中や他のクラスから参照できます。

下の画像のように Hierarchy に Canvas Panel と Progress Bar を追加します。
今回はわかりやすいように Progress Bar を「ProgressBar_HealthPercent」という名前に変更しました。

「View Bindings > + Add Widget」を押して要素を追加します。
追加した要素の「v」を押して先ほど Hierarchy に追加した Progress Bar を選択し、「Select」を押します。

上記の方法でウィジェットを追加しても良いのですが、Hierarchy でその Progress Bar を選択している状態では「+ Add Widget」が「+ Add Widget {Progress Bar の名前}」に変わっています。
その状態でボタンを押すと、その Progress Bar が設定されている状態の要素が追加されるので少しだけ手間が減ります。

また、対象のウィジェットを Hierarchy から View Bindings にドラッグ & ドロップして追加するのもオススメです。

そして、Hierarchy で対象のウィジェットを右クリックしたメニューに「Create Widget Binding」という項目があり、それを押してバインディングを作成することもできます。
(恐らく UE 5.6 で追加された機能)

追加した要素の鉛筆マークのある部分を押してその Progress Bar の「Percent」を選択し、「Select」を押します。

矢印の右側の鉛筆マークのある部分を押して「{作成した ViewModel} > Get Health Percent」を選択し、「Select」を押します。

以下のような状態になっていれば OK です。

View Bindings ではなく、下の画像のようにそのウィジェットの Details からバインドすることもできますが、この方法では ViewModel の変数のみ表示されて GetHealthPercent() は表示されませんでした…

「Edit > Project Settings… > Editor > Widget Designer (Team) > Compiler > Default Compiler Options > Property Binding Rule」をデフォルト値の「Allow」から「Prevent」に変更すると、Details でバインドする際に「+ Create Binding」の項目を表示させないようにすることもできます。

Property Binding Rule が「Allow」の状態
Property Binding Rule が「Prevent」の状態

「Edit > Project Settings… > Plugins > UMG Model View Viewmodel > UX > Allow Binding from Detail View」のチェックを外すと、Details で ViewModel をバインドさせないようにすることもできます。

Allow Binding from Detail View にチェックが付いている状態
Allow Binding from Detail View のチェックが外れている状態

Model の作成

次は Model となるブループリントを作成します。

コンテンツブラウザで右クリックして「Blueprint Class」を選択します。
親クラスを選択するウィンドウが開くので今回は「Actor」を押します。

ここではわかりやすいように「BP_Model」という名前にしました。

作成したブループリントをダブルクリックして開き、自作の ViewModel 型の「ViewModel」という変数を追加します。

その変数を選択している状態で Details から Instance Editable と Expose on Spawn にチェックを付けます。
これにより、このアクタをスポーンさせる時に外部から ViewModel を注入できるようになりました。

ViewModel の CurrentHealth の値を変更する処理をそのアクタに追加します。
今回は以下のように、Enter キーが押されるたびに CurrentHealth の値を1減らすという処理を追加しました。

今回の例では Enter キーが押された時に CurrentHealth の値を変更していますが、デフォルトの状態ではキーが押されたことを検知できないので「Class Defaults > Details > Input > Auto Receive Input」を「Player 0」に変更しておきましょう。

今回の例では下の画像のようにデクリメント演算子を使用したくなりますが、デクリメント演算子で CurrentHealth の値を変更するとその変更がブロードキャストされないようなので今回は Set ノードを使用しています。

この状態では CurrentHealth の値は変わるが、Progress Bar の Percent は変わらない(GetHealthPercent() がブロードキャストされない)

Model、View、ViewModel のインスタンス化

最後に Model、View、ViewModel をインスタンス化する処理を作成します。

今回はレベルブループリントにそれらの処理を追加します。

今回、レベルブループリントに追加した処理の概要は以下の通りです。

  1. ViewModel をインスタンス化してレベルブループリントの変数に保持する
  2. Model をインスタンス化して ViewModel をそれに渡す
  3. View をインスタンス化して ViewModel をそれに渡し、ビューポートに追加する
レベルブループリントの BeginPlay()
レベルブループリントの BeginPlay()
レベルブループリントの BeginPlay()

テスト

この状態でゲームを実行すると Enter キーを押すたびにバーが減っていき、Model が ViewModel の値を操作して、その変更が View に反映されているのがわかるかと思います。

従来のバインディング方法じゃダメなの?

公式チュートリアルを終えた自分は「わざわざウィジェットと ViewModel の変数/関数を View Bindings でバインドしなくても、従来のバインディング方法でバインドしちゃダメなの?」と疑問に思いました。

結論としては従来のバインディング方法では値が変更されたかどうかに関わらず、UI を更新する処理が毎フレーム呼ばれてしまいます。
なので制限時間タイマーなどの値が毎フレーム変わるものに対しては従来のバインディング方法を使用しても良いのかもしれませんが、そうでない場合(値が変更された時だけ UI を更新したい場合)は UMG Viewmodel のバインディングを使用した方が良いかと思います。

検証

ここからは UMG Viewmodel のバインディングを使用したときと、従来のバインディング方法でバインドしたときの違いをサクッと確認してみます。

まずは ViewModel で定義した GetHealthPercent() に PrintString を仕込みます。

UMG Viewmodel のバインディングを使用した場合

UMG Viewmodel のバインディングを使用している、公式チュートリアルの状態でゲームを実行すると CurrentHealth の値が変更された時だけ GetHealthPercent() が呼び出されているのがわかるかと思います。

従来のバインディング方法を使用した場合

では従来のバインディング方法でバインドした場合はどうでしょうか?

ウィジェットブループリントを開いて View Bindings で全てのバインディングを Remove し、Viewmodels も削除して空の状態にします。

View Bindings
Viewmodels

Designer から Graph に切り替えて自作の ViewModel 型の変数を追加します。

その変数を選択している状態で Details から Instance Editable と Expose on Spawn にチェックを付けます。
これにより、このウィジェットブループリントをインスタンス化する時に外部から ViewModel を注入できるようになりました。

Designer に戻って Progress Bar を選択し、「Details > Progress > Percent」の右のクリップマークを押し、「+ Create Binding」を選択します。

「Get_{Progress Bar の名前}_Percent」という名前の関数が作成されるので ViewModel の  GetHealthPercent() の戻り値を返す処理を組みます。

レベルブループリントにある、ウィジェットブループリントのインスタンス化の部分でエラーが発生している場合は ViewModel のピンを繋ぎ直してあげます。

これで UMG Viewmodel のバインディングを使用したときと同じように「Model と View は ViewModel のことは知っているけど、お互いのことは全く知らず、ViewModel は誰のことも知らない」という状態を維持できました。

この状態でゲームを実行すると CurrentHealth の値が変更されたかどうかに関わらず、GetHealthPercent() が毎フレーム呼び出されてしまっているのがわかるかと思います。

このように、Model が ViewModel の値を変更した時だけ View を更新したい場合は従来のバインディング方法ではなく、UMG Viewmodel のバインディングを使用した方が良いかと思います。

Creation Type

ウィジェットブループリントの Viewmodels で追加した ViewModel を選択している状態で Details から「Creation Type」というものを設定できるので、ここからは各 Creation Type でどのような違いがあるのかをまとめてみます。

Manual

まずは Manual についてです。
デフォルトで設定されている Creation Type はこの Manual かと思います。

Creation Type を Manual に設定するとそのウィジェットブループリントをインスタンス化する際、Viewmodels で追加した ViewModel のピンが出現します。

公式チュートリアルのようにウィジェットブループリントをインスタンス化する時に ViewModel も手動でインスタンス化してウィジェットブループリントに渡すか、そのウィジェットブループリント内などの他の場所や他のタイミングで ViewModel を手動でインスタンス化して View に保持させる必要があります。

なので、例えば以下のように ViewModel のピンに何も接続していない状態でそのウィジェットブループリントをインスタンス化してビューポートに追加後、そのウィジェットブループリントの持っている ViewModel が有効かどうか確認してみます。

この場合は「false」と表示され、ViewModel が無効な状態になっているのがわかります。
ウィジェットブループリントをインスタンス化する時に ViewModel も手動でインスタンス化して、それを渡してあげると「true」と表示されます。

Create Instance

次は Create Instance についてです。

Create Instance については公式ドキュメントで以下のように説明されています。

ウィジェットによってビューモデルの独自のインスタンスが自動的に作成されます。

UMG ビューモデル

Creation Type を Create Instance に設定すると「Create {ウィジェットブループリント名} Widget」に ViewModel のピンが表示されなくなります。

この状態で先ほどと同様に AddToViewport 後に ViewModel の状態を確認してみると「true」と表示されます。
Manual のときとは違って、ViewModel を手動で作成しなくても自動的に作成されたことがわかります。

Global Viewmodel Collection

次は Global Viewmodel Collection についてです。

Global Viewmodel Collection については公式ドキュメントで以下のように説明されています。

「グローバル ビューモデル コレクション」とは、MVVM ゲーム サブシステムにおける、グローバルにアクセス可能なビューモデルのリストです。 これらは、ゲーム オプション メニューの設定など、UI 全体を通じてアクセスする必要がある可能性がある変数の処理に適しています。 

UMG ビューモデル

Manual では ViewModel を手動でインスタンス化して管理する必要があり、Create Instance ではウィジェットブループリントのインスタンス化時に自動的に ViewModel もインスタンス化されるものの、その ViewModel を取得するときはそのウィジェットブループリントから取得する必要がありました。
しかし、Creation Type を Global Viewmodel Collection に設定すると、手動でインスタンス化した ViewModel を「Viewmodel Game Subsystem」というサブシステムに登録して他の場所からそれにアクセスできるようになります。

ViewModel を Viewmodel Game Subsystem に登録する処理に関しては公式ドキュメントで以下のように書かれていました。

これらの初期化には Game Instance クラスが便利です。

UMG ビューモデル

なので BP_GameInstance というゲームインスタンスを作成して Init() に以下の処理を追加してみました。
ViewModel(BP_ViewModel)をインスタンス化して、それを Viewmodel Game Subsystem → Get View Model Collection → Add View Model Instance に渡しています。
また、MVVM View Model Context という構造体も作成して Add View Model Instance に渡しています。

BP_GameInstance

この記事の執筆時点では MVVMGameSubsystem.h の UMVVMGameSubsystem の定義部分には以下のように書かれていて、ブループリントのグラフからこのサブシステムを取得するときに「Viewmodel Game Subsystem」ではなく「Viewmodel Game Subsytem」と表示されるのですが、これは普通に誤字なのでしょうか…?

UCLASS(MinimalAPI, DisplayName="Viewmodel Game Subsytem")
class UMVVMGameSubsystem : public UGameInstanceSubsystem

これまでは Model(BP_Model)外でインスタンス化した ViewModel を Model に注入していましたが、Global Viewmodel Collection では ViewModel が Viewmodel Game Subsystem に登録されているので、Model 内で ViewModel の値を変更する処理は以下のように書き換えることもできます。

BP_Model

Model と View のインスタンス化を行っているレベルブループリントは以下のようになりました。
ここでは ViewModel をインスタンス化したり、それを Model や View に渡したりといったこともしていません。

L_Sample(レベルブループリント)

自分の場合、GameInstance の Init() で Add View Model Instance すると結果が false になってしまいましたが、この画像のように1フレーム遅らせてから Add View Model Instance すると true になりました。

BP_GameInstance

また、ViewModel を Add View Model Instance する前に View をインスタンス化すると正常に動作しなかったのですが、この画像のように(少しゴリ押し実装ですが、)Add View Model Instance 後に View をインスタンス化するように変更すると正常に動作するようになりました。

L_Sample(レベルブループリント)

Add View Model Instance や Find View Model Instance するときに指定する Context Name や、View で設定する Global Viewmodel Identifier に関しては公式ドキュメントで以下のように書かれていました。

初期化モードとして [Global Viewmodel Collection] を選択した場合は、Add Viewmodel Instance ノードからの Context Name をグローバル ビューモデル識別子に提供します。 この名前はビューモデルのクラス名と一致する必要があります。

UMG ビューモデル

が、以下のようにそれらを全て「HelloWorld」という、ViewModel のクラス名と全く関係のない文字列に変更してみましたが、問題なく動作しました。
(公式ドキュメントのこの説明はそういう意味ではない…?)

BP_GameInstance
BP_Model
WBP_View

そして Add View Model Instance する時に設定した Context Name と、Find View Model Instance する時の Context Name、View で設定する Global Viewmodel Identifier が異なる文字列だと正常に動作しなくなります。
(その名前の ViewModel を Viewmodel Game Subsystem で探しても見つからないため)

Property Path

次は Property Path についてです。

Creation Type を Property Path に変更すると、そのプロパティのすぐ下に「Viewmodel Property Path」というプロパティが出現します。

この Viewmodel Property Path に設定すべき値については公式ドキュメントで以下のように説明されています。

 エディタ内の [Property Path] フィールドには、ピリオドで区切られた一連のメンバー名を入力します。

UMG ビューモデル

また、公式ドキュメントでは「GetPlayerController.Vehicle.ViewModel」という文字列が例として挙げられています。

今回は View(WBP_View)で GetViewModel() という、ViewModel を返す関数を追加してみました。
(この画像では Viewmodel Game Subsystem から Find View Model Instance したものを返していますが、ViewModel さえ返していればここの処理は何でもいいです。)

Graph から Designer に戻って Viewmodel Property Path を「GetViewModel」に変更します。

この状態でコンパイルすると「Viewodel ‘BP_ViewModel’: Viewmodel has an invalid Getter. Function ‘Get View Model’ is not readable at runtime.」というコンパイルエラーが発生してしまいましたが、GetViewModel() の「Details > Graph > Advanced > Const」にチェックを付けてその関数を const にするとコンパイルエラーが発生しなくなりました。

このように Creation Type を Property Path に設定すると、その View が ViewModel を取得するときに自作の関数を使用するようにできます。

Resolver

最後は Resolver についてです。

Resolver は UE 5.3 で新たに追加された Creation Type であり、この記事の執筆時点では Resolver についての説明は公式ドキュメントに記載されていません。

Added Resolver, a new way to select a Viewmodel. You can extend the Resolver and can implement your own logic on own to find/create the Viewmodel.

Unreal Engine 5.3 リリース ノート

Creation Type を Resolver に設定すると、View が特定の Resolver を使用して ViewModel を取得するようになります。

実際に試してみます。

まずはコンテンツブラウザで右クリックして「Blueprint Class」を選択します。
親クラスを選択するウィンドウが開くので「ALL CLASSES」で「viewmodel」などと検索して「MVVMViewModelContextResolver (Viewmodel Resolver)」を選択し、「Select」を押します。

ここではわかりやすいように「BP_ViewModelResolver」という名前にしました。

作成したブループリントをダブルクリックして開き、「My Blueprint > FUNCTIONS」の右の「Override」を押して「Create Instance」を選択します。

ViewModel を取得して返す処理を CreateInstance() に追加します。
(この画像では Viewmodel Game Subsystem から Find View Model Instance したものを返していますが、ViewModel さえ返していればここの処理は何でもいいです。)

この関数の戻り値は INotifyFieldValueChanged というインターフェースになっていますが、ViewModel のベースとなっているクラスである UMVVMViewModelBase はこの INotifyFieldValueChanged を実装しているので、ViewModel のインスタンスをそのまま返して OK です。
(↓参考:MVVMViewModelBase.h)

UCLASS(MinimalAPI, Blueprintable, Abstract, DisplayName="MVVM Base Viewmodel", meta=(ShowWorldContextPin="true"))
class UMVVMViewModelBase : public UObject, public INotifyFieldValueChanged

View のウィジェットブループリントを開いて Viewmodels で ViewModel を選択し、「Details > Viewmodel > Resolver」を自作のものに変更します。

これにより、この View は「BP_ViewModelResolver」という自作の Resolver を使用して ViewModel を取得するようになりました。

余談ですが、この Resolver というプロパティはその見た目からもわかるように Instanced なので、自作の Resolver に Instance Editable な変数を追加すると、View の Details からその変数の初期値を個別に設定できるようになります。

(↓参考:MVVMBlueprintViewModelContext.h)

UPROPERTY(EditAnywhere, Category = "Viewmodel", Instanced, meta = (EditInline))
TObjectPtr<UMVVMViewModelContextResolver> Resolver = nullptr;

最後に

参考記事・動画

基本用語

MVC / MVVM

UMG Viewmodel

お問い合わせ

    タイトルとURLをコピーしました