UEC 利用代理/委托写一个生命组件

时间:2022-10-29 16:11:44

首先基于ActorComponent创建一个组件 HealthComponent,将需要的变量与函数创建

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "HealthComponent.generated.h"

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class PVETPC_API UHealthComponent : public UActorComponent
{
    GENERATED_BODY()

public:    
    // Sets default values for this component's properties
    UHealthComponent();
    // 初始化健康值
    UFUNCTION(BlueprintCallable)
        void Init(int taotalHealth,int currentHealth);
    // 造成伤害
    UFUNCTION(BlueprintCallable)
        void HanldTakeAnyDamaged(AActor* DamagedActor, float Damage, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser);
    // 恢复健康值
    UFUNCTION(BlueprintCallable)
        void RestoreHealth(int restoreValue);
    UFUNCTION(BlueprintPure)
        float GetHealth() { return CurrentHealth; }
protected:
    // 总健康值
    float TotalHealth;
    // 当前健康值
    float CurrentHealth;

    // Called when the game starts
    virtual void BeginPlay() override;

public:    
    // Called every frame
    virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
};

这里的 HanldTakeAnyDamaged 函数是通过代理绑定到拥有者身上

HanldTakeAnyDamaged 需要的形参需要与 OnTakeAnyDamage 的宏定义一致

除此之外还有OnTakePointDamageOnTakeRadialDamage 也是一样的操作

#include "Components/HealthComponent.h"
#include "Engine.h"
#include "Kismet/KismetSystemLibrary.h"
// Sets default values for this component's properties
UHealthComponent::UHealthComponent()
{
    // Set this component to be initialized when the game starts, and to be ticked every frame.  You can turn these features
    // off to improve performance if you don't need them.
    PrimaryComponentTick.bCanEverTick = true;

    // ...
}

void UHealthComponent::Init(int taotalHealth, int currentHealth)
{
    TotalHealth = taotalHealth;
    CurrentHealth = currentHealth;
}

void UHealthComponent::HanldTakeAnyDamaged(AActor* DamagedActor, float Damage, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser)
{
    if (Damage <= 0) { return; }
    CurrentHealth = FMath::Clamp( CurrentHealth - Damage , 0.f, TotalHealth);
    UE_LOG(LogTemp, Warning, TEXT("I am Demaged! CurrentHealth = %f"), CurrentHealth);
}

void UHealthComponent::RestoreHealth(int restoreValue)
{
    CurrentHealth = FMath::Clamp(CurrentHealth + restoreValue, 0.f, TotalHealth);
    GEngine->AddOnScreenDebugMessage(-1, 20, FColor::Red, FString(TEXT("I am RestoreHealth!")));
}


// Called when the game starts
void UHealthComponent::BeginPlay()
{
    Super::BeginPlay();
    // 获取拥有者
    AActor* MyOwner = GetOwner();
    // 如果存在就将伤害接收函数绑定
    if (MyOwner)
    {
        UE_LOG(LogTemp, Warning, TEXT("I am bound!"));
        MyOwner->OnTakeAnyDamage.AddDynamic(this, &UHealthComponent::HanldTakeAnyDamaged);
    }
    Init(100,100);
    // ...
    
}

// Called every frame
void UHealthComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

    // ...
}

这时候我们将该组件挂载在角色身上,已经有了效果,但是角色不知道组件生命值是否改变

接着我们在组件头文件的头文件申明下添加代理的宏定义,并创建一个代理对象

并在需要响应的函数中添加广播

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "HealthComponent.generated.h"

// 自定义六参数代理事件
DECLARE_DYNAMIC_MULTICAST_DELEGATE_SixParams(FOnHealthChangedSignature, UHealthComponent*, HealthComp, float, Health, float, HealthDelta, const class UDamageType*, DamageType, class AController*, InstigatedBy, AActor*, DamageCauser);
......
    // 恢复健康值
    UFUNCTION(BlueprintCallable)
        void RestoreHealth(int restoreValue);
    UFUNCTION(BlueprintPure)
        float GetHealth() { return CurrentHealth; }
       // 定义代理
    UPROPERTY(BlueprintAssignable, Category = "Events")
        FOnHealthChangedSignature OnHealthChanged;

......
     
void UHealthComponent::HanldTakeAnyDamaged(AActor* DamagedActor, float Damage, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser)
{
    if (Damage <= 0) { return; }
    CurrentHealth = FMath::Clamp( CurrentHealth - Damage , 0.f, TotalHealth);
    UE_LOG(LogTemp, Warning, TEXT("I am Demaged! CurrentHealth = %f"), CurrentHealth);
    // 每当该函数被调用时,就将调用一次代理函数
    OnHealthChanged.Broadcast(this, CurrentHealth, Damage, DamageType, InstigatedBy, DamageCauser);
}

最后再到拥有者类中添加一个用于回调的操作函数,其中形参对应在生命组件中定义的那样(注意命名是否重复)

头文件

    // 代理事件
    UFUNCTION()
    void OnHealthChanged(UHealthComponent* OnwerHealthComp, float Health, float HealthDelta,
        const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser);

cpp文件

void APCharacter::OnHealthChanged(UHealthComponent* OnwerHealthComp, float Health, float HealthDelta, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser)
{
    if (IsDeath) return;
    UE_LOG(LogTemp, Warning, TEXT("I know I was hurt! "));
    if (Health <= 0 && !IsDeath)
    {
        UE_LOG(LogTemp, Warning, TEXT("I am Death! "));
        IsDeath = true;
     Death(); GetMovementComponent()
->StopMovementImmediately(); GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision); // 分离控制器 DetachFromControllerPendingDestroy(); // 3秒后执行 SetLifeSpan(3.0f);
} }
void APCharacter::BeginPlay() { Super::BeginPlay(); HealthComp->OnHealthChanged.AddDynamic(this, &APCharacter::OnHealthChanged); }

最后测试,结果无误

UEC 利用代理/委托写一个生命组件