虚幻引擎 UMG ViewModel 视图模式

发布时间 2023-09-11 18:06:19作者: xia0xia0

通常,UI 开发人员将后端数据和视觉设计分解为单独的系统。这使得构建用户界面 (UI) 的过程破坏性更小且更高效,因为设计人员可以在不破坏 UI 背后的代码的情况下更改视觉呈现,程序员可以专注于数据和系统,而无需完整的前端。视图模型插件通过引入视图模型资产和视图绑定,为这个工作流提供了一个媒介。

工作流程

视图模型包含可在 UI 中使用的变量。设计人员可以使用“视图绑定”面板将 UI 中的字段绑定到这些字段,而程序员可以自己构建视图模型,并根据需要将它们与应用程序的代码绑定在一起。

将视图模型添加到 UMG 小部件后,您可以访问它并调用函数或更新变量。视图模型将更新推送到字段绑定到其变量的任何小部件。

这是原始属性绑定的更有效的替代方法,因为它仅在更新变量时更新小部件。它还提供了事件驱动 UI 框架的优势,而无需手动设置实现时间。

必需的设置

要在项目的UI中使用视图模型,请在插件菜单中启用UMG视图模型插件

如果不启用此插件,您将无法使用该类,并且您将无法在UMG中使用视图绑定。UMVVMViewModelBase

视图模型

视图模型有两个主要用途:

  • 维护 UI 所需变量的清单。

  • 在 UI 和应用程序的其余部分之间提供通信媒介。

当您需要 UI 识别变量时,可以将其添加到视图模型,然后将视图模型添加到小部件并将字段绑定到该小组件。当您需要更新变量时,您可以获取对视图模型的引用并从那里设置它们。然后,它们将更改通知绑定到这些变量的小部件并更新它们。

创建视图模型

您可以通过实现接口在C++中创建视图模型。该类实现了它,您可以简单地扩展它。视图模型依赖于 FieldNotify 变量和函数将更改广播到绑定到它们的小部件。INotifyFieldValueChangedUMVVMViewModelBaseUObjects

在虚幻引擎的未来版本中,可以使用蓝图创建视图模型。

Viewmodel 系统使用与蓝图相同的访问权限。变量和函数需要在蓝图中可访问,以便视图模型系统访问。

具有字段通知的变量

在视图模型中定义变量时,每个变量都必须有一个带有说明符的宏。用于 FieldNotify 变量的说明符的完整列表如下所示:UPROPERTYFieldNotify

 

U属性说明符

描述

FieldNotify

使该属性可用于字段通知广播系统。

Setter

声明变量应允许设置其值。这假设 Setter 函数将具有格式为Set[Variable Name]

Setter="[FUNCTION_NAME]"

作为 Setter,但您可以提供自定义函数的名称以将其用作 Setter。

Getter

声明变量应允许检索其值。这假设 Getter 函数将具有格式的名称Get[VariableName]

Getter="[FUNCTION_NAME]"

作为 Getter,但您可以提供自定义函数的名称以将其用作 Getter。

需要说明符才能将值更改广播到小组件。具有此说明符的任何变量都将出现在“视图绑定”菜单中。否则,您将只能在一次性模式下绑定到变量。FieldNotifyFieldNotify

您可以决定是否提供 或 说明符。如果您决定不提供其中之一,您将无法在此类之外执行该操作。当您只想触发变量本身的通知时,仅指定没有函数名称的 Getter 和 Setter 说明符就足够了。访问变量时,它们会自动从脚本(蓝图、视图模型、序列器等)执行。它们不会从 cpp 代码自动调用。SetterGetter

自定义设置器和设置器在以下情况下很有用:

  • 您需要在检索变量之前执行操作。

  • 您希望在设置变量时触发其他函数或更新其他变量。

如果创建自定义 Getter 或 Setter 函数,请不要创建它们,因为这将为蓝图中的获取和设置函数创建冗余列表。变量的宏已以变量的 Get 和 Set 节点的形式提供对这些节点的访问。您还应该创建任何自定义 Getters 函数,因为它们的唯一工作应该是返回一个值。UFUNCTIONSUPROPERTYconst

由于C++不会强制用户调用 Getter 或 Setter 函数,因此变量应受到保护或私有以防止用户错误。

