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

190 lines
7.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: UE5 MVVM 笔记
date: 2025-08-26 10:05:44
excerpt:
tags:
rating: ⭐
---
# 前言
- 知乎文章
- [UE5.6新功能MVVM使用方式从笔记里迁移](https://zhuanlan.zhihu.com/p/1918763422243325641)
- B站视频
- [第43期 | 虚幻引擎新UI——MVVM | quabqi](https://www.bilibili.com/video/BV1Dj411N735/?spm_id_from=333.1387.search.video_card.click&vd_source=d47c0bb42f9c72fd7d74562185cee290)
![[UMG_MVVM.png]]
# Example
```c++
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
```c++
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内存连续可以通过索引快速查询。
数据结构:
```text
{
// 类型
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|800]]绑定方向
- 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|800]]
如何自定义转化函数:
如果我们是自己的某个接口,或者一个特殊的类型要有额外操作怎么处理,这个时候就需要自定义转化函数。
新的转换函数可以全局添加或在UserWidget控件蓝图上添加。函数不能是事件或网络也不能弃用或仅限编辑器。函数需要对蓝图可见有一个输入参数和一个返回值。如果在全局定义函数还需要带有static关键字。如果在UserWidget中定义函数还需要带有pure和const关键字。
一个在蓝图中转化的示例:
![[UMG_MVVM_ViewBindings_ConversionFunction_Custom.jpg|800]]![[UMG_MVVM_ViewBindings_ConversionFunction_CustomFunction.jpg|800]]
![[UMG_MVVM_ViewBindings_ConversionFunction_CustomFunction_Settings.jpg|800]]**Note:当前版本如果在Bindings赋值然后再Deatails再次赋值这样会出问题绑定会失效解决办法从Bindings中删掉原来的绑定重新赋值,(基于此原因个人建议关掉DeatailsBind的入口)**
### 禁用旧版绑定
旧版的Bind并不是监听事件而是通过Tick刷新。
新版确实是通过蓝图可用的多播委托去通知。
为了防止两种方式混用(**我们也不允许使用旧版方式绑定,性能洼地**),需要禁用旧版绑定。
#### DiablePropertBinding
![[UMG_DisablePropertBinding.jpg|800]]
开启上选项后将禁用传统属性绑定到Widget的选项可以看到如图效果BindList中只有ViewModel选项。
![[UMG_DisablePropertBinding_Result.jpg]]
#### ViewModel禁用DetailsBind
![[UMG_MVVM_DisableDetailsBinding.jpg|800]]
![[UMG_MVVM_DisableDetailsBinding_Result.jpg]]
# 在角色类中使用方式
![[UMG_MVVM_UseInCharacter.png]]
之后给ViewModel写入数据就可以了。