Unreal View Model结合GAS使用
这个东西真的难用,各种问题,记录下
官方文档
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个怪的生命值)