protected:

    /**
    * The variable can be accessed from the viewmodel system and can be written to. Enable TwoWay.
    * FieldNotify enabled the OneWay binding mode (enable notification).
    * It's protected in cpp (force the user to use the Getter/Setter).
    * It's public in Blueprint
    * Blueprint/ViewBindings/... will use the Getter/Setter.
    */

    UPROPERTY(BlueprintReadWrite, FieldNotify, Setter, Getter)
    int32 MyVariableA;

    private:

    /**
    * The variable can be accessed from the viewmodel system.
    * No FieldNotify, only the OneTime binding mode is available.
    * It's private in cpp (force the user to use the Getter/Setter).
    * It's public in Blueprint (because of `AllowPrivateAccess`.
    * Blueprint/ViewBindings/... will use the Getter/Setter.
    */

    UPROPERTY(BlueprintReadOnly, FieldNotify, Setter, Getter, meta=(AllowPrivateAccess))
    int32 MyPropertyB;

具有字段通知的函数

您可以创建广播 的自定义函数,并且可以像绑定变量一样将小部件的属性绑定到这些函数。以这种方式使用的函数必须遵循以下要求:FieldNotifies

  • 它们必须具有带有 和 说明符的宏。UFUNCTIONFieldNotifyBlueprintPure

  • 他们绝不能接受任何争论。

  • 它们必须是函数。const

  • 它们必须只返回单个值(无 out 参数)

如果您希望将小部件绑定到派生或转换自其他变量的值,但又不想创建额外的变量来保存该信息,则函数可能很有用。

例如,以下函数是一个 FieldNotify 函数,用于返回字符当前生命值的百分比值除以其最大生命值:

UFUNCTION(BlueprintPure, FieldNotify)

float GetHealthPercent() const
{
    //Check to avoid dividing by zero
    if (MaxHealth != 0)
    {
        return (float) CurrentHealth / (float) MaxHealth;
    }
    else
    {
        return 0;
    }
}

您需要手动通知何时更改或更改。GetHealthPercentCurrentHealthMaxHealth

触发字段使用宏通知

更改变量时,函数需要调用其中一个 Viewmodel 通知宏来将更改广播到绑定小部件。可用宏的列表如下所示:

 

视图模型宏

描述

UE_MVVM_BROADCAST_FIELD_VALUE_CHANGED([会员名称])

广播事件。

UE_MVVM_SET_PROPERTY_VALUE([成员名称], [新值])

测试字段的值是否已更改,然后设置字段的新值并广播事件。

宏执行与宏相同的操作,只是宏在分配值然后广播之前检查值是否更改。这是为视图模型创建设置器时非常常见的检查,为方便起见,将其包括在内。SET_PROPERTY_VALUEBROADCAST_FIELD_VALUESET_PROPERTY_VALUE

如果要通知直接绑定到该值的小部件,宏可以采用变量本身,也可以采用函数的名称。BROADCAST_FIELD_VALUE_CHANGED

以下代码片段是使用上述概念的 Viewmodel 类的示例。GetHealthPercent被定义为与Getters和Setters分开的函数,但Setters除了在变量本身发生更改时发出通知之外,还会调用它。

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的“视图模型”窗口中将视图模型添加到小部件中。您可以在UMG设计器选项卡的“窗口>视图模型”下找到它。

在“窗口”下拉列表中找到“视图模型”窗口

单击 + 视图模型按钮以选择项目的视图模型之一,然后单击选择

初始化视图模型

在“视图模型”窗口中单击视图模型时,可以选择如何使用“创建类型”设置对其进行初始化。可以使用以下方法:

 

视图模型创建类型

描述

创建实例

小组件会自动创建自己的视图模型实例。

手动

该小组件初始化时视图模型为 null,您需要手动创建实例并分配它。

全局视图模型集合

指可由项目中的任何小部件使用的全局可用的视图模型。需要全局视图模型标识符

属性路径

初始化时,执行函数以查找视图模型。视图模型属性路径使用以句点分隔的成员名称。例如:。属性路径始终相对于小组件。GetPlayerController.Vehicle.ViewModel

视图模型不一定与小部件具有一对一的关系。有多种方法可以设置它们并将它们分配给小部件,并且多个小部件可以从单个视图模型中获取信息。下面详细介绍了每种创建类型方法。

创建实例

“创建实例”方法会自动为小组件的每个唯一实例创建视图模型的新实例。这意味着,如果视口中有多个同一构件的副本,并且更改了其中一个小部件的 Viewmodel 变量,则只有该小部件会更新,而所有其他副本将保持不变。同样,如果您创建多个使用同一视图模型的不同小部件,则这些小部件都不会知道彼此信息的更改。当您希望多个小部件引用相同的数据时,下面的其他方法很有用。您还可以选择在视图模型创建后对其进行设置。

