多くの場合、UI デベロッパーはバックエンド データとビジュアル デザインを複数のシステムに分割します。こうすることで、ユーザー インターフェース (UI) の構築プロセスがより効率的になって安定します。デザイナは UI のコードを壊してしまうことなくビジュアル表現を変更でき、プログラマーは完成したフロントエンドを必要とせずに、データとシステムの作業に集中できるようになるためです。Viewmodel プラグインは、ビューモデル アセットと View Bindings (バインディングを表示) 機能を提供することで、このワークフローの媒体として機能します。
ワークフロー
ビューモデルには UI に使用できる変数が含まれています。デザイナは [View Binding (バインディングの表示)] パネルを使ってこれらを UI の各入力フィールドにバインドでき、プログラマーはビューモデルを自身でビルドし、適切な場合にそれをアプリケーションのコードに取り入れることができます。
ビューモデルを UMG ウィジェットに追加すると、そのウィジェットにアクセスして関数を呼び出したり、変数を更新したりできます。ビューモデルでは、入力フィールドがビューモデルの変数にバインドされているあらゆるウィジェットにアップデートをプッシュできます。
ウィジェットが更新されるのは変数を更新したときだけであるため、これは生の属性のバインディングよりも効率の良い代替手法と考えられます。またこの手法では、イベント駆動型の UI フレームワークを手動で設定する時間をかけずに、そのメリットを得ることができます。
必要な設定
ビューモデルをプロジェクトの UI に使用するには、[Plugins (プラグイン)] メニューで UMG Viewmodel プラグインを有効にします。
このプラグインを有効にしないと UMVVMViewModelBase クラスを使用できず、UMG で View Bindings を使用することもできません。
ビューモデル
ビューモデルの主な目的は次の 2 つです。
-
UI に必要な変数のマニフェストを保持する。
-
UI と残りのアプリケーションとの間のコミュニケーション媒体を提供する。
UI に変数を認識させる必要がある場合は、その変数をビューモデルに追加し、そのビューモデルをウィジェットに加えて、入力フィールドをその変数にバインドします。変数を更新する必要がある場合は、そのビューモデルへの参照を取得して、そこで設定を行います。変数によって、それらがバインドされたウィジェットに変更についての通知がなされ、更新されます。
ビューモデルを作成する
C++ でビューモデルを作成するには、INotifyFieldValueChanged インターフェースを実装します。クラス UMVVMViewModelBase では実装されているため、拡張のみ行います。ビューモデルは FieldNotify 変数に依存する UObjects であり、それらにバインドされたウィジェットに変更をブロードキャストする関数です。
Unreal Engine の今後のリリースでは、ビューモデルをブループリントでも作成できるようにする予定です。
ビューモデル システムは、ブループリントと同じアクセス権を使用します。ビューモデル システムでアクセスする変数と関数は、ブループリントでもアクセスできる必要があります。
FieldNotify を含む変数
ビューモデル内で変数を定義する際は、それぞれの変数に FieldNotify 指定子を含む UPROPERTY マクロを含める必要があります。次は、FieldNotify 変数で使用される指定子の完全なリストです。
| UPROPERTY 指定子 | 説明 |
|---|---|
FieldNotify |
プロパティをフィールド通知のブロードキャスト システムで使用できるようにします。 |
Setter |
変数が自身の値を設定できることを示します。ここでは Setter 関数の名前が「Set[Variable Name]」の形式になることを想定します。 |
Setter="[FUNCTION_NAME]" |
Setter ですが、Setter として使用するカスタム関数の名前を提供することもできます。 |
Getter |
変数が自身の値を取得できることを示します。ここでは Getter 関数の名前が「Get[VariableName]」の形式になることを想定します。 |
Getter="[FUNCTION_NAME]" |
Getter ですが、Getter として使用するカスタム関数の名前を提供することもできます。 |
値の変更をウィジェットにブロードキャストするには FieldNotify 指定子が必要です。この指定子を含むすべての変数が [View Binding] メニューに表示されます。FieldNotify がない場合は、OneTime モードでのみ変数にバインドすることが可能です。
Setter と Getter のどちらの指定子を提供するかは選択することができます。いずれも提供しない場合は、当該の操作をこのクラス外で実行できなくなります。変数自体の通知をトリガーしたいだけの場合は、関数の名前なしに Getter か Setter 指定子を指定します。これらは変数にアクセスすると自動的にスクリプト (ブループリント、ビューモデル、シーケンサーなど) から実行されますが、cpp コードから自動的に呼び出されることはありません。
カスタム Getter (ゲッター) と Setter (セッター) が便利なのは次のような状況です。
-
変数を取得する前に操作を実行する必要がある。
-
変数の設定時に他の関数をトリガーするか、他の変数を更新したい。
カスタム Getter または Setter 関数を作成する際は UFUNCTIONS にはしないでください。ブループリント内で Get および Set 関数が重複してリスト出力されるためです。この変数の UPROPERTY マクロでは、変数の Get および Set の各ノードを通じてこれらへのアクセスをすでに提供しています。また、すべてのカスタム ゲッターは const 関数にしてください。唯一の役割が値を返すことのみであるためです。
C++ ではユーザーは Getter または Setter 関数の呼び出しを強制されないため、変数を保護するかプライベートにしてユーザーの間違いを防ぐ必要があります。
protected:
/**
* 変数はビューモデル システムからアクセスしたり、書き込んだりすることができます。TwoWay (双方向) を有効にします。
* FieldNotify では、OneWay (一方向) バインディング モードが有効になりました (通知が有効になります)。
* cpp では保護されます (ユーザーは Getter/Setter の使用が強制されます)。
* ブループリントで公開されます
* ブループリントや ViewBindings などでは Getter/Setter を使用します。
*/
UPROPERTY(BlueprintReadWrite, FieldNotify, Setter, Getter)
int32 MyVariableA;
private:
/**
* 変数はビューモデル システムからアクセスできます。
* FieldNotify がない場合は、OneTime バインディング モードのみが利用可能です。
* cpp ではプライベートです (ユーザーは Getter/Setter の使用が強制されます)。
* ブループリントで公開されます (`AllowPrivateAccess` による)。
* ブループリントや ViewBindings などでは Getter/Setter を使用します。
*/
UPROPERTY(BlueprintReadOnly, FieldNotify, Setter, Getter, meta=(AllowPrivateAccess))
int32 MyPropertyB;
FieldNotify を含む関数
FieldNotifies をブロードキャストするカスタム関数を作成し、ウィジェットのプロパティを変数と同様にこれらの関数にバインドできます。このように使用する関数は次の要件を満たしている必要があります。
-
FieldNotifyとBlueprintPure指定子を含むUFUNCTIONマクロが含まれる。 -
引数を受け取らない。
-
const関数である。 -
単一の値のみを返す (out 引数以外)。
関数は、他の変数から派生または変換された値にバインドされるウィジェットを必要とするものの、その情報を保持する追加の変数を作成したくない場合に便利です。
たとえば、次の関数は、キャラクターの現在のヘルス値を最大ヘルス値で割ったパーセンテージ値を返す FieldNotify です。
UFUNCTION(BlueprintPure, FieldNotify)
float GetHealthPercent() const
{
//ゼロで除算しないように確認します
if (MaxHealth != 0)
{
return (float) CurrentHealth / (float) MaxHealth;
}
else
{
return 0;
}
}
CurrentHealth や MaxHealth が変更されたときは、GetHealthPercent の変更を手動で通知する必要があります。
マクロを使って FieldNotify をトリガーする
変数の変更時には、関数でビューモデル通知マクロの 1 つを呼び出して、バインドされているウィジェットに変更をブロードキャストする必要があります。利用可能なマクロは次のとおりです。
| ビューモデル マクロ | 説明 |
|---|---|
| UE_MVVM_BROADCAST_FIELD_VALUE_CHANGED([メンバー名]) | イベントをブロードキャストします。 |
| UE_MVVM_SET_PROPERTY_VALUE([メンバー名], [新しい値]) | フィールド値が変更されたかどうかをテストしてから、フィールドの新しい値を設定し、イベントをブロードキャストします。 |
SET_PROPERTY_VALUE マクロは BROADCAST_FIELD_VALUE マクロと同じ処理を行いますが、SET_PROPERTY_VALUE マクロでは値を割り当ててブロードキャストする前に値が変更されたかどうかを確認します。これはビューモデル用のセッターを作成する際によく行われる確認で、利便性のために含まれています。
BROADCAST_FIELD_VALUE_CHANGED マクロでは、特定の値に直接バインドされているウィジェットに通知したい場合や、マクロで関数の名前を受け取れる場合に、そのいずれかの変数自体を受け取ることができます。
例
次のコード スニペットは、前述の概念を使ったビューモデル クラスの一例です。GetHealthPercent は Getter と Setter とは異なる個別の関数として定義されていますが、Setter により、変数自体が変更されたときの通知に加えて呼び出されます。
UCLASS(BlueprintType)
class UVMCharacterHealth : public UMVVMViewModelBase
{
GENERATED_BODY()
private:
UPROPERTY(BlueprintReadWrite, FieldNotify, Setter, Getter, meta=(AllowPrivateAccess))
int32 CurrentHealth;
UPROPERTY(BlueprintReadWrite, FieldNotify, Setter, Getter, meta=(AllowPrivateAccess))
int32 MaxHealth;
public:
void SetCurrentHealth(int32 NewCurrentHealth)
{
if (UE_MVVM_SET_PROPERTY_VALUE(CurrentHealth, nNewCurrentHealth))
{
UE_MVVM_BROADCAST_FIELD_VALUE_CHANGED(GetHealthPercent);
}
}
void SetMaxHealth(int32 newMaxHealth)
{
if (UE_MVVM_SET_PROPERTY_VALUE(MaxHealth, NewMaxHealth))
{
UE_MVVM_BROADCAST_FIELD_VALUE_CHANGED(GetHealthPercent);
}
}
int32 GetCurrentHealth() const
{
return CurrentHealth;
}
int32 GetMaxHealth() const
{
return MaxHealth;
}
public:
UFUNCTION(BlueprintPure, FieldNotify)
float GetHealthPercent() const
{
if (MaxHealth != 0)
{
return (float) CurrentHealth / (float) MaxHealth;
}
else
return 0;
}
};
ビューモデルをウィジェットに追加する
ビューモデルは、UMG の [Viewmodels] ウィンドウでウィジェットに追加できます。このウィンドウにアクセスするには、UMG の [Designer (デザイナ)] タブで [Window (ウィンドウ)] > [Viewmodels (ビューモデル)] に行きます。
[+ Viewmodel] ボタンをクリックしてプロジェクトのビューモデルの 1 つを選び、[Select (選択)] をクリックします。
ビューモデルを初期化する
[Viewmodels] ウィンドウでビューモデルをクリックすると、[Creation Type (作成タイプ)] 設定を使ってビューモデルの初期化方法を選択できます。選択肢は次のとおりです。
| ビューモデルの作成タイプ | 説明 |
|---|---|
| Create Instance (インスタンスを作成) | ウィジェットによってビューモデルの独自のインスタンスが自動的に作成されます。 |
| Manual (手動) | ウィジェットによってビューモデルとともにヌルとして初期化されます。ユーザーはインスタンスを手動で作成し、それを割り当てる必要があります。 |
| Global Viewmodel Collection (グローバル ビューモデル コレクション) | プロジェクトに含まれるあらゆるウィジェットで使用できる、グローバルに利用可能なビューモデルを指します。グローバル ビューモデル識別子 が必要になります。 |
| Property Path (プロパティ パス) | 初期化時に、ビューモデルを検索するための関数を実行します。ビューモデルの [Property Path] では、ピリオド区切りのメンバー名を使用します。たとえば GetPlayerController.Vehicle.ViewModel です。プロパティ パスはウィジェットを基準とした相対パスです。 |
ビューモデルは、ウィジェットと必ずしも 1 対 1 の関係にあるわけではありません。ビューモデルを設定してウィジェットに割り当てるにはさまざまな方法があり、1 つのビューモデルからの情報を複数のウィジェットで受け取るようにすることも可能です。以下では、それぞれの作成タイプについて説明します。
Create Instance (インスタンスを作成)
[Create Instance] 作成メソッドでは、ウィジェットの固有のインスタンスそれぞれに対して、ビューモデルの新しいインスタンスを自動的に作成します。これは、ビューポート内に同じウィジェットの複数のコピーがあり、その 1 つに対してビューモデルの変数を変更した場合、そのウィジェットのみが更新されて、その他すべてのコピーは変わらずに維持されることを意味します。同じように、同じビューモデルを使用する異なるウィジェットを複数作成した場合、これらは互いの情報への変更を認識しません。複数のウィジェットに同じデータを参照させる場合は、以下で説明する他のメソッドが便利です。また、作成後にビューモデルを設定するオプションもあります。
C++ での初期化呼び出し後、またはブループリントでの初期化呼び出し中に、ビューモデルを割り当てることができます。ビューモデルが設定されていない場合、システムでは新しいインスタンスのみが作成されます。ビューモデルの作成は、PreConstruct イベントと Construct イベントの間に行われます。
Manual
[Manual] 作成メソッドでは、ユーザー自身がアプリケーションのコード内にビューモデルのインスタンスを作成し、それを手動でウィジェットに割り当てる必要があります。ウィジェットにはビューモデルのオブジェクト参照がありますが、ユーザーがそれにビューモデルを割り当てるまでは、その値はヌルのままです。ビューモデルを割り当てると、UI を更新したい場合に、ウィジェットへの参照を取得せずにビューモデルを更新できるようになります。
ここで、同じビューモデルを UI に含まれる複数の異なるウィジェットに割り当てることができます。
Property Paths (プロパティ パス)
[Property Path] 作成メソッドでは、より明確でコード サポートの必要性が少ない代替手法を使用できます。他のクラスでウィジェット内部にアクセスしてそのビューモデル参照を設定するのではなく、ウィジェットで一連の関数呼び出しと参照とともにアクセスしてビューモデルをフェッチします。エディタ内の [Property Path] フィールドには、ピリオドで区切られた一連のメンバー名を入力します。また、このフィールドでは、これらの関数の呼び出しの起点として Self を想定しています。つまり、常に編集中のウィジェットから開始されることを意味します。
[Property Path] フィールドでは Self への参照で開始することを想定しているため、手動で Self をプロパティ パスに指定しないでください。
次の例では、ウィジェットの所有するプレイヤー コントローラーをフィールドが取得し、次に現在制御しているビークルのビューモデルを取得します。
GetPlayerController.Vehicle.ViewModel
ブループリントで定義した関数を呼び出すこともできます。これによって、プロパティ パスのロジックを簡略化して柔軟性を高めることが可能になります。次の例の関数は、ウィジェットを所有するキャラクターから Character Health ビューモデルを取得しています。
その後、この関数の名前をプロパティ パスとして使用できるようになります。
GetHealthViewModel
Global Viewmodel Collection (グローバル ビューモデル コレクション)
「グローバル ビューモデル コレクション」とは、MVVM サブシステム における、グローバルにアクセス可能なビューモデルのリストです。これらは、ゲーム オプション メニューの設定など、UI 全体を通じてアクセスする必要がある可能性がある変数の処理に適しています。ビューモデルをブループリント内でグローバル ビューモデル コレクションに追加するには、以下のステップに従います。
-
MVVM サブシステムに参照を追加します。
-
MVVM Subsystem ノードのピンをドラッグし、Get Global Viewmodel Collection を呼び出します。
-
Global Viewmodel Collection のピンをドラッグし、Add Viewmodel Instance を呼び出します。
これらのステップを終えると、ビューモデルのインスタンスを構築し、このノードを使ってそれをコレクションに加えることができます。これらの初期化には Game Instance クラスが便利です。
初期化モードとして [Global Viewmodel Collection] を選択した場合は、Add Viewmodel Instance ノードからの Context Name を グローバル ビューモデル識別子 に提供します。この名前はビューモデルのクラス名と一致する必要があります。たとえば、ビューモデルの名前が「VM_GraphicsOptions」である場合は、その名前をコンテキスト名 (Context Name) とグローバル ビューモデル識別子の両方として提供します。
ビューモデルのメンバーにアクセスする
ビューモデルをウィジェットに割り当てると、ウィジェットのプロパティとしてそのビューモデルにブループリント内でアクセスできるようになり、ビューモデルが [Variables (変数)] > [Viewmodel] カテゴリに表示されます。ビューモデルへの参照を取得すると、その変数と関数にアクセスできるようになります。
ビューモデルで設定されるオプションによっては、内部の関数とセッターのすべてにはアクセスできない場合があります。
配列で作業する
通常、ビューモデルで配列にアクセスすることはできません。ビューモデルで配列にアクセスするには、ビューモデル自体から配列にメンバーを直接追加、削除、取得するための独自の FieldNotify 関数を作成してください。
配列はビュー (ListView、TreeView、TileView) で使用することができます。配列内で要素が追加、削除、移動された場合は通知する必要があります。
ビューモデルの作成に関するベスト プラクティス
ビューモデルを作成する際は、巨大なものにするのではなく、小さく簡潔なビューモデルにするよう心がけてください。こうすることで、UI のデバッグが容易になります。
たとえば、RPG のキャラクターを表現するビューモデルを作成する際に、特性やインベントリ、ヒット ポイントなどの完全な配列を含めることはできますが、このビューモデルに依存する UI 要素のいずれかをデバッグするときに、ビューモデルのデータを埋めるために、最初にキャラクター全体をスポーンしなければなりません。これを異なるコンポーネントに分割した場合は、デバッグ時にビューモデルにテスト データを提供する作業が容易になります。
ビューモデルは別のビューモデル内にネスティングできます。一連の複雑なデータを表現する際に役立つ場合があります。
View Bindings (バインディングを表示)
ビューモデルを作成したら、UMG エディタでそれをウィジェットに追加して [View Bindings] ウィンドウでターゲティングできます。
View Binding をウィジェットに追加する
View Binding をウィジェットに追加するには、[Details (詳細)] パネルにあるプロパティ バインディングのドロップダウンを使って追加する方法と、[View Binding] メニューを使ってすべてのウィジェットのバインディングを管理する方法の 2 つがあります。
[Details] パネルを使用する
[Details] パネルで View Bindings を使用するには、バインディングを追加する先のウィジェットを選択し、バインドするプロパティの [Bind (バインド)] ドロップダウンをクリックします。ドロップダウンの下部に、そのプロパティに有効なすべてのビューモデル変数と関数が表示されます。そのうちの 1 つをクリックしてバインディングを割り当てます。
[View Binding] メニューを使用する
[View Bindings] ウィンドウでは、View Binding の動作をより細かく制御できます。UMG の [Designer] タブで [Window] > [View Binding] をクリックして [View Binding] ウィンドウを開きます。
[+ Add Widget (+ ウィジェットを追加)] をクリックして、[View Binding] のリストにエントリを追加します。
[Viewmodels] ウィンドウに追加したすべてのビューモデルをバインディングに使用できます。
Unreal Engine の現在のバージョンでは、[View Bindings] メニューを使ってビューモデルをバインドし、[Details] パネルでそれを再度割り当てると、そのバインディングが無効になることがあります。これを修正するには、当該のバインディングを [View Bindings] メニューから削除して、再び割り当ててください。
View Binding を設定する
View Bindings には次の情報が含まれています。
- バインディングの ターゲット ウィジェット と ターゲット ビューモデル。
- バインドする ウィジェット プロパティ と ビューモデル プロパティ。
- バインディングの 方向。2 つのターゲット プロパティ間での情報の流れを定義します。
- バインディングの 更新タイプ。
- 有効 / 無効 のトグル。無効にすると、ランタイムからバインディングが削除されます。これはバインディングがコンパイルされず、ランタイム時に利用できないことを意味します。
以下のセクションでは、それぞれのフィールドの詳細と設定方法について説明します。
ターゲット ウィジェットを選択する
View Binding エントリの最初のドロップダウンでは、View Binding を追加する先のウィジェットを選択します。ウィジェットをクリックすると、ドロップダウンにウィジェットの階層が表示され、その親ウィジェット自体か、いずれかの子ウィジェットを選択できます。[Select] をクリックして選択内容を確定します。
View Binding エントリを作成する
ターゲット ウィジェットの下には、ビューモデルのバインディングの設定先となる個別のプロパティのエントリがあります。各バインディングは、それぞれが属するウィジェットと並んでいます。1 つのウィジェットに複数のバインディングを追加するには、ウィジェットのドロップダウンの隣にある [+] ボタンをクリックします。それぞれのバインディングは異なるプロパティをターゲティングする必要があります。
ウィジェット プロパティを選択する
View Binding エントリの最初のドロップダウンには、ターゲット ウィジェットの変数と関数がリスト表示されます。たとえば、Progress Bar (進捗バー) ウィジェットを使用する場合は、[Percent (パーセンテージ)] プロパティを使用できます。
C++ で定義されたプロパティまたは関数をこのリストに表示するには、UFUNCTION または UPROPERTY マクロを使ってそれを Unreal Engine の反射システムに認識させる必要があります。ブループリントで定義された変数と関数は自動的に利用可能になります。
ビューモデルのプロパティを選択する
3 つ目のドロップダウンでは、ターゲティングするビューモデルと、View Binding に使用するそのプロパティの両方を選択します。ドロップダウンをクリックすると、このウィジェットに追加したビューモデルのリストが表示されます。
View Bindings で利用可能な変数と関数のリストを表示するために使用するビューモデルをクリックします。これは One Way To Widget であるため、変数と関数をここに表示するには、それらに FieldNotify 指定子が含まれている必要があります。
バインド方向を設定する
2 つ目のドロップダウンでは、View Binding の バインド方向 を選択します。これにより、ウィジェットとビューモデル間の情報の流れが定義されます。
利用可能なバインド方向は次のとおりです。
| バインド方向 | 説明 |
|---|---|
| One Time to Widget (ウィジェットに一度) | バインディングは、ビューモデルからウィジェットへ一度のみ適用されます。選択したウィジェット プロパティが更新されます。 |
| One Way to Widget (ウィジェットに一方向) | バインディングは、ビューモデルからウィジェットへの一方向にのみ適用されます。ビューモデル内の対応する変数が更新されると、変数が変更されたことをウィジェットに通知し、選択したウィジェット プロパティを更新します。関数を選択していた場合は、その関数を呼び出すことで、選択したウィジェット プロパティを更新します。 |
| One Way to Viewmodel (ビューモデルに一方向) | バインディングは、ウィジェットからビューモデルへの一方向にのみ適用されます。ウィジェットの選択したプロパティがユーザーまたはコードのいずれかによって変更された場合に、その変更をビューモデル プロパティに適用します。典型的な例としては、ユーザー編集のテキスト フィールドやグラフィック オプションがあります。 |
| Two Way (双方向) | バインディングが双方向に適用されます。 |
すべてのバインディングは、PreConstruct イベントと Construct イベントの間に 1 回実行されます。バインド方向が TwoWay の場合、OneWay バインディングのみが実行されます。ビューモデルの値が SetViewmodel で変更されると、そのビューモデルを含むすべてのバインディングが実行されます。
変換関数を使用する
変数に直接バインドする方法の代替手段として、変換関数 を使う方法があります。この関数では、整数値をテキストに変えるなど、ビューモデルからの変数を異なるデータ型に変換できるインターフェースが提供されます。変換関数は、ビューモデル プロパティのドロップダウンの、ビューモデルのリストの下に表示されます。
変換関数を選択すると、その関数の引数を設定するための一連のオプションが、View Binding のドロップダウンの下に表示されます。
これらのプロパティの 1 つで [link (リンク)] ボタンをクリックすると、そのプロパティをビューモデルの値にバインドできます。
新しい変換関数はグローバルに追加することも、UserWidget (ウィジェット ブループリント) に追加することもできます。この関数をイベント、ネットワーク、非推奨、エディタ専用にすることはできません。関数はブループリントに表示され、1 つの入力引数と 1 つの戻り値を含める必要があります。また、グローバルに定義されている場合は関数も静的である必要があり、UserWidget に定義されている場合は pure で const である必要があります。