feat: 完成PVP,新增投射物OverLap判断,关闭子弹引力

This commit is contained in:
2025-12-06 21:28:57 +08:00
parent 445e60f1d1
commit df294a0b83
9 changed files with 118 additions and 18 deletions

Binary file not shown.

View File

@@ -0,0 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=34B09978_002D31CE_002D7DEB_002D9423_002DD53D7BFF22C5_002Fd_003Aunsupported_002Fd_003AEigen_002Fd_003ACXX11_002Fd_003Asrc_002Fd_003ATensor_002Ff_003ATensorIndexList_002Eh/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=14C902C7_002DE755_002DF679_002D12DC_002D5C36326B2B52_002Fd_003A1_002E1_002E1t_002Fd_003Ainclude_002Fd_003AWin64_002Fd_003AVS2015_002Fd_003Aopenssl_002Ff_003Acms_002Eh/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>

View File

@@ -42,39 +42,121 @@ AProjectileBase::AProjectileBase() {
ProjectileMovement->MaxSpeed = 2000.f; ProjectileMovement->MaxSpeed = 2000.f;
ProjectileMovement->bRotationFollowsVelocity = true; // 子弹朝向跟随速度方向 ProjectileMovement->bRotationFollowsVelocity = true; // 子弹朝向跟随速度方向
ProjectileMovement->bShouldBounce = false; ProjectileMovement->bShouldBounce = false;
// 关掉重力
ProjectileMovement->ProjectileGravityScale = 0.0f;
// 3秒后自动销毁服务器销毁后客户端也会自动销毁 // 3秒后自动销毁服务器销毁后客户端也会自动销毁
InitialLifeSpan = BulletLifeSpan; InitialLifeSpan = BulletLifeSpan;
} }
void AProjectileBase::BeginPlay() { void AProjectileBase::BeginPlay() {
Super::BeginPlay(); Super::BeginPlay();
// 仅在服务器绑定碰撞事件
if (HasAuthority()) { UE_LOG(LogTemp, Warning, TEXT("[ProjectileBase] BeginPlay - HasAuthority: %d, Name: %s"),
HasAuthority(), *GetName());
// 绑定碰撞事件(服务器和客户端都绑定,但只在服务器处理伤害)
CollisionComp->OnComponentHit.AddDynamic(this, &AProjectileBase::OnHit); CollisionComp->OnComponentHit.AddDynamic(this, &AProjectileBase::OnHit);
// 忽略发射者(防止子弹生成时直接炸到自己) CollisionComp->OnComponentBeginOverlap.AddDynamic(this, &AProjectileBase::OnBeginOverlap);
UE_LOG(LogTemp, Log, TEXT("[ProjectileBase] OnComponentHit event bound"));
// 忽略发射者
if (GetInstigator()) { if (GetInstigator()) {
CollisionComp->IgnoreActorWhenMoving(GetInstigator(), true); CollisionComp->IgnoreActorWhenMoving(GetInstigator(), true);
UE_LOG(LogTemp, Log, TEXT("[ProjectileBase] Ignoring instigator: %s"), *GetInstigator()->GetName());
} else {
UE_LOG(LogTemp, Warning, TEXT("[ProjectileBase] No instigator found"));
} }
// 日志碰撞设置
if (HasAuthority()) {
UE_LOG(LogTemp, Log, TEXT("[ProjectileBase] Collision Profile: %s, Enabled: %d"),
*CollisionComp->GetCollisionProfileName().ToString(),
(int32)CollisionComp->GetCollisionEnabled());
} }
// 测试子弹轨迹
DrawDebugLine(
GetWorld(),
GetActorLocation(),
GetActorLocation() + GetActorForwardVector() * 1000.0f,
FColor::Red,
false,
5.0f, // 持续2秒
0,
1.0f
);
} }
void AProjectileBase::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, void AProjectileBase::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp,
FVector NormalImpulse, const FHitResult& Hit) { 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 (!HasAuthority()) return;
if (OtherActor != nullptr && OtherActor != this && OtherActor != GetInstigator()) { if (OtherActor != nullptr && OtherActor != this && OtherActor != GetInstigator()) {
UE_LOG(LogTemp, Log, TEXT("[ProjectileBase] Processing hit on: %s (Class: %s)"),
*OtherActor->GetName(), *OtherActor->GetClass()->GetName());
// 1. 基类默认行为:单体伤害 // 1. 基类默认行为:单体伤害
IDamageableInterface* DamageableActor = Cast<IDamageableInterface>(OtherActor); IDamageableInterface* DamageableActor = Cast<IDamageableInterface>(OtherActor);
if (DamageableActor) { if (DamageableActor) {
UE_LOG(LogTemp, Warning, TEXT("[ProjectileBase] ✓ DamageableActor found, applying damage: %f"), BaseDamage);
DamageableActor->ReceiveDamage(BaseDamage, GetInstigator()); DamageableActor->ReceiveDamage(BaseDamage, GetInstigator());
} else {
UE_LOG(LogTemp, Warning, TEXT("[ProjectileBase] ✗ %s does not implement IDamageableInterface")
, *OtherActor->GetName());
} }
// 2. 播放特效 // 2. 播放特效
Multicast_OnImpact(Hit.Location, Hit.ImpactNormal.Rotation()); Multicast_OnImpact(Hit.Location, Hit.ImpactNormal.Rotation());
// 3. 根据配置决定是否销毁 // 3. 根据配置决定是否销毁
if (bDestoryOnHit) { if (bDestoryOnHit) {
Destroy(); Destroy();
} }
} else {
UE_LOG(LogTemp, Warning, TEXT("[ProjectileBase] Hit ignored - OtherActor is null, self, or instigator"));
} }
} }
void AProjectileBase::OnBeginOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) {
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 (OtherActor != nullptr && OtherActor != this && OtherActor != GetInstigator()) {
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) {
UE_LOG(LogTemp, Warning, TEXT("[ProjectileBase] ✓ DamageableActor found, applying damage: %f"), BaseDamage);
DamageableActor->ReceiveDamage(BaseDamage, GetInstigator());
} else {
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 {
UE_LOG(LogTemp, Warning, TEXT("[ProjectileBase] Overlap ignored - OtherActor is null, self, or instigator"));
}
}
void AProjectileBase::Multicast_OnImpact_Implementation(FVector HitLocation, FRotator HitRotation) { void AProjectileBase::Multicast_OnImpact_Implementation(FVector HitLocation, FRotator HitRotation) {
} }

View File

@@ -59,6 +59,13 @@ protected:
virtual void OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, virtual void OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp,
FVector NormalImpulse, const FHitResult& Hit); FVector NormalImpulse, const FHitResult& Hit);
/**
* @brief 仅在服务器触发的重叠回调
*/
UFUNCTION()
virtual void OnBeginOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
/** /**
* @brief 多播通知所有客户端播放击中特效(粒子、声音) * @brief 多播通知所有客户端播放击中特效(粒子、声音)
* NetMulticast: 服务器调用,服务器+所有客户端执行 * NetMulticast: 服务器调用,服务器+所有客户端执行

View File

@@ -21,6 +21,7 @@
ASurviverPlayer::ASurviverPlayer() { ASurviverPlayer::ASurviverPlayer() {
// 网络同步设置 // 网络同步设置
bReplicates = true; bReplicates = true;
AActor::SetReplicateMovement(true);
GetCharacterMovement()->SetIsReplicated(true); GetCharacterMovement()->SetIsReplicated(true);
// 允许该角色每帧调用Tick() // 允许该角色每帧调用Tick()
@@ -46,8 +47,14 @@ ASurviverPlayer::ASurviverPlayer() {
// configure the character comps // configure the character comps
GetMesh()->SetOwnerNoSee(true); GetMesh()->SetOwnerNoSee(true);
GetMesh()->FirstPersonPrimitiveType = EFirstPersonPrimitiveType::WorldSpaceRepresentation; GetMesh()->FirstPersonPrimitiveType = EFirstPersonPrimitiveType::WorldSpaceRepresentation;
// 确保Mesh可以被子弹击中网络同步
GetMesh()->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
GetMesh()->SetCollisionResponseToChannel(ECC_Visibility, ECR_Block);
GetCapsuleComponent()->SetCapsuleSize(34.0f, 96.0f); GetCapsuleComponent()->SetCapsuleSize(34.0f, 96.0f);
// 确保胶囊体可以被子弹击中
GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
GetCapsuleComponent()->SetCollisionResponseToChannel(ECC_Visibility, ECR_Block);
// Configure character movement // Configure character movement
GetCharacterMovement()->BrakingDecelerationFalling = 1500.0f; GetCharacterMovement()->BrakingDecelerationFalling = 1500.0f;
@@ -152,8 +159,12 @@ void ASurviverPlayer::DoJumpEnd() {
*/ */
void ASurviverPlayer::ReceiveDamage(float DamageAmount, AActor* InstigatorActor) { void ASurviverPlayer::ReceiveDamage(float DamageAmount, AActor* InstigatorActor) {
if (HealthComponent) { if (HealthComponent) {
UE_LOG(LogTemp, Log, TEXT("[SurviverPlayer] ReceiveDamage called - DamageAmount: %f, Instigator: %s"),
DamageAmount, InstigatorActor ? *InstigatorActor->GetName() : TEXT("None"));
// 通过生命组件处理伤害 // 通过生命组件处理伤害
HealthComponent->HandleDamage(DamageAmount); HealthComponent->HandleDamage(DamageAmount);
} else {
UE_LOG(LogTemp, Warning, TEXT("[SurviverPlayer] HealthComponent is null, cannot process damage"));
} }
} }