您可以在C++初始化回调之后或在蓝图中初始化回调期间分配视图模型。仅当视图模型未设置时,系统才会创建新实例。视图模型的创建是在 PreConstruct 和 Construct(构造)事件之间完成的。

手动

手动创建方法要求您自己在应用程序代码中的某个位置创建视图模型的实例,然后手动将其分配给小组件。该小组件具有视图模型对象引用,但在您为其分配视图模型之前,它将具有空值。分配视图模型后,您可以随时更新 UI 时更新它,而无需获取对小部件的任何引用。

这提供了将同一视图模型分配给 UI 中的多个不同小部件的机会。

属性路径

属性路径创建方法提供了一种替代方法,该方法可能更干净,并且需要较少的代码支持。不是其他类到达小部件内部来设置其视图模型引用,而是使用一系列函数调用和引用来获取视图模型。编辑器中的“属性路径”字段需要一系列以句点分隔的成员名称,并假定调用这些函数的起点是 。换句话说,它总是从您正在编辑的小部件开始。Self

不要在“属性路径”中手动指定,因为“属性路径”字段已假定您从引用 开始。SelfSelf

例如,以下字段获取控件的拥有玩家控制器,然后获取当前控制的车辆上的视图模型:

GetPlayerController.Vehicle.ViewModel

您还可以调用在蓝图中定义的函数,这有助于压缩属性路径的逻辑并提供额外的灵活性。例如,以下函数从拥有小组件的角色获取角色运行状况视图模型:

然后,可以使用此函数的名称作为属性路径:

GetHealthViewModel

全局视图模型集合

全局视图模型集合是 MVVM 子系统中全局可访问的视图模型列表。这些非常适合处理可能需要在整个 UI 中访问的变量,例如游戏选项菜单的设置。要将视图模型添加到蓝图中的全局视图模型集合,请执行以下步骤:

  1. 添加对 MVVM 子系统的引用。

  2. 从 MVVM 子系统节点拖动一个引脚,然后调用获取全局视图模型集合

  3. 从全局视图模型集合中拖动一个图钉,然后调用添加视图模型实例

然后,可以构造视图模型的实例,并将其添加到具有此节点的集合中。游戏实例类是初始化这些内容的方便位置。

选择“全局视图模型集合”作为初始化模式时,请提供“全局视图模型标识符”中“添加视图模型实例”节点中的“上下文名称”。此名称必须与视图模型的类名匹配。例如,如果视图模型被调用VM_GraphicsOptions则需要将其作为上下文名称和全局视图模型标识符提供。

访问视图模型的成员

将视图模型分配给控件后,可以在蓝图中将其作为控件的属性进行访问。它将位于“变量>视图模型”类别下。获得对视图模型的引用后,即可访问其变量和函数。

根据视图模型中配置的选项,您可能无权访问所有内部函数或设置器。

使用数组

数组通常无法在视图模型中访问。要访问视图模型中的数组,请创建自己的函数,以直接从视图模型本身添加、删除和获取数组中的成员。FieldNotify

您可以将数组与视图(列表视图、树视图、平铺视图)一起使用。您需要在数组中添加、删除或移动元素时发出通知。

创建视图模型的最佳实践

创建视图模型时,应使用小型、简洁的视图模型,而不是大型的整体视图模型。这种做法使调试 UI 变得更加容易。

例如,您可以创建一个表示 RPG 中角色的视图模型,其中包含完整的特征、物品栏和生命值数组。但是,要调试依赖于此视图模型的 UI 的任何部分,首先需要生成整个字符来填充视图模型的数据。如果将它们拆分为不同的组件,则可以在调试时更轻松地使用测试数据填充它们。

您可以将视图模型嵌套在其他视图模型中。这对于表示复杂的数据集非常有用。

查看绑定

创建视图模型后,可以将其添加到 UMG 编辑器中的小部件中,并使用“视图绑定”窗口将其定位。

向小组件添加视图绑定

向构件添加视图绑定有两种方法:可以使用“详细信息”面板中的属性绑定下拉列表添加它们,也可以使用“视图绑定”菜单管理控件的所有绑定。

使用详细信息面板

要在详细信息面板中使用视图绑定,请选择要向其添加绑定的小组件,然后单击要绑定的属性上的绑定下拉列表。对该属性有效的任何视图模型变量和函数都将显示在下拉列表的底部。单击一个以分配绑定。

