在前一节中我们实现了血条功能,但是部分功能是在蓝图中实现的,因为在AttributeSetBase类中定义了动态多播代理,而这次是想直接使用代码来全部完成,不在蓝图中使用。
新建了一个UI,作为血条。在C++中不能直接CAST蓝图类,这就很让人抓狂。
于是,用C++新建了一个HealthWidget类,继承自UserWidget。
它的头文件里是这样的:
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Components/ProgressBar.h"
#include "Components/CanvasPanel.h"
#include "HealthWidget.generated.h"
/**
*
*/
UCLASS()
class SHIBI_API UHealthWidget : public UUserWidget
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere,BlueprintReadWrite,meta = (BindWidget))
class UProgressBar* healthBar;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget))
class UCanvasPanel* rootPanel;
bool Initialize() override;
};
healthBar和rootPanel要记住这俩名字,因为刚才 BP123蓝图类中的组件必须改为这俩名字,否则找不到组件,编译不通过。
而且要重写bool Initialize方法。
它的CPP文件是这样的:
// Fill out your copyright notice in the Description page of Project Settings.
#include "HealthWidget.h"
bool UHealthWidget::Initialize()
{
Super::Initialize();
rootPanel = Cast<UCanvasPanel>(GetRootWidget());
if (rootPanel) {
healthBar = Cast<UProgressBar>(GetWidgetFromName("healthBar"));
healthBar->SetPercent(1.0f);
}
return true;
}
这一操作就是将C++的HealthWidget中的变量与 BP123中的组件相关联,这一步很重要。
接下来编译,编译通过后,打开BP123
看到图左下角的两个组件名字了吗,要改成与HealthWidget类变量一样的名字。
为这个BP123的蓝图选择一个父类,就是我们定义的C++的HealthWidget类。
因为我们要改变的目标没变,还是血条数据,所以进入到属性AttributeSetBase类中,进行修改。
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "AttributeSet.h"
#include "AttributeSetBase.generated.h"
/**
*
*/
//DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FHealthDelegate, float, Health, float, MaxHealth);
DECLARE_MULTICAST_DELEGATE_TwoParams(FHealthDelegate,float,float);
UCLASS()
class SHIBI_API UAttributeSetBase : public UAttributeSet
{
public:
GENERATED_BODY()
UAttributeSetBase();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Properties From AttributeSetBase")
FGameplayAttributeData Health;
FGameplayAttributeData MaxHealth;
FHealthDelegate onHealthChangeDelegate;
void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData& Data) override;
};
这里使用的不再是动态多播代理了,而是多播代理。意味着我们不需要在蓝图中实现血条数据变化的功能了。
它的CPP内容没变,依旧和原来一样:
// Fill out your copyright notice in the Description page of Project Settings.
#include "AttributeSetBase.h"
#include "GameplayEffectExtension.h"
#include "GameplayEffect.h"
UAttributeSetBase::UAttributeSetBase():Health(200.0f), MaxHealth(200.0f){
}
void UAttributeSetBase::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
if (Data.EvaluatedData.Attribute.GetUProperty() == FindFieldChecked<UProperty>(UAttributeSetBase::StaticClass(),GET_MEMBER_NAME_CHECKED(UAttributeSetBase,Health))) {
onHealthChangeDelegate.Broadcast(Health.GetBaseValue(),MaxHealth.GetCurrentValue());
}
}
现在就差最后一步,让主角(和敌人)拥有血条UI,并改变血量数据从而影响UI的显示。
在主角的头文件中引入:#include "Components/WidgetComponent.h"和#include "HealthWidget.h"(这是我们刚才定义的C++的UserWidget的子类)
让主角能够拥有widget的Component。
并增加以下代码。onDamage就是处理血条数据的。Health与MaxHealth数据是由AttributeSetBase类中的postGameplayEffectExecute方法通过多播代理传递过来的。
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Shinbi | UI")
class UWidgetComponent* widget;
UFUNCTION(BlueprintCallable)
void onDamage(float Health, float MaxHealth);
在主角构造器中加入以下代码:
widget = CreateDefaultSubobject<UWidgetComponent>(TEXT("widget"));
widget->SetupAttachment(shibiSkeletal);
widget->SetRelativeLocation(FVector(0,0,200));
auto asset = LoadClass<UHealthWidget>(NULL, TEXT("WidgetBlueprint'/Game/BP123.BP123_C'"));
widget->SetWidgetClass(asset);
widget->SetWidgetSpace(EWidgetSpace::Screen);
//attributeComponent->onHealthChangeDelegate.AddDynamic(this, &AShinbi::OnHealthChanged);
attributeComponent->onHealthChangeDelegate.AddUObject(this, &AShinbi::onDamage);
1.先创建UWidgetComponent的对象。
2.调整widget对象与人物的相对位置。
3.通过LoadClass来读取BP123蓝图类。
4.将蓝图类BP123设置为widget组件的WidgetClass属性的参数,这样人物就可以拥有显示出来的UI血条了。
5.设置widget组件为screen,保证在任何角度都能够清楚看见血条。
6.实施多播代理的绑定。
绑定的方法如下:
void AShinbi::onDamage(float Health, float MaxHealth) {
float temp = Health / MaxHealth;
UUserWidget* tobeCast = widget->GetUserWidgetObject();
UHealthWidget* bp123 = Cast<UHealthWidget>(tobeCast);
bp123->healthBar->SetPercent(temp);
}
这一过程与蓝图操作一致。这里能成功将widget组件中的UserWidget类的UI转成UHealthWidget类,且成功访问到healthBar,是因为多态机制决定的。我们利用这一机制完成了类型转换。同事UHealthWidget的参数是绑定了BP123中的组件的,此时访问UHealthWidget中的参数就是在访问BP123中的组件。
成功运行,效果如下: