feat: 完成PVP,新增投射物OverLap判断,关闭子弹引力
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
3
FirstPersonDemo.uproject.DotSettings.user
Normal file
3
FirstPersonDemo.uproject.DotSettings.user
Normal 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>
|
||||||
@@ -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"),
|
||||||
CollisionComp->OnComponentHit.AddDynamic(this, &AProjectileBase::OnHit);
|
HasAuthority(), *GetName());
|
||||||
// 忽略发射者(防止子弹生成时直接炸到自己)
|
|
||||||
if (GetInstigator()) {
|
// 绑定碰撞事件(服务器和客户端都绑定,但只在服务器处理伤害)
|
||||||
CollisionComp->IgnoreActorWhenMoving(GetInstigator(), true);
|
CollisionComp->OnComponentHit.AddDynamic(this, &AProjectileBase::OnHit);
|
||||||
}
|
CollisionComp->OnComponentBeginOverlap.AddDynamic(this, &AProjectileBase::OnBeginOverlap);
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("[ProjectileBase] OnComponentHit event bound"));
|
||||||
|
|
||||||
|
// 忽略发射者
|
||||||
|
if (GetInstigator()) {
|
||||||
|
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) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: 服务器调用,服务器+所有客户端执行
|
||||||
|
|||||||
@@ -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"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user