feat: 敌人单次伤害,优化伤害处理逻辑

This commit is contained in:
2025-12-13 00:12:03 +08:00
parent ad285f403c
commit 4aeb26c27b
9 changed files with 270 additions and 118 deletions

Binary file not shown.

View File

@@ -4,20 +4,41 @@
#include "Surviver_FPS/Battle/EnemyBase.h"
#include "HealthComponent.h"
#include "GameFramework/Actor.h"
#include "Components/CapsuleComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Surviver_FPS/Battle/DamageableInterface.h"
/**
* @brief AEnemyBase的构造函数。
*/
AEnemyBase::AEnemyBase() {
// 设置敌人 Tag
Tags.Add(FName("Enemy"));
// 允许该Pawn每帧调用Tick()
PrimaryActorTick.bCanEverTick = true;
// 创建并附加生命组件
HealthComponent = CreateDefaultSubobject<UHealthComponent>(TEXT("HealthComponent"));
// 创建并附加胶囊碰撞组件
CapsuleComponent = CreateDefaultSubobject<UCapsuleComponent>(TEXT("CapsuleComponent"));
RootComponent = CapsuleComponent;
StaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMeshComponent"));
StaticMeshComponent->SetupAttachment(CapsuleComponent);
bReplicates = true; // Enable replication
AActor::SetReplicateMovement(true); // Replicate movement
}
void AEnemyBase::BeginPlay() {
Super::BeginPlay();
if (CapsuleComponent) {
CapsuleComponent->OnComponentBeginOverlap.AddDynamic(this, &AEnemyBase::OnBeginOverlap);
CapsuleComponent->OnComponentHit.AddDynamic(this, &AEnemyBase::OnHit);
}
}
void AEnemyBase::Tick(float DeltaTime) {
@@ -28,14 +49,102 @@ void AEnemyBase::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent
Super::SetupPlayerInputComponent(PlayerInputComponent);
}
/**
* @brief 接收伤害的实现。
* @param DamageAmount 伤害量。
* @param InstigatorActor 造成伤害的Actor。
*/
void AEnemyBase::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const {
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// Add replicated properties here if needed
// DOREPLIFETIME(AEnemyBase, PropertyName);
}
// Modify ReceiveDamage to handle server-side logic
void AEnemyBase::ReceiveDamage(float DamageAmount, AActor* InstigatorActor) {
if (HealthComponent) {
// 通过生命组件处理伤害
HealthComponent->HandleDamage(DamageAmount);
if (HasAuthority()) {
// Ensure this logic runs only on the server
if (HealthComponent) {
HealthComponent->HandleDamage(DamageAmount);
// Optionally, broadcast damage to clients if needed
// Example: MulticastDamageEffect(DamageAmount);
}
}
}
void AEnemyBase::OnBeginOverlap(UPrimitiveComponent* OverlappedComp,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex,
bool bFromSweep,
const FHitResult& SweepResult) {
HandleActorContact(OtherActor);
}
void AEnemyBase::OnHit(UPrimitiveComponent* HitComp,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
FVector NormalImpulse,
const FHitResult& Hit) {
HandleActorContact(OtherActor);
}
void AEnemyBase::HandleActorContact(AActor* OtherActor) {
if (bEnableLog) {
UE_LOG(LogTemp,
Warning,
TEXT(
"[ProjectileBase] OnBeginOverlap called - HasAuthority: %d, OtherActor: %s, Instigator: %s"
),
HasAuthority(),
OtherActor ? *OtherActor->GetName() : TEXT("None"),
GetInstigator() ? *GetInstigator()->GetName() : TEXT("None"));
}
// 只在服务器上执行伤害逻辑
if (!HasAuthority()) {
return;
}
if (bEnableLog) {
UE_LOG(LogTemp,
Log,
TEXT("[ProjectileBase] Processing overlap with: %s (Class: %s)"),
*OtherActor->GetName(),
*OtherActor->GetClass()->GetName());
}
// 确保碰撞对象不是自己
if (!OtherActor || OtherActor == this) {
if (bEnableLog) {
UE_LOG(LogTemp,
Warning,
TEXT("[ProjectileBase] Contact ignored - OtherActor is null or self"));
}
return;
}
// 检查碰撞物体Tag
for (const FName& Tag : DamageTags) {
if (OtherActor->ActorHasTag(Tag)) {
// 尝试施加伤害
IDamageableInterface* DamageableActor = Cast<IDamageableInterface>(OtherActor);
if (DamageableActor) {
if (bEnableLog) {
UE_LOG(LogTemp,
Warning,
TEXT("[ProjectileBase] ✓ DamageableActor found, applying damage: %f"),
ContactDamage);
}
DamageableActor->ReceiveDamage(ContactDamage, GetInstigator());
} else {
if (bEnableLog) {
UE_LOG(LogTemp,
Warning,
TEXT("[ProjectileBase] ✗ %s does not implement IDamageableInterface"),
*OtherActor->GetName());
}
}
return;
}
}
if (bEnableLog) {
UE_LOG(LogTemp,
Warning,
TEXT("[ProjectileBase] ✗ %s does not have any Tag in DamageTags"),
*OtherActor->GetName());
}
}

View File

@@ -7,11 +7,12 @@
#include "GameFramework/Pawn.h"
#include "EnemyBase.generated.h"
class UCapsuleComponent;
class UHealthComponent;
/**
* @class AEnemyBase
* @brief 敌人基类,实现了可受伤害接口。
* @brief 敌人基类,实现了可受伤害接口。当碰撞到 Character 对象时,尝试对其造成伤害
* @ingroup Battle
*/
UCLASS()
@@ -19,6 +20,24 @@ class FIRSTPERSONDEMO_API AEnemyBase : public APawn, public IDamageableInterface
GENERATED_BODY()
public:
/**
* @brief 造成伤害量
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Damage")
float ContactDamage = 5.0f;
/**
* @brief 可伤害物体的 Actor Tag 列表
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Damage")
TArray<FName> DamageTags;
/**
* @brief 是否输出 Log
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Log")
bool bEnableLog = false;
/**
* @brief 构造函数设置Pawn的默认属性。
*/
@@ -31,6 +50,18 @@ protected:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
UHealthComponent* HealthComponent;
/**
* @brief 敌人胶囊碰撞体组件
*/
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Components")
UCapsuleComponent* CapsuleComponent;
/**
* @brief 敌人网格体组件
*/
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Components")
UStaticMeshComponent* StaticMeshComponent;
virtual void BeginPlay() override;
public:
@@ -44,4 +75,47 @@ public:
* @param InstigatorActor 造成伤害的Actor。
*/
virtual void ReceiveDamage(float DamageAmount, AActor* InstigatorActor) override;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
protected:
/**
* @brief 处理与其他Actor的重叠事件造成伤害。
* @param OverlappedComp 当前重叠的组件。
* @param OtherActor 另一个重叠的Actor。
* @param OtherComp 另一个Actor的组件。
* @param OtherBodyIndex 另一个组件的身体索引。
* @param bFromSweep 是否是从扫掠开始的重叠。
* @param SweepResult 重叠的碰撞结果信息。
*/
UFUNCTION()
virtual void OnBeginOverlap(UPrimitiveComponent* OverlappedComp,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex,
bool bFromSweep,
const FHitResult& SweepResult);
/**
* @brief 处理与其他Actor的碰撞事件造成伤害。
* @param HitComp 被击中的组件。
* @param OtherActor 另一个碰撞的Actor。
* @param OtherComp 另一个Actor的碰撞组件。
* @param NormalImpulse 碰撞的法线冲量。
* @param Hit 碰撞结果。
*/
UFUNCTION()
virtual void OnHit(UPrimitiveComponent* HitComp,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
FVector NormalImpulse,
const FHitResult& Hit);
private:
/**
* @brief 处理与另一个Actor的接触碰撞或重叠
* @param OtherActor 接触到的Actor
*/
void HandleActorContact(AActor* OtherActor);
};

