一、MVC简单介绍
说到UI管理,最容易实现也是最简单的管理方式,就是使用MVC设计框架。
View层——也就是UE中的WidgetUI类,主要负责视图变化。
Model层——数据层,在UE的RPC游戏中是负责持有人物角色属性的类,人物角色属性一般是放在PlayerController类、PlayerState类、AttributeSet类中,数据变化以及计算是在Model层中进行。
WidgetController类——负责联系View层与Model层,是这两层的连接桥梁,Controller类持有Model层的对象指针(PlayerController、PlayerState、AttributeSet),Model层数据变化发出通知(通过绑定Model层中的委托Delegate),Controller层接收到数据变化的通知后,进而通知View层数据变化(WidgetController类中定义多播委托,然后在UI中绑定对于的事件,UI就能够对数据变动做出反应)。
通过MVC框架,Model层与View层之间并不直接关联相互持有,保证了解耦性。
二、View层--WidegtUI创建
1、UserWidegt父类创建
View层也就是UI类中,需要接受到来自Controller类中的数据变化的通知,所以UI类中需要有一个Controller类的指针,同时需要一个设置Controller类指针的设置函数,一个设置Controller类时的通知事件,用来在controller设置时,同步绑定数据变化时View对应变化的事件。
因为项目中所有的UI都使用了这种管理方式,所有为所有UI类创建一个共同的UI父类UAuraUserWidget类。
UAuraUserWidget.h代码:
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "AuraUserWidget.generated.h"
/**
*
*/
UCLASS()
class GAS_RPG_STUDY_API UAuraUserWidget : public UUserWidget
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable)
void SetWidgetController(UObject* InWidgetController);
UPROPERTY(BlueprintReadOnly)
TObjectPtr<UObject> WidgetController;
protected:
UFUNCTION(BlueprintImplementableEvent)
void WidgetControllerSetEvent();
};
UAuraUserWidget.cpp代码如下:
#include "UI/Widget/AuraUserWidget.h"
void UAuraUserWidget::SetWidgetController(UObject* InWidgetController)
{
WidgetController = InWidgetController;
WidgetControllerSetEvent();
}
WidgetControllerSetEvent函数在蓝图中重载,此函数主要做的事,就是在UI设置Controller类时,同步绑定Controller类中的通知事件,使得该UI能够对数据变化做出反应。
2、创建血条蓝条UI
血条蓝条UI的功能一样,仅仅是进度条的颜色不同,所有我们可以创建一个血条蓝条共同的父类WBP_GlobeProgressBar类,具体血条蓝条的子类只需要设置参数即可。同时WBP_GlobeProgressBar类继承自UAuraUserWidget,所有也有各自的Controller类负责控制。
我们只需要将UI中图像的尺寸、Texture等变量暴露出来,在子类设置即可。
3、创建玩家屏幕显示的Overlay类
当前玩家只有血量与蓝量数据,所以Overlay页面中也暂时只有WBP_HealthGlobe和WBP_ManaGlobe两个小部件,用于显示血量与蓝量。
现在View层的类创建完成,可以看到View层就是UI类,只负责显示。
三、创建WidgetController类
1、创建WidgetController父类
Controller类中需要接受Model层的数据变化通知,所以需要储存Model层的指针,也就是PlayerController、PlayerState、AttributeSet类的指针,AttributeSet数据变化通知是在AbilitySystemComponent中,所以也需要AbilitySystemComponent的指针。AS中数值变化时,ASC会调用一个数值变化的委托,在Controller类中绑定一个通知View层的回调函数到此数值变化的委托中,则可以完成数值变化通知到View层的功能。
Controller类同样需要将数据的变化通知给View层,所以需要在各个子类的Controller中,定义多播委托。
UI初始化时,需要同步初始化时的数据状态,所以需要一个BroadcastInitialValues函数,此函数在UI初始化时调用,目的是拿到初始化的数据。
UAuraWidgetController.h代码如下:
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "AuraWidgetController.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 GAS_RPG_STUDY_API UAuraWidgetController : public UObject
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable)
void SetWidgetControllerParams(const FWidgetControllerParams& WCParams);
virtual void BroadcastInitialValues();
virtual void BindCallBackToDependencies();
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;
};
为了方便初始化PlayerController、PlayerState、AttributeSet、AbilitySystemComponent几个变量,所以定义一个结构体FWidgetControllerParams。
UAuraWidgetController.cpp代码如下:
#include "UI/WidgetController/AuraWidgetController.h"
void UAuraWidgetController::SetWidgetControllerParams(const FWidgetControllerParams& WCParams)
{
PlayerController = WCParams.PlayerController;
PlayerState = WCParams.PlayerState;
AbilitySystemComponent = WCParams.AbilitySystemComponent;
AttributeSet = WCParams.AttributeSet;
}
void UAuraWidgetController::BroadcastInitialValues()
{
}
void UAuraWidgetController::BindCallBackToDependencies()
{
}
通知UI的多播委托不同的UI需要的参数不同,所以在WidgetController的子类中定义。
2、创建OverlayWidgetController类
OverlayWidget中有血条与蓝条,主要的数据就是Health、MaxHealth、Mana、MaxMana,对应的需要定义这四个属性的变化委托。
UOverlayWidgetController.h代码如下
#pragma once
#include "CoreMinimal.h"
#include "AbilitySystem/AuraAttributeSet.h"
#include "UI/WidgetController/AuraWidgetController.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 GAS_RPG_STUDY_API UOverlayWidgetController : public UAuraWidgetController
{
GENERATED_BODY()
public:
//初始化时,调用回调函数设置初始时的UI中的数值
virtual void BroadcastInitialValues() override;
//ASC中自带了数值变化时的回调函数,将WidgetController中的函数绑定到ASC中的回调函数中
virtual void BindCallBackToDependencies() 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:
//AS中,Health值变化时的绑定的回调函数
void HealthChanged(const FOnAttributeChangeData& Data) const;
//AS中,MaxHealth值变化时的绑定的回调函数
void MaxHealthChanged(const FOnAttributeChangeData& Data) const;
//AS中,Mana值变化时的绑定的回调函数
void ManaChanged(const FOnAttributeChangeData& Data) const;
//AS中,MaxMana值变化时的绑定的回调函数
void MaxManaChanged(const FOnAttributeChangeData& Data) const;
};
可以看到,定义了四个委托类型FOnHealthChangedSignature、FOnMaxHealthChangedSignature、FOnManaChangedSignature、FOnMaxManaChangedSignature,这四个委托在UI设置Controller类的WidgetControllerSetEvent事件中绑定。
UOverlayWidgetController.cpp代码:
#include "UI/WidgetController/OverlayWidgetController.h"
#include "AbilitySystem/AuraAttributeSet.h"
void UOverlayWidgetController::BroadcastInitialValues()
{
Super::BroadcastInitialValues();
const UAuraAttributeSet* AuraAttributeSet = CastChecked<UAuraAttributeSet>(AttributeSet);
OnHealthChanged.Broadcast(AuraAttributeSet->GetHealth());
OnMaxHealthChanged.Broadcast(AuraAttributeSet->GetMaxHealth());
OnManaChanged.Broadcast(AuraAttributeSet->GetMana());
OnMaxManaChanged.Broadcast(AuraAttributeSet->GetMaxMana());
}
void UOverlayWidgetController::BindCallBackToDependencies()
{
Super::BindCallBackToDependencies();
const UAuraAttributeSet* AuraAttributeSet = CastChecked<UAuraAttributeSet>(AttributeSet);
//绑定Health变化回调函数
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AuraAttributeSet->GetHealthAttribute())
.AddUObject(this,&UOverlayWidgetController::HealthChanged);
//绑定MaxHealth变化回调函数
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AuraAttributeSet->GetMaxHealthAttribute())
.AddUObject(this,&UOverlayWidgetController::MaxHealthChanged);
//绑定Mana变化回调函数
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AuraAttributeSet->GetManaAttribute())
.AddUObject(this,&UOverlayWidgetController::ManaChanged);
//绑定MaxMana变化回调函数
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AuraAttributeSet->GetMaxManaAttribute())
.AddUObject(this,&UOverlayWidgetController::MaxManaChanged);
}
void UOverlayWidgetController::HealthChanged(const FOnAttributeChangeData& Data) const
{
//通过Controller类,通知到UI类中的事件。
OnHealthChanged.Broadcast(Data.NewValue);
}
void UOverlayWidgetController::MaxHealthChanged(const FOnAttributeChangeData& Data) const
{
//通过Controller类,通知到UI类中的事件。
OnMaxHealthChanged.Broadcast(Data.NewValue);
}
void UOverlayWidgetController::ManaChanged(const FOnAttributeChangeData& Data) const
{
//通过Controller类,通知到UI类中的事件。
OnManaChanged.Broadcast(Data.NewValue);
}
void UOverlayWidgetController::MaxManaChanged(const FOnAttributeChangeData& Data) const
{
//通过Controller类,通知到UI类中的事件。
OnMaxManaChanged.Broadcast(Data.NewValue);
}
BroadcastInitialValues函数主要在UI初始化时调用,在UI初始化时同步数据。
AS中的属性变化时,会调用ASC中的FOnGameplayEffectAppliedDelegate类型的委托,在BindCallBackToDependencies函数中,我们可以通过AS中的属性,获取ASC中的对应委托,在将Controller类中通知UI的回调函数绑定到委托中。这样就完成了AS数据变动——通知UI对应变化。
3、Controller中委托绑定
定义的四个委托在UI类中进行绑定,以血量UI为例,在血量UI的蓝图中,当Health或MaxHealth变化时,对应变化血量UI进度条的百分比。
四、HUD类
1、创建HUD类
HUD类是UE中专门用来管理UI的类,一般UI类的创建以及初始化都在HUD类中进行,所以WBP_Overlay的创建,以及OverlayWidgetController的创建都可以在HUD中进行。
AAuraHUD.h代码:
#pragma once
#include "CoreMinimal.h"
#include "AttributeSet.h"
#include "GameFramework/HUD.h"
#include "AuraHUD.generated.h"
class UAbilitySystemComponent;
struct FWidgetControllerParams;
class UOverlayWidgetController;
class UAuraUserWidget;
/**
*
*/
UCLASS()
class GAS_RPG_STUDY_API AAuraHUD : public AHUD
{
GENERATED_BODY()
public:
UPROPERTY()
TObjectPtr<UAuraUserWidget> OverlayWidget;
/**
* 类似单例模式的获取函数,如果OverlayWidgetController存在直接返回,为空则创建一个。
* @param WCParams 初始化WidgetController的结构体
* @return 返回UOverlayWidgetController
*/
UOverlayWidgetController* GetOverlayWidgetController(const FWidgetControllerParams& WCParams);
/**
* 初始化OverlayUI,设置此UI的Controller类,并将此UI添加到玩家视口,调用此函数时,必须确保参数的类初始化完毕,推荐在InitAbilityActorInfo函数中调用,
* 因为玩家初始化时,InitAbilityActorInfo调用时参数一定初始化完成。
* @param PC 玩家控制器,APlayerController
* @param PS 玩家数据,APlayerState
* @param ASC GAS中的ASC组件,UAbilitySystemComponent
* @param AS GAS的参数集,UAttributeSet
*/
void InitOverlay(APlayerController* PC,APlayerState* PS, UAbilitySystemComponent* ASC,UAttributeSet* AS);
protected:
virtual void BeginPlay() override;
private:
UPROPERTY(EditAnywhere)
TSubclassOf<UAuraUserWidget> OverlayWidgetClass;
UPROPERTY(EditAnywhere)
TSubclassOf<UOverlayWidgetController> OverlayWidgetControllerClass;
UPROPERTY()
TObjectPtr<UOverlayWidgetController> OverlayWidgetController;
};
AAuraHUD.cpp代码:
#include "UI/HUD/AuraHUD.h"
#include "UI/Widget/AuraUserWidget.h"
#include "Blueprint/UserWidget.h"
#include "UI/WidgetController/OverlayWidgetController.h"
UOverlayWidgetController* AAuraHUD::GetOverlayWidgetController(const FWidgetControllerParams& WCParams)
{
if (!OverlayWidgetController)
{
if (OverlayWidgetControllerClass)
{
OverlayWidgetController = NewObject<UOverlayWidgetController>(this,OverlayWidgetControllerClass);
OverlayWidgetController->SetWidgetControllerParams(WCParams);
//绑定Controller中的回调事件
OverlayWidgetController->BindCallBackToDependencies();
return OverlayWidgetController;
}
else
{
UE_LOG(LogTemp,Warning,TEXT("OverlayWidgetControllerClass is Nullptr! Please Set OverlayWidgetControllerClass."));
}
}
return OverlayWidgetController;
}
void AAuraHUD::InitOverlay(APlayerController* PC, APlayerState* PS, UAbilitySystemComponent* ASC, UAttributeSet* AS)
{
//检查类是否在蓝图中设置
checkf(OverlayWidgetClass,TEXT("OverlayWidgetClass is Nullptr! Please Set OverlayWidgetControllerClass."));
checkf(OverlayWidgetControllerClass,TEXT("OverlayWidgetControllerClass is Nullptr! Please Set OverlayWidgetControllerClass."));
//创建Overlay的UI类
OverlayWidget = CreateWidget<UAuraUserWidget>(GetWorld(),OverlayWidgetClass);
//初始化此UI的Controller类
const FWidgetControllerParams WidgetControllerParams(PC,PS,ASC,AS);
UOverlayWidgetController* WidgetController = GetOverlayWidgetController(WidgetControllerParams);
OverlayWidget->SetWidgetController(WidgetController);
//初始化完成后的广播事件
WidgetController->BroadcastInitialValues();
//将UI显示到视口
OverlayWidget->AddToViewport();
}
void AAuraHUD::BeginPlay()
{
Super::BeginPlay();
}
GetOverlayWidgetController函数时类似单例模式的创建获取函数,如果实例存在则直接返回,如果实例不存在(第一次获取时)则创建Controller,在GetOverlayWidgetController中,Controller类创建后立马调用了BindCallBackToDependencies函数,绑定了回调函数。
UI和WidgetController类初始化完成,并且为UI设置了WidgetController类之后,调用BroadcastInitialValues值,初始化了UI中的数值。
2、InitOverlay函数调用
可以看到,初始化Overlay以及WidgetController时,需要PlayerController、PlayerState、AttributeSet、AbilitySystemComponent几个值是有效的。
有什么地方可以确保所有四个值都有效呢?在之前的ASC初始化时,我们也需要PlayerController、PlayerState值是有效的,所以当ASC初始化完成后,这四个值是一定有效的。
所以我们可以在玩家角色Character类的InitAbilityActorInfo函数中,调用InitOverlay函数。
void AAuraCharacter::InitAbilityActorInfo()
{
AAuraPlayerState* AuraState = GetPlayerState<AAuraPlayerState>();
check(AuraState);
AuraState->GetAbilitySystemComponent()->InitAbilityActorInfo(AuraState,this);
AbilitySystemComponent = AuraState->GetAbilitySystemComponent();
AttributeSet = AuraState->GetAttributeSet();
//在玩家视口中添加OverlayUI(初始化UI并设置UI的Controller类)
if (AAuraPlayerController* AuraPlayerController = Cast<AAuraPlayerController>(GetController()))
{
if (AAuraHUD* AuraHUD = Cast<AAuraHUD>(AuraPlayerController->GetHUD()))
{
AuraHUD->InitOverlay(AuraPlayerController,AuraState,AbilitySystemComponent,AttributeSet);
}
}
}
在InitAbilityActorInfo函数的最后调用InitOverlay函数,我们确保了PlayerController、PlayerState、AttributeSet、AbilitySystemComponent所有值有效。
需要注意的是,Cast<AAuraPlayerController>(GetController())这一步,由于我们是联机多人游戏,所以PlayerController不止一个,GetController可能会获取多个PlayerController,但是Cast后,只有客户端本地的玩家的PlayerController是有效的,所以在这里我们对AuraPlayerController 进行了判空,不为空则说明获取的是本地的PlayerController。
五、蓝图设置
最后别忘记在UI的蓝图WBP_XXX中绑定事件。并且一定要记住在HUD的蓝图类的Defaults中,将OverlayWidgetClass以及OverlayWidgetControllerClass选择为对应的蓝图类。

-- UI管理&spm=1001.2101.3001.5002&articleId=153468575&d=1&t=3&u=d7749cba9c2a4af8a42744a5e03cf9e3)

被折叠的 条评论
为什么被折叠?