使用视图绑定菜单

“视图绑定”窗口提供了对视图绑定行为方式的更详细控制。通过单击“UMG 设计器”选项卡中的“窗口>视图绑定”打开“视图绑定”窗口。

在“窗口”下拉列表中找到“视图绑定”菜单

单击“+ 添加小组件”以将条目添加到“视图绑定”列表。

在“视图模型”窗口中添加的任何视图模型都有资格进行绑定。

在当前版本的虚幻引擎中,如果将视图模型与“视图绑定”菜单绑定,然后使用“细节”面板重新分配,则绑定可能会失效。若要解决此问题,应从“视图绑定”菜单中删除绑定,然后重新分配它。

配置视图绑定

视图绑定包含以下信息:

  • 绑定的目标构件和目标视图模型**。

  • 要相互绑定的小组件属性和视图模型属性

  • 绑定的方向,它确定两个目标属性之间的信息流。

  • 绑定的更新类型

  • 启用/禁用切换,用于在禁用时从运行时中删除绑定。这意味着绑定将不会编译,并且在运行时不可用。

以下各节提供有关其中每个字段以及如何配置它们的详细信息。

选择目标小组件

视图绑定条目的第一个下拉列表选择要将视图绑定添加到的小组件。单击它时,下拉列表将显示小组件的层次结构,您可以选择父小组件本身或其任何子小组件。单击选择以确认您的选择。

创建视图绑定条目

在目标小部件下方,有要为其设置 Viewmodel 绑定的各个属性的条目。每个绑定都从其所属的小组件缩进。您可以通过单击小组件下拉列表旁边的 + 按钮为单个小组件添加多个绑定。每个绑定都必须面向不同的属性。

选择小组件属性

视图绑定条目中的第一个下拉列表显示目标小组件的变量和函数的列表。例如,如果使用选择进度条小组件,则 Percent 属性将可用。

要使C++中定义的属性或函数显示在此列表中,它必须对具有 or 宏的虚幻引擎反射系统可见。蓝图定义的变量和函数自动可用。UFUNCTIONUPROPERTY

选择视图模型属性

第三个下拉列表选择要面向的视图模型,以及要用于视图绑定的视图模型属性。单击它时,它会显示您添加到此小部件的视图模型列表。

单击要用于显示可用于视图绑定的变量和函数列表的视图模型。因为这是一个 ,要使变量和函数出现在这里,它们必须具有说明符。One Way To WidgetFieldNotify

设置绑定方向

第二个下拉列表选择视图绑定的绑定方向。这决定了信息在小部件和视图模型之间的流动方式。

绑定方向下拉列表

可用的绑定方向如下:

 

绑定方向

描述

一次性到小部件

绑定仅从视图模型应用于小组件一次。它将更新选定的小组件属性。

一种小部件方式

绑定仅从视图模型应用于小组件。每次更新视图模型中的相应变量时,它都会通知小部件变量已更改并更新选定的小部件属性。或者,如果选择了一个函数,则调用该函数将更新选定的小组件属性。

一种查看模型的方式

绑定仅适用于从小部件到视图模型。每当用户或您的代码更改小组件中的选定属性时,它都会将该更改应用于 Viewmodel 属性。典型示例包括用户编辑的文本字段或图形选项。

双向

绑定在两个方向上应用。

所有绑定在 PreConstruct 和 Construct(构造)事件之间执行一次。如果绑定方向为双向,则仅执行单向绑定。如果视图模型值发生更改,则使用 SetViewmodel,将执行包含该视图模型的所有绑定。

使用转换函数

作为直接绑定到变量的替代方法,可以选择“转换函数”。它们提供了一个接口,用于将变量从视图模型转换为不同类型的数据,例如将整数更改为文本。转换函数显示在视图模型列表下方的视图模型属性下拉列表中。

选择转换函数时,用于配置该函数参数的选项列表将显示在视图绑定的下拉列表下方。

如果单击其中一个属性的链接按钮,则可以将该属性绑定到任何视图模型值。

向转换函数添加值

新的转换函数可以全局添加,也可以在用户控件(控件蓝图)上添加。该函数不能是事件、网络、已弃用或仅编辑器。该函数需要对蓝图可见,具有一个输入参数和一个返回值。如果全局定义,则该函数也需要是静态的。如果在 UserWidget 上定义,则该函数还需要是纯函数和常量函数。