View File

@@ -109,60 +109,14 @@ void AProjectileBase::OnHit(UPrimitiveComponent* HitComp,
UPrimitiveComponent* OtherComp,
FVector NormalImpulse,
const FHitResult& Hit) {
UE_LOG(LogTemp,
Warning,
TEXT(
"[ProjectileBase] OnHit called - HasAuthority: %d, OtherActor: %s, OtherComp: %s, Instigator: %s"
),
HasAuthority(),
OtherActor ? *OtherActor->GetName() : TEXT("None"),
OtherComp ? *OtherComp->GetName() : TEXT("None"),
GetInstigator() ? *GetInstigator()->GetName() : TEXT("None"));
if (!HasAuthority()) {
return;
}
if (OtherActor != nullptr && OtherActor != this && OtherActor != GetInstigator()) {
if (bEnableLog) {
UE_LOG(LogTemp,
Log,
TEXT("[ProjectileBase] Processing hit on: %s (Class: %s)"),
*OtherActor->GetName(),
*OtherActor->GetClass()->GetName());
// 1. 基类默认行为:单体伤害
IDamageableInterface* DamageableActor = Cast<IDamageableInterface>(OtherActor);
if (DamageableActor) {
if (bEnableLog) {
UE_LOG(LogTemp,
Warning,
TEXT("[ProjectileBase] ✓ DamageableActor found, applying damage: %f"),
BaseDamage);
}
DamageableActor->ReceiveDamage(BaseDamage, GetInstigator());
} else {
if (bEnableLog) {
UE_LOG(LogTemp,
Warning,
TEXT("[ProjectileBase] ✗ %s does not implement IDamageableInterface"),
*OtherActor->GetName());
}
}
// 2. 播放特效
Multicast_OnImpact(Hit.Location, Hit.ImpactNormal.Rotation());
// 3. 根据配置决定是否销毁
if (bDestoryOnHit) {
Destroy();
}
} else {
if (bEnableLog) {
UE_LOG(LogTemp,
Warning,
TEXT("[ProjectileBase] Hit ignored - OtherActor is null, self, or instigator"));
}
Warning,
TEXT("[ProjectileBase] OnHit called - HasAuthority: %d, OtherActor: %s"),
HasAuthority(),
OtherActor ? *OtherActor->GetName() : TEXT("None"));
}
HandleActorContact(OtherActor, Hit);
}
@@ -175,62 +129,64 @@ void AProjectileBase::OnBeginOverlap(UPrimitiveComponent* OverlappedComp,
if (bEnableLog) {
UE_LOG(LogTemp,
Warning,
TEXT(
"[ProjectileBase] OnBeginOverlap called - HasAuthority: %d, OtherActor: %s, Instigator: %s"
),
TEXT("[ProjectileBase] OnBeginOverlap called - HasAuthority: %d, OtherActor: %s"),
HasAuthority(),
OtherActor ? *OtherActor->GetName() : TEXT("None"),
GetInstigator() ? *GetInstigator()->GetName() : TEXT("None"));
}
if (!HasAuthority()) {
return;
}
if (OtherActor != nullptr && OtherActor != this && OtherActor != GetInstigator()) {
if (bEnableLog) {
UE_LOG(LogTemp,
Log,
TEXT("[ProjectileBase] Processing overlap with: %s (Class: %s)"),
*OtherActor->GetName(),
*OtherActor->GetClass()->GetName());
}
// 1. 基类默认行为:单体伤害
IDamageableInterface* DamageableActor = Cast<IDamageableInterface>(OtherActor);
if (DamageableActor) {
if (bEnableLog) {
UE_LOG(LogTemp,
Warning,
TEXT("[ProjectileBase] ✓ DamageableActor found, applying damage: %f"),
BaseDamage);
}
DamageableActor->ReceiveDamage(BaseDamage, GetInstigator());
} else {
if (bEnableLog) {
UE_LOG(LogTemp,
Warning,
TEXT("[ProjectileBase] ✗ %s does not implement IDamageableInterface"),
*OtherActor->GetName());
}
}
// 2. 播放特效
Multicast_OnImpact(SweepResult.Location, SweepResult.ImpactNormal.Rotation());
// 3. 根据配置决定是否销毁
if (bDestoryOnHit) {
Destroy();
}
} else {
if (bEnableLog) {
UE_LOG(LogTemp,
Warning,
TEXT("[ProjectileBase] Overlap ignored - OtherActor is null, self, or instigator"));
}
OtherActor ? *OtherActor->GetName() : TEXT("None"));
}
HandleActorContact(OtherActor, SweepResult);
}
void AProjectileBase::Multicast_OnImpact_Implementation(FVector HitLocation, FRotator HitRotation) {
}
void AProjectileBase::HandleActorContact(AActor* OtherActor, const FHitResult& HitResult) {
// 只在服务器上执行伤害逻辑
if (!HasAuthority()) {
return;
}
// 确保碰撞对象有效且不是自己或发射者
if (OtherActor == nullptr || OtherActor == this || OtherActor == GetInstigator()) {
if (bEnableLog) {
UE_LOG(LogTemp,
Warning,
TEXT("[ProjectileBase] Contact ignored - OtherActor is null, self, or instigator"));
}
return;
}
if (bEnableLog) {
UE_LOG(LogTemp,
Log,
TEXT("[ProjectileBase] Processing contact with: %s (Class: %s)"),
*OtherActor->GetName(),
*OtherActor->GetClass()->GetName());
}
// 1. 基类默认行为:单体伤害
if (IDamageableInterface* DamageableActor = Cast<IDamageableInterface>(OtherActor)) {
if (bEnableLog) {
UE_LOG(LogTemp,
Warning,
TEXT("[ProjectileBase] ✓ DamageableActor found, applying damage: %f"),
BaseDamage);
}
DamageableActor->ReceiveDamage(BaseDamage, GetInstigator());
} else {
if (bEnableLog) {
UE_LOG(LogTemp,
Warning,
TEXT("[ProjectileBase] ✗ %s does not implement IDamageableInterface"),
*OtherActor->GetName());
}
}
// 2. 播放特效
Multicast_OnImpact(HitResult.Location, HitResult.ImpactNormal.Rotation());
// 3. 根据配置决定是否销毁
if (bDestoryOnHit) {
Destroy();
}
}

View File

@@ -92,4 +92,11 @@ protected:
*/
UFUNCTION(BlueprintImplementableEvent, Category = "Visual")
void BP_PlayImpactEffects(FVector Location, FRotator Rotation);
private:
/**
* @brief 处理与另一个Actor的接触碰撞或重叠
* @param OtherActor 接触到的Actor
*/
void HandleActorContact(AActor* OtherActor, const FHitResult& HitResult);
};

View File

@@ -19,6 +19,9 @@
* @brief ASuriverPlayer的构造函数。
*/
ASurviverPlayer::ASurviverPlayer() {
// 设置玩家 Tag
Tags.Add(FName("Player"));
// 网络同步设置
bReplicates = true;
AActor::SetReplicateMovement(true);