feat: 敌人单次伤害,优化伤害处理逻辑
This commit is contained in:
Binary file not shown.
BIN
Content/Surviver/Blueprints/BP_EnemyBase.uasset
LFS
Normal file
BIN
Content/Surviver/Blueprints/BP_EnemyBase.uasset
LFS
Normal file
Binary file not shown.
Binary file not shown.
BIN
Content/__ExternalActors__/Surviver/Surviver/9/JX/5WNI2S0L9WSVXLCT5TIPLU.uasset
LFS
Normal file
BIN
Content/__ExternalActors__/Surviver/Surviver/9/JX/5WNI2S0L9WSVXLCT5TIPLU.uasset
LFS
Normal file
Binary file not shown.
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
@@ -19,6 +19,9 @@
|
||||
* @brief ASuriverPlayer的构造函数。
|
||||
*/
|
||||
ASurviverPlayer::ASurviverPlayer() {
|
||||
// 设置玩家 Tag
|
||||
Tags.Add(FName("Player"));
|
||||
|
||||
// 网络同步设置
|
||||
bReplicates = true;
|
||||
AActor::SetReplicateMovement(true);
|
||||
|
||||
Reference in New Issue
Block a user