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

169 lines
7.0 KiB
Markdown
Raw Normal View History

2025-08-26 10:39:13 +08:00
---
title: UE5 MVVM 笔记
date: 2025-08-26 10:05:44
excerpt:
tags:
rating: ⭐
---
# 前言
- 知乎文章
- [UE5.6新功能MVVM使用方式从笔记里迁移](https://zhuanlan.zhihu.com/p/1918763422243325641)
2025-08-26 12:30:00 +08:00
- B站视频
- [第43期 | 虚幻引擎新UI——MVVM | quabqi](https://www.bilibili.com/video/BV1Dj411N735/?spm_id_from=333.1387.search.video_card.click&vd_source=d47c0bb42f9c72fd7d74562185cee290)
2025-08-26 10:39:13 +08:00
# 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;
}
};
2025-08-26 11:56:17 +08:00
```
# MVVM
ViewModels管理器位于Window -> ViewModels。
大致操作步骤:
1. 在该界面中创建ViewModels。
2. 选择ViewModels并且添加动态绑定的变量。
3. 修改CreationType有4种类型
## MVVM CreationType
### Create Instance (一对一)
自动为控件的每个唯一实例创建一个新的Viewmodel实例。这意味着如果你在视口中有同一控件的数个副本并且你更改了其中一个副本的Viewmodel变量则只有该控件会更新所有其他副本将保持不变。同理如果你创建多个使用同一Viewmodel的不同控件这些控件都不会感知到彼此信息的变化。
**UE只会在ViewModel为空时创建新实例ViewModel会在PreConstruct和Construct事件之间创建。**
### Manual看设计可以一对多
自行创建ViewModel实例进行手动指定赋值在赋值之前Widget中的ViewModel一直都是空的。
### Global View Model Collection (全局共享)
[MVVMGameSubsystem](https://zhida.zhihu.com/search?content_id=259254754&content_type=Article&match_order=1&q=MVVMGameSubsystem&zhida_source=entity)中维护了一个全局访问的ViewModel列表可以通过任意GameInstance访问。
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]]