这个东西真的难用,各种问题,记录下
官方文档
bilibili教学
开启插件
插件开启 Viewmodel:
build.cs内PublicDependencyModuleNames加上ModelViewViewModel
创建ViewModel类
#pragma once
#include "CoreMinimal.h"
#include "MVVMViewModelBase.h"
#include "AttributeViewModel.generated.h"
UCLASS(BlueprintType)
class PROJECT2D_API UAttributeViewModel : public UMVVMViewModelBase
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, FieldNotify, Setter, Getter)
float Health;
UPROPERTY(BlueprintReadWrite, FieldNotify, Setter, Getter)
float MaxHealth;
public:
float GetHealth() const;
void SetHealth(float NewHealth);
float GetMaxHealth() const;
void SetMaxHealth(float NewMaxHealth);
UFUNCTION(BlueprintCallable, FieldNotify)
float GetHealthPercent() const;
};
#include "UI/AttributeViewModel.h"
float UAttributeViewModel::GetHealth() const
{
return Health;
}
void UAttributeViewModel::SetHealth(float NewHealth)
{
if (UE_MVVM_SET_PROPERTY_VALUE(Health, NewHealth))
{
UE_MVVM_BROADCAST_FIELD_VALUE_CHANGED(GetHealthPercent);
}
}
float UAttributeViewModel::GetMaxHealth() const
{
return MaxHealth;
}
void UAttributeViewModel::SetMaxHealth(float NewMaxHealth)
{
if (UE_MVVM_SET_PROPERTY_VALUE(MaxHealth, NewMaxHealth))
{
UE_MVVM_BROADCAST_FIELD_VALUE_CHANGED(GetHealthPercent);
}
}
float UAttributeViewModel::GetHealthPercent() const
{
return Health / FMath::Max(1, MaxHealth);
}
用角色类管理 ViewModel 实例:(这样子各个UI都可以用这个数据)
UPROPERTY()
class UAttributeViewModel* AttributeViewModel;
UFUNCTION(BlueprintCallable)
class UAttributeViewModel* GetAttributeViewModel();
class UAttributeViewModel* AXXXCharacter::GetAttributeViewModel()
{
if (!AttributeViewModel)
{
AttributeViewModel = NewObject<UAttributeViewModel>(this);
}
return AttributeViewModel;
}
AttributeSet内部存一份ViewModel(记得多端):
void UXXXAttributeSet::Reset()
{
if (UAbilitySystemComponent* ASC = GetOwningAbilitySystemComponent())
{
if (AXXXCharacter* Char = Cast<AXXXCharacter>(ASC->GetOwnerActor()))
{
ViewModel = Char->GetAttributeViewModel();
}
}
}
在属性集的PostAttributeChange和属性的OnRep函数调用ViewModel的Set函数:
#define XXX_GAMEPLAYATTRIBUTE_REPNOTIFY(ClassName, PropertyName, OldValue) \
static FProperty* ThisProperty = FindFieldChecked<FProperty>(ClassName::StaticClass(), GET_MEMBER_NAME_CHECKED(ClassName, PropertyName)); \
GetOwningAbilitySystemComponentChecked()->SetBaseAttributeValueFromReplication(FGameplayAttribute(ThisProperty), PropertyName, OldValue); \
SyncInfoFromAttribute(Get##PropertyName##Attribute(), Get##PropertyName());
#define SYNC_VIEW_MODEL_BEGIN(PropertyName) \
if (Attribute == Get##PropertyName##Attribute()) \
{ \
ViewModel->Set##PropertyName(Value); \
}
#define SYNC_VIEW_MODEL(PropertyName) \
else if (Attribute == Get##PropertyName##Attribute()) \
{ \
ViewModel->Set##PropertyName(Value); \
}
void UXXXAttributeSet::PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue)
{
...
SyncInfoFromAttribute(Attribute, NewValue);
}
void UXXXAttributeSet::OnRep_Health(const FGameplayAttributeData& OldHealth)
{
XXX_GAMEPLAYATTRIBUTE_REPNOTIFY(UXXXAttributeSet, Health, OldHealth);
}
void UXXXAttributeSetPrimary::SyncInfoFromAttribute(const FGameplayAttribute& Attribute, float Value)
{
if (ViewModel)
{
SYNC_VIEW_MODEL_BEGIN(Health)
SYNC_VIEW_MODEL(MaxHealth)
}
}
这样子在属性修改后,各个端都会调用到ViewModel的Set函数
Widget使用
UserWidget来到Designer界面:
为当前的Widget创建一个ViewModel:
进行数据绑定:
然后就是根据UProperty进行绑定,这里可以直接绑定到ViewModel的某个带有FieldNotify关键字的UFunction:
需要在ViewModel的Set函数内部,主动触发回调:
UE_MVVM_BROADCAST_FIELD_VALUE_CHANGED(GetHealthPercent);
Widget寻找ViewModel
Widget的ViewModel有4种方式:
UENUM()
enum class EMVVMBlueprintViewModelContextCreationType : uint8
{
Manual, // The viewmodel will be assigned later.
CreateInstance, // A new instance of the viewmodel will be created when the widget is created.
GlobalViewModelCollection, // The viewmodel exists and is added to the MVVMSubsystem. It will be fetched there.
PropertyPath, // The viewmodel will be fetched by evaluating a function or a property path.
};
PropertyPath
以Self作为开始点,调用各种UFunction和UProperty链,获取到目标ViewModel,但是在初始化的时候就需要有。
本人用的就是这个。
蓝图定义一个获取ViewModel的函数:
然后修改ViewModel的View Model Property Path为刚才的函数:
Manual
人为设置变量:
CreateInstance
每个Widget都会创建一个,然后别的地方用这个变量去操作
GlobalViewModelCollection
在某个地方创建全局的ViewModel,但是有个麻烦的地方是以字符串作为Key去检索,所以重复性的数据没法弄(例如100个怪的生命值)