Files
BlueRoseNote/03-UnrealEngine/UI/UE5 MVVM 笔记.md

7.3 KiB
Raw Blame History

title, date, excerpt, tags, rating
title date excerpt tags rating
UE5 MVVM 笔记 2025-08-26 10:05:44

前言

!UMG_MVVM.png

Example

UCLASS(Blueprintable)
class UMyViewModelBase : public UMVVMViewModelBase
{
	GENERATED_BODY()

private:
	// FieldNotify使得属性可以用于通知广播
	// Setter:此属性可以被设置Setter函数的名称格式 Set[Variable Name],CurrentHealth的Setter为SetCurrentHealth
	// Setter = [Function Name] 也可以自己指定名称,写法如前
	// Getter:此属性可以被获取Getter函数的名称格式为 Get[Variable Name],CurrentHealth的Getter为GetCurrentHealth
	// Getter = [Function Name] 也可以自己指定名称,写法如前
	// 此字段在ViewModel中使用Get/Set访问在蓝图中是Public的在蓝图中ViewBanding使用Get/Set
	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, NewCurrentHealth))
		{
			UE_MVVM_BROADCAST_FIELD_VALUE_CHANGED(GetHealthPercent);
		}
	}
 
	void SetMaxHealth(int32 NewMaxHealth)
	{
		// 内部是一个模版函数有特化版本最终作用是做compare并触发Boradcast
		// 原理也一样就是通过名字传递Property做compare
		// 然后通过名字拿到Fiedld取Delegate进行boradcast
		// ([Variable Name][NewValue])
		if (UE_MVVM_SET_PROPERTY_VALUE(MaxHealth, NewMaxHealth))
		{
			// 如果MaxHealth改变了HealthPercent也需要更新
			// 内部通过名称拿到一个FieldId然后通过FieldId找到对应的Delegate进行Boradcast
			UE_MVVM_BROADCAST_FIELD_VALUE_CHANGED(GetHealthPercent);
		}
 
	}
 
	int32 GetCurrentHealth() const
	{
		return CurrentHealth;
	}
 
	int32 GetMaxHealth() const
	{
		return MaxHealth;
	}
 
public:
	/**
	 * 必须具有带 FieldNotify 和 BlueprintPure 说明符的 UFUNCTION 宏。
	 * 不得带有参数。
	 * 必须是 const 函数。
	 * 必须仅返回单个值(没有输出参数)。
	 * @return 
	 */
	UFUNCTION(BlueprintPure, FieldNotify)
	float GetHealthPercent() const
	{
		if (MaxHealth != 0)
		{
			return (float) CurrentHealth / (float) MaxHealth;
		}
		else
			return 0;
	}
};

MVVM

ViewModels管理器位于Window -> ViewModels。

大致操作步骤:

  1. 在该界面中创建ViewModels。
  2. 选择ViewModels并且添加动态绑定的变量。
  3. 修改CreationType有4种类型

MVVM CreationType

Manual看设计可以一对多

  • 默认不创建需要业务自己调用SetViewModel来关联
  • 可以手动把同一个ViewModel绑定到个Widget中

默认是Optional其他方式必须在Widget启动前设置好AddViewModelInstance()

Create Instance (一对一)

  • 在界面Construct时候会创建一个新的ViewModel
  • 每个界面实例都是单独的ViewModel

Global View Model Collection (全局共享)

  • GlobalViewModelCollection自动从全局的MVVMSubsystem中获取指定Name和类型的ViewModel
  • 业务必须手动创建一个,并添加到MVVMSubsyste
const auto Collection = GEngine->GetEngineSubsystem<UMVVMSubsystem>()->GetGlobalViewModelCollection()
FMVVMViewModelContext Context;
Context.ContextClass = ViewModelClass;
Context.ContextName = "Test";

// 把VM添加到UMVVMSubsystem
const auto VM = NewObject<UMVVMViewModelBase>(LocalPlayer, ViewModelClass, "Test");
Collection->AddViewModelInstance(Context, VM);

//查询VM
const auto VMResult = Collection->FindViewModelInstance(Context);

// 从UMVVMSubsystem移除VM
Collection->RemoveViewModel(Context);

Note这里注意AddViewModel必须提供ContextNameUE要求此名称必须和ViewMode的类名一致。 原因MVVM中维护数据使用的是TArray内存连续可以通过索引快速查询。

数据结构:

{
	// 类型
	TSubclassOf<UMVVMViewModelBase> ContextClass;
	// 上下文信息UE要求和类名一致
	FName ContextName;
}

Property Path

类似如下写法加粗的self是默认的不需要手动写和函数默认的This类似 **Self.**GetPlayerController.Vehicle.ViewModel

这个指定方式是说通过当前的Widget获取PC通过PC拿到载具上的ViewModel信息。

使用方法

  1. 从MVVM界面中直接退拽进UMG编辑器。
  2. Detail面板Binding。
  3. 使用ViweBindingWindow→ViewBindings

!UMG_MVVM_ViewBindings.jpg绑定方向

  • OneTimeToWidget←1VM到控件执行一次。
  • OneWayToWidget: VM到V每次刷新都会通知
  • OneWayToViewModel :V到VM, 每次通知到VM,典型案例:编辑文本或图形选项,按钮需要自己包裹一下(研究下给个合理方式
  • TwoWay:绑定在两个方向都可用可以相互通知不用担心陷入死循环MVVMModelBase中的SetFunc已经做好了处理只有值不等才会Boradcast

Conversion Function

何时使用?
如图我想要在Text文本中显示我最大血量最大血量是integer,如果直接绑定compile时会有报错无法编译此时就需要有转换函数出现了通过转化函数将Integer转化为可用的FText !UMG_MVVM_ViewBindings_ConversionFunction.jpg

如何自定义转化函数:
如果我们是自己的某个接口,或者一个特殊的类型要有额外操作怎么处理,这个时候就需要自定义转化函数。
新的转换函数可以全局添加或在UserWidget控件蓝图上添加。函数不能是事件或网络也不能弃用或仅限编辑器。函数需要对蓝图可见有一个输入参数和一个返回值。如果在全局定义函数还需要带有static关键字。如果在UserWidget中定义函数还需要带有pure和const关键字。
一个在蓝图中转化的示例: !UMG_MVVM_ViewBindings_ConversionFunction_Custom.jpg!UMG_MVVM_ViewBindings_ConversionFunction_CustomFunction.jpg !UMG_MVVM_ViewBindings_ConversionFunction_CustomFunction_Settings.jpgNote:当前版本如果在Bindings赋值然后再Deatails再次赋值这样会出问题绑定会失效解决办法从Bindings中删掉原来的绑定重新赋值,(基于此原因个人建议关掉DeatailsBind的入口)

禁用旧版绑定

旧版的Bind并不是监听事件而是通过Tick刷新。 新版确实是通过蓝图可用的多播委托去通知。 为了防止两种方式混用(我们也不允许使用旧版方式绑定,性能洼地),需要禁用旧版绑定。

DiablePropertBinding

!UMG_DisablePropertBinding.jpg 开启上选项后将禁用传统属性绑定到Widget的选项可以看到如图效果BindList中只有ViewModel选项。 !UMG_DisablePropertBinding_Result.jpg

ViewModel禁用DetailsBind

!UMG_MVVM_DisableDetailsBinding.jpg

!UMG_MVVM_DisableDetailsBinding_Result.jpg

在角色类中使用方式

!UMG_MVVM_UseInCharacter.png 之后给ViewModel写入数据就可以了。