iT邦幫忙

2025 iThome 鐵人賽

DAY 14
0

既然已經做了攻擊,那沒有造成傷害也太解,所以今天要來為敵人增加(被)傷害系統。

1. 前置作業

  • 新增 HealthComponent.h/.cpp

2. Code

HealthComponent.h

#pragma once

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

// 用於 BoardCast 的,這樣之後要在 Blueprint 裡特效、音效之類的就很方便。
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(
    FOnHealthChanged, float, NewHealth, float, Delta, AActor*, InstigatorActor);

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

public:
    UHealthComponent();

protected:
    virtual void BeginPlay() override;

public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Health")
    float MaxHealth = 100.f;

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Health")
    float CurrentHealth=MaxHealth;

    // Event for UI / FX / etc
    UPROPERTY(BlueprintAssignable, Category="Health")
    FOnHealthChanged OnHealthChanged;

    UFUNCTION(BlueprintCallable, Category="Health")
    void ApplyDamage(float Amount, AActor* InstigatorActor);

    UFUNCTION(BlueprintCallable, Category="Health")
    void Heal(float Amount);

    // 直接回傳 IsAlive 就不用在 cpp 裡另外寫了
    UFUNCTION(BlueprintCallable, Category="Health")
    bool IsAlive() const { return CurrentHealth > 0.f; }
};

HealthComponent.cpp

#include "HealthComponent.h"

UHealthComponent::UHealthComponent()
{
    PrimaryComponentTick.bCanEverTick = false;
}

void UHealthComponent::BeginPlay()
{
    Super::BeginPlay();
    CurrentHealth = MaxHealth;
}

void UHealthComponent::ApplyDamage(float Amount, AActor* InstigatorActor)
{
    if (Amount <= 0.f || !IsAlive()) return;

    float OldHealth = CurrentHealth;
    CurrentHealth = FMath::Clamp(CurrentHealth - Amount, 0.f, MaxHealth);

    // 受到傷害值
    float Delta = CurrentHealth - OldHealth;

    // Broadcast event (UI, FX, etc)
    OnHealthChanged.Broadcast(CurrentHealth, Delta, InstigatorActor);
}

// 這裡連治療也一起加進去了
void UHealthComponent::Heal(float Amount)
{
    if (Amount <= 0.f || !IsAlive()) return;

    float OldHealth = CurrentHealth;
    CurrentHealth = FMath::Clamp(CurrentHealth + Amount, 0.f, MaxHealth);

    float Delta = CurrentHealth - OldHealth;

    OnHealthChanged.Broadcast(CurrentHealth, Delta, nullptr);
}

iThome30daysCharacter.h

public:
    // 這些變數是為的不讓敵人在秒數內受到多次傷害,尤其是在 FPS 會影響攻擊判定的時候
    UPROPERTY()
    TMap<AActor*, float> DamageCooldownMap;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float DamageCooldown = 0.5f;

iThome30daysCharacter.cpp

#include "Component/HealthComponent.h"
// 在做攻擊判定時 Apply Damage 到 Hit Actor
void AiThome30daysCharacter::DoAttackTrace(...){
    float CurrentTime = GetWorld()->GetTimeSeconds();
    if (bHit)
    {
        AActor* HitActor = HitResult.GetActor();
        if (UHealthComponent* HealthComp = HitActor->FindComponentByTag<UHealthComponent>("Damage"))
        {
            float* LastDamageTime = DamageCooldownMap.Find(HitActor);
            if (!LastDamageTime || (CurrentTime - *LastDamageTime >= DamageCooldown)){
                if (CurrentWeapon) HealthComp->ApplyDamage(CurrentWeapon->Damage, HitActor);
                else HealthComp->ApplyDamage(5.f, HitActor);
                // 更新此 Actor 最後攻擊時間
                DamageCooldownMap.Add(HitActor, CurrentTime);
            }
        }
    }
    ...
}

// 最後在攻擊動畫結束時清空 Map
void AiThome30daysCharacter::AttackMontageEnded(...){
    DamageCooldownMap.Empty();
    ...
}

3. Editor 裡使用方式

  • 在要打的敵人身上加 Health Component
    https://ithelp.ithome.com.tw/upload/images/20250928/20171036WI5d5mu0BJ.png

  • 然後增加 Component Tags
    https://ithelp.ithome.com.tw/upload/images/20250928/20171036YKy15nadwS.png

就完成了!

超簡單的吧~
這裡大劍動畫又有 Root Motion 了,因為我直接把他拉到 Blender 自己加了 Root Motion = =


上一篇
# Day 13|武器系統
下一篇
# Day 15|進階攻擊判定
系列文
30 天用 Unreal Engine 5 C++ 開發遊戲15
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言