UE GAS学习笔记(六)-- UI管理

一、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类负责控制。

WBP_GlobeProgressBar结构

        我们只需要将UI中图像的尺寸、Texture等变量暴露出来,在子类设置即可。

3、创建玩家屏幕显示的Overlay类

        当前玩家只有血量与蓝量数据,所以Overlay页面中也暂时只有WBP_HealthGlobe和WBP_ManaGlobe两个小部件,用于显示血量与蓝量。

Overlay页面

        现在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进度条的百分比。

在UI中绑定Controller的通知委托

四、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选择为对应的蓝图类。

蓝图中设置默认值
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值