UEC 利用代理/委託寫一個生命元件

2022-10-29 18:00:57

首先基於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); }

最後測試,結果無誤