既然已經做了攻擊,那沒有造成傷害也太解,所以今天要來為敵人增加(被)傷害系統。
HealthComponent.h/.cpp
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();
...
}
在要打的敵人身上加 Health Component
然後增加 Component Tags
超簡單的吧~
這裡大劍動畫又有 Root Motion 了,因為我直接把他拉到 Blender 自己加了 Root Motion = =