在前两篇中,我们在C++中实现了对GameplayTag的创建,并且创建DataAsset存储数据,按照之前的规划:
- 首先我们需要通过c++去实现创建GameplayTag,这样可以在c++和UE里同时获取到Tag
- 创建一个DataAsset类,用于设置tag对应的属性和显示内容
- 创建AttributeMenuWidgetController实现对应逻辑
我们将在这一篇里实现对AttributeMenuWidgetController创建,可以将数据从AS里同步到UI上面。
创建AttributeMenuWidgetController
首先基于之前的创建的WidgetController的基类创建一个子类
将其命名为AttributeMenuWidgetController
覆盖一下父类的初始化属性函数和构件委托的函数,我们后续将在这两个函数内实现对属性面板的属性的广播。
我们并创建了一个委托AttributeInfoDelegate这个委托设置了BlueprintAssignable修饰符,那么就可以在蓝图里面作为回调绑定使用。
AttributeInfo参数只能在UE面板编辑,我们在上一篇添加了对应的Tag和属性名描述,作为显示内容,如果需要本地化,可以添加多个,切换中英文等其它语种的显示。
cpp
// 版权归暮志未晚所有。
#pragma once
#include "CoreMinimal.h"
#include "UI/WidgetController/MyWidgetController.h"
#include "AttributeMenuWidgetController.generated.h"
class UAttributeInfo;
struct FMyAttributeInfo;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAttibuteInfoSignature, const FMyAttributeInfo&, Info);
/**
*
*/
UCLASS(BlueprintType, Blueprintable)
class AURA_API UAttributeMenuWidgetController : public UMyWidgetController
{
GENERATED_BODY()
public:
virtual void BindCallbacksToDependencies() override;
virtual void BroadcastInitialValues() override;
UPROPERTY(BlueprintAssignable, Category="GAS|Attributes")//设置BlueprintAssignable可以在蓝图作为委托绑定监听
FAttibuteInfoSignature AttributeInfoDelegate;
protected:
UPROPERTY(EditDefaultsOnly)//EditDefaultsOnly只能在UE面板编辑
TObjectPtr<UAttributeInfo> AttributeInfo;
};
这两个函数我们将在后续对其内容进行实现。
在HUD设置Controller
之前,我们在HUD类设置了创建OverlayWidgetController,用于进入游戏时,主界面生命值和法力值的更新。
相应的,我们也将实现对AttributeMenuWidgetController的添加,作为AttributeMenuWidgetController的单例承载。
我们在HUD类里面创建一个承载它实例的变量,并添加一个获取方法
cpp
UAttributeMenuWidgetController* GetAttributeMenuWidgetController(const FWidgetControllerParams& WCParams);
UPROPERTY()
TObjectPtr<UAttributeMenuWidgetController> AttributeMenuWidgetController;
UPROPERTY(EditAnywhere)
TSubclassOf<UAttributeMenuWidgetController> AttributeMenuWidgetControllerClass;
函数的实现这里,其实就是保证只实例化一次,后面的都复用它即可
cpp
UAttributeMenuWidgetController* AMyHUD::GetAttributeMenuWidgetController(const FWidgetControllerParams& WCParams)
{
if(AttributeMenuWidgetController == nullptr)
{
AttributeMenuWidgetController = NewObject<UAttributeMenuWidgetController>(this, AttributeMenuWidgetControllerClass);
AttributeMenuWidgetController->SetWidgetControllerParams(WCParams);
AttributeMenuWidgetController->BindCallbacksToDependencies(); //绑定监听数值变化
}
return AttributeMenuWidgetController;
}
编译,打开UE,创建一个基于AttributeMenuWidgetController类的蓝图,这里创建蓝图的原因是我们需要通过蓝图去设置AttributeInfo的数据。
将我们之前创建的属性数据设置上去
接着到HUD蓝图这里,将AttributeMenuWidgetControllerClass类设置上蓝图。
这样,在项目运行得时候,这两个Controller都会被创建出来,去使用。
接下来就是需要考虑如何在UI里面去获取到Controller,我们之前对OverlayWidgetController的获取是在创建UI的时候,在Widget的事件里面通过蓝图设置过去的,但是在属性面板内,层级太多,我们一层层的设置过去太过于麻烦,下面,我们将使用一个新的方式能够在全局蓝图里获取Controller。
创建蓝图函数库获取Controller
我们创建一个蓝图函数库,可以通过静态函数在任意位置获取到对应的Controller,这样可以让结构更加的清晰,也更加方便。
首先打开UE,创建一个新的c++类,这个类继承至BlueprintFunctionLibrary
设置好名称
编辑器打开,创建两个静态函数,分别用于获取OverlayWidgetController和AttributeMenuWidgetController
BlueprintPure 为去掉在蓝图内使用时的执行接口,这样,我们不需要调用,也可以直接从节点获取返回。
cpp
// 版权归暮志未晚所有。
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "MyAbilitySystemBlueprintLibrary.generated.h"
class UAttributeMenuWidgetController;
class UOverlayWidgetController;
/**
*
*/
UCLASS()
class AURA_API UMyAbilitySystemBlueprintLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintPure, Category="MyAbilitySystemLibrary|WidgetController")
static UOverlayWidgetController* GetOverlayWidgetController(const UObject* WorldContextObject);
UFUNCTION(BlueprintPure, Category="MyAbilitySystemLibrary|WidgetController")
static UAttributeMenuWidgetController* GetAttributeMenuWidgetController(const UObject* WorldContextObject);
};
在函数实现这里,我们通过函数,从世界上下文的对象获取到本地的PlayerController,然后根据PlayerController获取到所需的配置项,再从HUD身上的函数获取Controller。
这里比较有意思的就是通过在一个世界上下文的对象可以获取到它所处的世界内的PlayerController。
cpp
// 版权归暮志未晚所有。
#include "AbilitySystem/MyAbilitySystemBlueprintLibrary.h"
#include "Kismet/GameplayStatics.h"
#include "Player/PlayerStateBase.h"
#include "UI/HUD/MyHUD.h"
#include "UI/WidgetController/MyWidgetController.h"
UOverlayWidgetController* UMyAbilitySystemBlueprintLibrary::GetOverlayWidgetController(const UObject* WorldContextObject)
{
//获取到playerController, 需要传入一个世界空间上下文的对象,用于得到对应世界中的PC列表,0为本地使用的PC
if(APlayerController* PC = UGameplayStatics::GetPlayerController(WorldContextObject, 0))
{
//从PC获取到HUD,我们就可以从HUD获得对应的Controller
if(AMyHUD* HUD = Cast<AMyHUD>(PC->GetHUD()))
{
APlayerStateBase* PS = PC->GetPlayerState<APlayerStateBase>();
UAbilitySystemComponent* ASC = PS->GetAbilitySystemComponent();
UAttributeSet* AS = PS->GetAttributeSet();
const FWidgetControllerParams WidgetControllerParams(PC, PS, ASC, AS);
return HUD->GetOverlayWidgetController(WidgetControllerParams);
}
}
return nullptr;
}
UAttributeMenuWidgetController* UMyAbilitySystemBlueprintLibrary::GetAttributeMenuWidgetController(const UObject* WorldContextObject)
{
//获取到playerController, 需要传入一个世界空间上下文的对象,用于得到对应世界中的PC列表,0为本地使用的PC
if(APlayerController* PC = UGameplayStatics::GetPlayerController(WorldContextObject, 0))
{
//从PC获取到HUD,我们就可以从HUD获得对应的Controller
if(AMyHUD* HUD = Cast<AMyHUD>(PC->GetHUD()))
{
APlayerStateBase* PS = PC->GetPlayerState<APlayerStateBase>();
UAbilitySystemComponent* ASC = PS->GetAbilitySystemComponent();
UAttributeSet* AS = PS->GetAttributeSet();
const FWidgetControllerParams WidgetControllerParams(PC, PS, ASC, AS);
return HUD->GetAttributeMenuWidgetController(WidgetControllerParams);
}
}
return nullptr;
}
然后编译,我们在UE打开Overlay的UI,就可以直接右键获取,下面的事OverlayWidget里面的函数,上面的则是静态函数
我们直接将之前设置方式修改,
也能够实现之前的效果。
接着,我们在WBP_AttributeMenu里面使用静态函数获取到AttributeMenuWidgetController设置
可以做一下测试,打印一下Controller的名称
运行项目,点击打开属性面板,查看是否有名称打印。
这里有个问题,我们在设置OverlayWidgetController时,是在C++内,初始化UI属性显示的。
在函数内,执行逻辑是:
- OverlayWidget->SetWidgetController(OverlayWidgetController); //设置用户控件的控制器层
- 设置属性后,会调用WidgetControllerSet();执行函数,这个在UI里面可以
Widget会在WidgetControllerSet()执行后,进行Controller设置并绑定监听。
- 接着就会执行OverlayWidgetController->BroadcastInitialValues();在函数内进行广播将数据广播出去实现了数据在UI上面初始显示。
但是属性面板是动态创建的,所以我们需要将BroadcastInitialValues设置为蓝图可调用
然后打开WBP_AttributeMenu里面,等构造完成后使用蓝图调用BroadcastInitialValues函数。
Event Construct事件是在自身和自己的子元素都创建完成后才会被调用,我在子元素的Event Construct后面打印11111111,在WBP_AttributeMenu里面打印Hello,发现在最后打印了Hello,证明我们在使用蓝图调用BroadcastInitialValues函数时,子元素可以在Event Construct里面绑定监听委托,即可实现初始化。
我说这么多的原因在于大家一定要了解清楚引擎的运行逻辑,这样才能更好的梳理逻辑学习以及使用内置的内容。
到这里,我们创建了AttributeMenuWidgetController,并实现了在Widget内的任意位置获取它,由于篇幅太长,我将在下一篇里,对AttributeMenuWidgetController内部进行实现,实现数据更新,直接同步到UI上面。