在上一篇文章里,制作了显示血量和蓝量的ui,并且还将ui和获取数据使用的控制器层创建出来并初始化成功。现在只有主用户控件上面被添加了控制器层,还未给每个用户控件赋予控制器层。接下来要实现对属性的广播功能,在属性值变化的时候,能够在蓝图中获取到数值的变化并更新到用户控件上面。
使用委托设置初始值
实现逻辑是设置委托变量,在Widget Controller里面初始化时向外面广播一次当前的数值,相应的监听此委托的widget触发回调进行初始化数值显示。
首先在控制器层里面添加一个函数
virtual void BroadcastInitialValues();
这个函数用于广播初始化函数。我们上一文章里基于控制器层类创建了一个子类,专门用于Overlay的,在里面复写这个函数
public:
virtual void BroadcastInitialValues() override;
接下来就是重点,我们将以委托的形式设置每个属性的广播功能,这里就需要用到委托对应的函数。不清楚的小伙伴可以看下这里
一文理解透UE委托Delegate
这里使用了动态多播委托函数绑定,下面是实现,委托的函数DelegateName 首字母必须以F开头。
一下是定义了获取血量的委托,返回一个参数就是当前的血量浮点数。
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthChangedSignature,float, NewHealth);
接下来定义一个变量,实现对回调的引用
UPROPERTY(BlueprintAssignable, Category="GAS|Attributes")
FOnHealthChangedSignature FOnHealthChanged;
然后最后通过广播形式将数据广播出去
FOnHealthChanged.Broadcast(50.f);
当前,我们需要实际的血量数值,所以广播出去的就是从AttributeSet里面获取到的值
const UAttributeSetBase* AttributeSetBase = CastChecked<UAttributeSetBase>(AttributeSet);
OnHealthChanged.Broadcast(AttributeSetBase->GetHealth());
OnMaxHealthChanged.Broadcast(AttributeSetBase->GetMaxHealth());
函数有了,需要有个地方调用,实例化这个函数我们是在HUD类里面实例化的,那就在实例化完成后面调用。也就是MyHUD.cpp里面的InitOverlay()里面,这个顺序不能错,首先需要设置控制器层,这时会触发蓝图里面可以调用的设置控制器层回调通知,用户控件会以此来绑定广播回调。然后再广播初始的值,用户控件里就实现了对值的初始化设置。
OverlayWidgetController = GetOverlayWidgetController(WidgetControllerParams); //获取控制器层
OverlayWidget->SetWidgetController(OverlayWidgetController); //设置用户控件的控制器层
OverlayWidgetController->BroadcastInitialValues(); //初始化广播
这样设置完成以后,打开UE,在设置的WBP_Overlay里面,在上一章最后设置的设置完成控制器层对象后的回调中,将控制器层对象传递给需要显示的用户控件节点。
做到这一步,我们在需要同步数据的UI上面就都可以获取到控制器层对象。接着,我们要将Overlay的控制器层类公开给蓝图。
UCLASS(BlueprintType, Blueprintable)
class AURA_API UOverlayWidgetController : public UMyWidgetController
直接在类上面的UCLASS()内添加上两个参数。
BlueprintType
将此类公开为可用于蓝图中的变量的类型。
Blueprintable
将此类公开为用于创建蓝图的可接受基类。默认为NotBlueprintable
,除非继承时就并非如此。此说明符由子类继承。
设置完成这两项,我们就可以把控制器层对象转换为控制器层类的实例了。
为了方便后面的工作,推荐不要直接使用c++的类,通过蓝图创建一个c++的基类去使用。
将HUD里面的控制器层类的引用修改成蓝图的。
在进度条的蓝图基类WBP_GlobeProgressBar中添加一个函数,用于设置进度条百分比,接收一个参数
之前在OverlayWidget里面,将在c++里面生成的控制器层对象传递给了对应的用户组件。我们在血量控件中,也可以使用WidgetControllerSet通知,获得通知后将变量保存。
之前我们在OverlayWidgetController里面添加了对血量和最大血量的广播,所以,在蓝图里,我们可以实现绑定血量变化。在数值变化后,可以通过调用设置进度条百分比进行进度条设置。
顺便也绑定最大血量。
监听数值的变化
上面我们实现通过广播的形式对用户控件内的值进行初始化,还未实现对数值变化后触发广播更新ui显示。
我们需要通过使用AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate()来注册一个监听一个值的变化,如果监听的数值变化了接着将变化后的数值进行广播。比如下面就是监听血量属性是否变化
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AttributeSetBase->GetHealthAttribute())
为了区分开,我们需要在控制器层类里面新加一个函数,专门用来注册监听数据变化回调的。
virtual void BindCallbacksToDependencies() override;
接着创建两个保护类型的函数,用于监听到数值变化后的回调函数
protected:
void HealthChanged(const FOnAttributeChangeData& Data) const;
void MaxHealthChanged(const FOnAttributeChangeData& Data) const;
接着,在绑定监听函数内实现监听:
void UOverlayWidgetController::BindCallbacksToDependencies()
{
const UAttributeSetBase* AttributeSetBase = CastChecked<UAttributeSetBase>(AttributeSet);
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(
AttributeSetBase->GetHealthAttribute()).AddUObject(this, &UOverlayWidgetController::HealthChanged);
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(
AttributeSetBase->GetMaxHealthAttribute()).AddUObject(this, &UOverlayWidgetController::MaxHealthChanged);
}
在数值变化时,就会触发去调用Changed函数,我们只需要在changed函数内,将数值广播给蓝图即可。
void UOverlayWidgetController::HealthChanged(const FOnAttributeChangeData& Data) const
{
OnHealthChanged.Broadcast(Data.NewValue);
}
void UOverlayWidgetController::MaxHealthChanged(const FOnAttributeChangeData& Data) const
{
OnMaxHealthChanged.Broadcast(Data.NewValue);
}
最后,需要找一个地方调用监听函数绑定,我选在了HUD对初始化数值调用之后
OverlayWidget->SetWidgetController(OverlayWidgetController); //设置用户控件的控制器层
OverlayWidgetController->BroadcastInitialValues(); //初始化广播的值
OverlayWidgetController->BindCallbacksToDependencies(); //绑定监听数值变化
接下来,按照之前的方式将蓝的设置添加上,这里不在编写实现,因为和血量的方式一行按照上面一步步来即可。下面列一下代码。
控制器层代码
MyWidgetController.h
// 版权归暮志未晚所有。
#pragma once
#include "CoreMinimal.h"
#include "AbilitySystemComponent.h"
#include "UObject/NoExportTypes.h"
#include "MyWidgetController.generated.h"
class UAttributeSet;
class UAbilitySystemComponent;
/**
* 生成用户控件控制器的结构体,需要四项内容去生成。
*/
USTRUCT(BlueprintType)
struct FWidgetControllerParams
{
GENERATED_BODY()
FWidgetControllerParams(){}
FWidgetControllerParams(APlayerController* PC, APlayerState* PS, UAbilitySystemComponent* ASC, UAttributeSet* AS)
: PlayerController(PC),
PlayerState(PS),
AbilitySystemComponent(ASC),
AttributeSet(AS)
{}
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TObjectPtr<APlayerController> PlayerController = nullptr;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TObjectPtr<APlayerState> PlayerState = nullptr;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TObjectPtr<UAbilitySystemComponent> AbilitySystemComponent = nullptr;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TObjectPtr<UAttributeSet> AttributeSet = nullptr;
};
/**
* 用户控件控制器层,用户控件可从控制器层更新数据显示,以及为控制器层提供输入。
*/
UCLASS()
class AURA_API UMyWidgetController : public UObject
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable)
void SetWidgetControllerParams(const FWidgetControllerParams& WCParams);
virtual void BroadcastInitialValues(); //广播初始化的值
virtual void BindCallbacksToDependencies(); //绑定数值变动后回调的广播
protected:
UPROPERTY(BlueprintReadOnly, Category="WidgetController")
TObjectPtr<APlayerController> PlayerController;
UPROPERTY(BlueprintReadOnly, Category="WidgetController")
TObjectPtr<APlayerState> PlayerState;
UPROPERTY(BlueprintReadOnly, Category="WidgetController")
TObjectPtr<UAbilitySystemComponent> AbilitySystemComponent;
UPROPERTY(BlueprintReadOnly, Category="WidgetController")
TObjectPtr<UAttributeSet> AttributeSet;
};
MyWidgetController.cpp
// 版权归暮志未晚所有。
#include "UI/WidgetController/MyWidgetController.h"
void UMyWidgetController::SetWidgetControllerParams(const FWidgetControllerParams& WCParams)
{
PlayerController = WCParams.PlayerController;
PlayerState = WCParams.PlayerState;
AbilitySystemComponent = WCParams.AbilitySystemComponent;
AttributeSet = WCParams.AttributeSet;
}
void UMyWidgetController::BroadcastInitialValues()
{
}
void UMyWidgetController::BindCallbacksToDependencies()
{
}
OverlayWidgetController.h
// 版权归暮志未晚所有。
#pragma once
#include "CoreMinimal.h"
#include "UI/WidgetController/MyWidgetController.h"
#include "OverlayWidgetController.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthChangedSignature,float, NewHealth);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMaxHealthChangedSignature,float, NewMaxHealth);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnManaChangedSignature,float, NewMana);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMaxManaChangedSignature,float, NewMaxMana);
/**
* 屏幕覆盖用户控件控制器层基类,继承与用户控件控制器
*/
UCLASS(BlueprintType, Blueprintable)
class AURA_API UOverlayWidgetController : public UMyWidgetController
{
GENERATED_BODY()
public:
virtual void BroadcastInitialValues() override;
virtual void BindCallbacksToDependencies() override;
UPROPERTY(BlueprintAssignable, Category="GAS|Attributes")
FOnHealthChangedSignature OnHealthChanged;
UPROPERTY(BlueprintAssignable, Category="GAS|Attributes")
FOnMaxHealthChangedSignature OnMaxHealthChanged;
UPROPERTY(BlueprintAssignable, Category="GAS|Attributes")
FOnManaChangedSignature OnManaChanged;
UPROPERTY(BlueprintAssignable, Category="GAS|Attributes")
FOnMaxManaChangedSignature OnMaxManaChanged;
protected:
void HealthChanged(const FOnAttributeChangeData& Data) const;
void MaxHealthChanged(const FOnAttributeChangeData& Data) const;
void ManaChanged(const FOnAttributeChangeData& Data) const;
void MaxManaChanged(const FOnAttributeChangeData& Data) const;
};
OverlayWidgetController.cpp
// 版权归暮志未晚所有。
#include "UI/WidgetController/OverlayWidgetController.h"
#include "AbilitySystem/AttributeSetBase.h"
void UOverlayWidgetController::BroadcastInitialValues()
{
const UAttributeSetBase* AttributeSetBase = CastChecked<UAttributeSetBase>(AttributeSet);
OnHealthChanged.Broadcast(AttributeSetBase->GetHealth());
OnMaxHealthChanged.Broadcast(AttributeSetBase->GetMaxHealth());
OnManaChanged.Broadcast(AttributeSetBase->GetMana());
OnMaxManaChanged.Broadcast(AttributeSetBase->GetMaxMana());
}
void UOverlayWidgetController::BindCallbacksToDependencies()
{
const UAttributeSetBase* AttributeSetBase = CastChecked<UAttributeSetBase>(AttributeSet);
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(
AttributeSetBase->GetHealthAttribute()).AddUObject(this, &UOverlayWidgetController::HealthChanged);
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(
AttributeSetBase->GetMaxHealthAttribute()).AddUObject(this, &UOverlayWidgetController::MaxHealthChanged);
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(
AttributeSetBase->GetManaAttribute()).AddUObject(this, &UOverlayWidgetController::ManaChanged);
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(
AttributeSetBase->GetMaxManaAttribute()).AddUObject(this, &UOverlayWidgetController::MaxManaChanged);
}
void UOverlayWidgetController::HealthChanged(const FOnAttributeChangeData& Data) const
{
OnHealthChanged.Broadcast(Data.NewValue);
}
void UOverlayWidgetController::MaxHealthChanged(const FOnAttributeChangeData& Data) const
{
OnMaxHealthChanged.Broadcast(Data.NewValue);
}
void UOverlayWidgetController::ManaChanged(const FOnAttributeChangeData& Data) const
{
OnManaChanged.Broadcast(Data.NewValue);
}
void UOverlayWidgetController::MaxManaChanged(const FOnAttributeChangeData& Data) const
{
OnMaxManaChanged.Broadcast(Data.NewValue);
}
多人模式调试UI显示
在运行下拉中,将人数修改为2
然后将客户端同时作为服务器使用
接着会发现运行了两个实例,并且拥有相同的血量和蓝量
接下来,控制一个角色去吃掉药瓶,会发现她的血和蓝都产生了变化,而另外一个没有变化
到这里功能实现了。