From 83e7924776249a9d93bb0b9bc6d678228ee2f489 Mon Sep 17 00:00:00 2001 From: Stanley_233 Date: Sat, 6 Dec 2025 19:55:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=A2=E6=88=B7=E7=AB=AF=E5=AD=90?= =?UTF-8?q?=E5=BC=B9=E5=90=8C=E6=AD=A5=EF=BC=8CPlayer=E5=86=85=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E7=AE=80=E5=8D=95=E7=9A=84=E5=B0=84=E5=87=BBWeapon?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Win64/UnrealEditor-FirstPersonDemo.dll | 4 +- Content/Input/IMC_Default.uasset | 4 +- .../Blueprints/BP_ProjectileBase.uasset | 3 + .../BP_SurviverPlayerCharacter.uasset | 4 +- .../5/84/N4MOHHCT7VJR8LHTAP84EH.uasset | 3 + .../7/5K/9C6MU3HE74FP84Y52PFT33.uasset | 3 + .../Surviver_FPS/Battle/HealthComponent.cpp | 5 +- .../Surviver_FPS/Battle/ProjectileBase.cpp | 80 +++++++++++++++++++ .../Surviver_FPS/Battle/ProjectileBase.h | 75 +++++++++++++++++ .../Surviver_FPS/SurviverGameMode.cpp | 2 +- .../{Battle => }/SurviverPlayer.cpp | 79 ++++++++++++++++-- .../{Battle => }/SurviverPlayer.h | 52 +++++++++++- .../{Battle => }/SurviverPlayerController.cpp | 0 .../{Battle => }/SurviverPlayerController.h | 0 .../Surviver_FPS/UI/BattleHUD.cpp | 2 +- 15 files changed, 296 insertions(+), 20 deletions(-) create mode 100644 Content/Surviver/Blueprints/BP_ProjectileBase.uasset create mode 100644 Content/__ExternalActors__/Surviver/Surviver/5/84/N4MOHHCT7VJR8LHTAP84EH.uasset create mode 100644 Content/__ExternalActors__/Surviver/Surviver/7/5K/9C6MU3HE74FP84Y52PFT33.uasset create mode 100644 Source/FirstPersonDemo/Surviver_FPS/Battle/ProjectileBase.cpp create mode 100644 Source/FirstPersonDemo/Surviver_FPS/Battle/ProjectileBase.h rename Source/FirstPersonDemo/Surviver_FPS/{Battle => }/SurviverPlayer.cpp (63%) rename Source/FirstPersonDemo/Surviver_FPS/{Battle => }/SurviverPlayer.h (67%) rename Source/FirstPersonDemo/Surviver_FPS/{Battle => }/SurviverPlayerController.cpp (100%) rename Source/FirstPersonDemo/Surviver_FPS/{Battle => }/SurviverPlayerController.h (100%) diff --git a/Binaries/Win64/UnrealEditor-FirstPersonDemo.dll b/Binaries/Win64/UnrealEditor-FirstPersonDemo.dll index da85671..596705b 100644 --- a/Binaries/Win64/UnrealEditor-FirstPersonDemo.dll +++ b/Binaries/Win64/UnrealEditor-FirstPersonDemo.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ad68d06f6733059f525dd5442226d090508cddf0c8d738ef10e13307a4ecdb3c -size 691200 +oid sha256:b140d8683ea76c6a9d8654f5ab2e936b05ebab42c1c461d924906059f3f2572a +size 720384 diff --git a/Content/Input/IMC_Default.uasset b/Content/Input/IMC_Default.uasset index b63309a..bdd4d04 100644 --- a/Content/Input/IMC_Default.uasset +++ b/Content/Input/IMC_Default.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:133bda6567aaeea1aae05c03597345e4b1d9ef24d868a0275203b3203a7aae98 -size 7787 +oid sha256:d6dc12f6649d742820beedf61426a75a58d58acfe4dd0672dcc13aa0d7793dad +size 8336 diff --git a/Content/Surviver/Blueprints/BP_ProjectileBase.uasset b/Content/Surviver/Blueprints/BP_ProjectileBase.uasset new file mode 100644 index 0000000..d0c5fbf --- /dev/null +++ b/Content/Surviver/Blueprints/BP_ProjectileBase.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f9593438f0731172851e47ca7bd54498556c19bcefc61b9129b7af83bd7f94c +size 33338 diff --git a/Content/Surviver/Blueprints/BP_SurviverPlayerCharacter.uasset b/Content/Surviver/Blueprints/BP_SurviverPlayerCharacter.uasset index 97c0fc8..0af6d67 100644 --- a/Content/Surviver/Blueprints/BP_SurviverPlayerCharacter.uasset +++ b/Content/Surviver/Blueprints/BP_SurviverPlayerCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2db5f42b21ff2d5f1819239511183b367d46cf0920f3113c4f9cfa38bae7b7de -size 38738 +oid sha256:b90fe94dfd626e3060d1034ac1d4e5eef1d1344903ddc786bc5b0b044d40b171 +size 38493 diff --git a/Content/__ExternalActors__/Surviver/Surviver/5/84/N4MOHHCT7VJR8LHTAP84EH.uasset b/Content/__ExternalActors__/Surviver/Surviver/5/84/N4MOHHCT7VJR8LHTAP84EH.uasset new file mode 100644 index 0000000..2989f8a --- /dev/null +++ b/Content/__ExternalActors__/Surviver/Surviver/5/84/N4MOHHCT7VJR8LHTAP84EH.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d89e8c87ab54ef7ff1642aabe4fc0e59bcdb0a19dc559aaad536a076aa8c953 +size 4171 diff --git a/Content/__ExternalActors__/Surviver/Surviver/7/5K/9C6MU3HE74FP84Y52PFT33.uasset b/Content/__ExternalActors__/Surviver/Surviver/7/5K/9C6MU3HE74FP84Y52PFT33.uasset new file mode 100644 index 0000000..8386a6c --- /dev/null +++ b/Content/__ExternalActors__/Surviver/Surviver/7/5K/9C6MU3HE74FP84Y52PFT33.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ee94199c99ff549e09f27f4a689e51184cda8e3b2780d95ba4f0bcb92bfa025 +size 4286 diff --git a/Source/FirstPersonDemo/Surviver_FPS/Battle/HealthComponent.cpp b/Source/FirstPersonDemo/Surviver_FPS/Battle/HealthComponent.cpp index f802b9c..08dacd9 100644 --- a/Source/FirstPersonDemo/Surviver_FPS/Battle/HealthComponent.cpp +++ b/Source/FirstPersonDemo/Surviver_FPS/Battle/HealthComponent.cpp @@ -41,7 +41,8 @@ void UHealthComponent::BeginPlay() { * @brief 当CurrentHealth属性被复制时在客户端上调用。 */ void UHealthComponent::OnRep_CurrentHealth() { - // 在客户端上,当CurrentHealth被复制时调用 + // 在客户端上,当CurrentHealth被复制时调用、 + UE_LOG(LogTemp, Log, TEXT("[Client]UHealthComponent::OnRep_CurrentHealth(), CurrentHealth: %f"), CurrentHealth); OnHealthChanged.Broadcast(CurrentHealth, MaxHealth); } @@ -63,7 +64,7 @@ void UHealthComponent::HandleDamage(float DamageAmount) { // 仅在服务器上处理伤害逻辑 if (GetOwner()->HasAuthority()) { CurrentHealth = FMath::Clamp(CurrentHealth - DamageAmount, 0.0f, MaxHealth); - + UE_LOG(LogTemp, Log, TEXT("[Server]UHealthComponent::HandleDamage(), CurrentHealth: %f"), CurrentHealth); // 在服务器上直接广播事件 OnHealthChanged.Broadcast(CurrentHealth, MaxHealth); diff --git a/Source/FirstPersonDemo/Surviver_FPS/Battle/ProjectileBase.cpp b/Source/FirstPersonDemo/Surviver_FPS/Battle/ProjectileBase.cpp new file mode 100644 index 0000000..280791b --- /dev/null +++ b/Source/FirstPersonDemo/Surviver_FPS/Battle/ProjectileBase.cpp @@ -0,0 +1,80 @@ +// Copyright Majowaveon Games. All Rights Reserved. + + +#include "ProjectileBase.h" + +#include "DamageableInterface.h" +#include "Components/SphereComponent.h" +#include "GameFramework/ProjectileMovementComponent.h" + +// 构造函数 +AProjectileBase::AProjectileBase() { + // 网络同步 + bReplicates = true; + PrimaryActorTick.bCanEverTick = false; + AActor::SetReplicateMovement(true); + SetNetUpdateFrequency(60.0f); + SetMinNetUpdateFrequency(30.0f); + // ========================================================= + // 1. 碰撞体设置 + CollisionComp = CreateDefaultSubobject(TEXT("SphereComp")); + CollisionComp->InitSphereRadius(10.0f); + CollisionComp->SetCollisionProfileName("Projectile"); + CollisionComp->SetNotifyRigidBodyCollision(true); // 必须开启以触发 Hit 事件 + + // 这里很关键:如果是物理模拟子弹,客户端也可以开启碰撞以进行预测, + // 但实际伤害逻辑只由服务器判定。 + CollisionComp->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); + + RootComponent = CollisionComp; + + // 2. 网格设置 + ProjectileMesh = CreateDefaultSubobject(TEXT("MeshComp")); + ProjectileMesh->SetupAttachment(CollisionComp); + ProjectileMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); // 网格无碰撞 + // 设置相对缩放,根据你的模型调整 + ProjectileMesh->SetRelativeScale3D(FVector(0.5f)); + + // 3. 移动组件设置 + ProjectileMovement = CreateDefaultSubobject(TEXT("ProjectileComp")); + ProjectileMovement->UpdatedComponent = CollisionComp; + ProjectileMovement->InitialSpeed = 2000.f; // 速度(提高到3000使其看起来更像子弹) + ProjectileMovement->MaxSpeed = 2000.f; + ProjectileMovement->bRotationFollowsVelocity = true; // 子弹朝向跟随速度方向 + ProjectileMovement->bShouldBounce = false; + // 3秒后自动销毁(服务器销毁后,客户端也会自动销毁) + InitialLifeSpan = BulletLifeSpan; +} + +void AProjectileBase::BeginPlay() { + Super::BeginPlay(); + // 仅在服务器绑定碰撞事件 + if (HasAuthority()) { + CollisionComp->OnComponentHit.AddDynamic(this, &AProjectileBase::OnHit); + // 忽略发射者(防止子弹生成时直接炸到自己) + if (GetInstigator()) { + CollisionComp->IgnoreActorWhenMoving(GetInstigator(), true); + } + } +} + +void AProjectileBase::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, + FVector NormalImpulse, const FHitResult& Hit) { + if (!HasAuthority()) return; + if (OtherActor != nullptr && OtherActor != this && OtherActor != GetInstigator()) { + // 1. 基类默认行为:单体伤害 + IDamageableInterface* DamageableActor = Cast(OtherActor); + if (DamageableActor) { + DamageableActor->ReceiveDamage(BaseDamage, GetInstigator()); + } + // 2. 播放特效 + Multicast_OnImpact(Hit.Location, Hit.ImpactNormal.Rotation()); + // 3. 根据配置决定是否销毁 + if (bDestoryOnHit) { + Destroy(); + } + } +} + +void AProjectileBase::Multicast_OnImpact_Implementation(FVector HitLocation, FRotator HitRotation) { +} diff --git a/Source/FirstPersonDemo/Surviver_FPS/Battle/ProjectileBase.h b/Source/FirstPersonDemo/Surviver_FPS/Battle/ProjectileBase.h new file mode 100644 index 0000000..5a3dea7 --- /dev/null +++ b/Source/FirstPersonDemo/Surviver_FPS/Battle/ProjectileBase.h @@ -0,0 +1,75 @@ +// Copyright Majowaveon Games. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "ProjectileBase.generated.h" + +class UProjectileMovementComponent; +class USphereComponent; + +UCLASS() +/** + * @brief 可被网络复制的投射物基类,具有简单移动、碰撞检测、造成伤害逻辑 + * @ingroup Battle + * @note 这个类比 APawn 轻,简单逻辑的敌人可以直接派生自这个类,只需要再挂一个 @ref UHealthComponent + */ +class FIRSTPERSONDEMO_API AProjectileBase : public AActor { + GENERATED_BODY() + +public: + // Sets default values for this actor's properties + AProjectileBase(); + +protected: + // Called when the game starts or when spawned + virtual void BeginPlay() override; + +public: + // 碰撞组件 + UPROPERTY(VisibleDefaultsOnly, BlueprintReadWrite, Category = "Projectile") + USphereComponent* CollisionComp; + + // 客户端能看到的子弹模型 + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Projectile") + UStaticMeshComponent* ProjectileMesh; + + // 移动组件 + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Projectile") + UProjectileMovementComponent* ProjectileMovement; + +protected: + /** 伤害值 */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Projectile") + float BaseDamage = 20.0f; + + /** 子弹生命时长 */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Projectile") + float BulletLifeSpan = 3.0f; + + /** 碰撞后是否销毁 */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Projectile") + bool bDestoryOnHit = true; + + /** + * @brief 仅在服务器触发的碰撞回调 + */ + UFUNCTION() + virtual void OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, + FVector NormalImpulse, const FHitResult& Hit); + + /** + * @brief 多播通知所有客户端播放击中特效(粒子、声音) + * NetMulticast: 服务器调用,服务器+所有客户端执行 + * Unreliable: 这种瞬时特效允许在网络极差时丢包,不阻塞网络 + */ + UFUNCTION(NetMulticast, Unreliable) + void Multicast_OnImpact(FVector HitLocation, FRotator HitRotation); + + /** + * @brief 蓝图实现的特效逻辑(播放声音、生成粒子) + */ + UFUNCTION(BlueprintImplementableEvent, Category = "Visual") + void BP_PlayImpactEffects(FVector Location, FRotator Rotation); +}; diff --git a/Source/FirstPersonDemo/Surviver_FPS/SurviverGameMode.cpp b/Source/FirstPersonDemo/Surviver_FPS/SurviverGameMode.cpp index d451e4d..c8dbda6 100644 --- a/Source/FirstPersonDemo/Surviver_FPS/SurviverGameMode.cpp +++ b/Source/FirstPersonDemo/Surviver_FPS/SurviverGameMode.cpp @@ -3,7 +3,7 @@ #include "Surviver_FPS/SurviverGameMode.h" -#include "Battle/SurviverPlayer.h" +#include "SurviverPlayer.h" ASurviverGameMode::ASurviverGameMode() { DefaultPawnClass = ASurviverPlayer::StaticClass(); diff --git a/Source/FirstPersonDemo/Surviver_FPS/Battle/SurviverPlayer.cpp b/Source/FirstPersonDemo/Surviver_FPS/SurviverPlayer.cpp similarity index 63% rename from Source/FirstPersonDemo/Surviver_FPS/Battle/SurviverPlayer.cpp rename to Source/FirstPersonDemo/Surviver_FPS/SurviverPlayer.cpp index 112a39c..c7eb615 100644 --- a/Source/FirstPersonDemo/Surviver_FPS/Battle/SurviverPlayer.cpp +++ b/Source/FirstPersonDemo/Surviver_FPS/SurviverPlayer.cpp @@ -2,7 +2,7 @@ #include "SurviverPlayer.h" -#include "HealthComponent.h" +#include "Battle/HealthComponent.h" #include "GameFramework/Actor.h" #include "Animation/AnimInstance.h" @@ -13,6 +13,7 @@ #include "InputActionValue.h" #include "GameFramework/CharacterMovementComponent.h" #include "FirstPersonDemo.h" +#include "Battle/ProjectileBase.h" /** * @brief ASuriverPlayer的构造函数。 @@ -21,7 +22,7 @@ ASurviverPlayer::ASurviverPlayer() { // 网络同步设置 bReplicates = true; GetCharacterMovement()->SetIsReplicated(true); - + // 允许该角色每帧调用Tick() PrimaryActorTick.bCanEverTick = true; @@ -32,15 +33,16 @@ ASurviverPlayer::ASurviverPlayer() { GetCapsuleComponent()->InitCapsuleSize(55.f, 96.0f); // Create the first person mesh that will be viewed only by this character's owner - CharacterMesh = CreateDefaultSubobject(TEXT("角色 Mesh")); - CharacterMesh->SetupAttachment(GetMesh()); + // CharacterMesh = CreateDefaultSubobject(TEXT("角色 Mesh")); + // CharacterMesh->SetupAttachment(GetMesh()); // CharacterMesh->SetOnlyOwnerSee(true); // CharacterMesh->FirstPersonPrimitiveType = EFirstPersonPrimitiveType::FirstPerson; - CharacterMesh->SetCollisionProfileName(FName("NoCollision")); + // CharacterMesh->SetCollisionProfileName(FName("NoCollision")); // Create the Camera Component CharacterCameraComponent = CreateDefaultSubobject(TEXT("First Person Camera")); - CharacterCameraComponent->SetupAttachment(CharacterMesh, FName("head")); + // CharacterCameraComponent->SetupAttachment(CharacterMesh, FName("head")); + // CharacterCameraComponent->SetupAttachment(this->GetMesh(), FName("head")); CharacterCameraComponent-> SetRelativeLocationAndRotation(FVector(-2.8f, 5.89f, 0.0f), FRotator(0.0f, 90.0f, -90.0f)); CharacterCameraComponent->bUsePawnControlRotation = true; @@ -97,6 +99,9 @@ void ASurviverPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComp EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &ASurviverPlayer::LookInput); EnhancedInputComponent->BindAction(MouseLookAction, ETriggerEvent::Triggered, this, &ASurviverPlayer::LookInput); + // Firing + EnhancedInputComponent->BindAction(FireAction, ETriggerEvent::Started, this, &ASurviverPlayer::DoStartFiring); + EnhancedInputComponent->BindAction(FireAction, ETriggerEvent::Completed, this, &ASurviverPlayer::DoStopFiring); } else { UE_LOG(LogFirstPersonDemo, Error, @@ -167,3 +172,65 @@ void ASurviverPlayer::ReceiveDamage(float DamageAmount, AActor* InstigatorActor) void ASurviverPlayer::OnPlayerDied() { UE_LOG(LogTemp, Warning, TEXT("你死了")); } + +void ASurviverPlayer::DoStartFiring() { + UE_LOG(LogTemp, Log, TEXT("[Client] Starting Firing - Role: %d, RemoteRole: %d"), (int32)GetLocalRole(), (int32)GetRemoteRole()); + bIsFiring = true; + Fire(); +} + +void ASurviverPlayer::DoStopFiring() { + UE_LOG(LogTemp, Log, TEXT("[Client] Stop Firing")); + bIsFiring = false; + GetWorldTimerManager().ClearTimer(FireTimerHandle); +} + +void ASurviverPlayer::Fire() { + if (!bIsFiring) return; + + // 客户端获取准确的射击位置和方向 + FVector SpawnLocation = CharacterCameraComponent->GetComponentLocation(); + FRotator SpawnRotation = CharacterCameraComponent->GetComponentRotation(); + + UE_LOG(LogTemp, Log, TEXT("[Fire] Calling RPC_ServerFire - HasAuthority: %d, Location: %s, Rotation: %s"), + HasAuthority(), *SpawnLocation.ToString(), *SpawnRotation.ToString()); + + // 把客户端的准确位置和方向传给服务器 + RPC_ServerFire(SpawnLocation, SpawnRotation); + + // 设置下次射击时间 + GetWorldTimerManager().SetTimer(FireTimerHandle, this, &ASurviverPlayer::Fire, FireRate, false); +} + +void ASurviverPlayer::RPC_ServerFire_Implementation(FVector SpawnLocation, FRotator SpawnRotation) { + UE_LOG(LogTemp, Warning, TEXT("[Server] RPC_ServerFire called - HasAuthority: %d, Location: %s, Rotation: %s"), + HasAuthority(), *SpawnLocation.ToString(), *SpawnRotation.ToString()); + + if (!ProjectileClass) { + UE_LOG(LogTemp, Error, TEXT("[Server] ProjectileClass 未设置,无法射击")); + return; + } + + // 在服务器生成子弹 + FActorSpawnParameters SpawnParams; + SpawnParams.Owner = this; + SpawnParams.Instigator = GetInstigator(); + + AProjectileBase* SpawnedProjectile = GetWorld()->SpawnActor(ProjectileClass, SpawnLocation, SpawnRotation, SpawnParams); + + if (SpawnedProjectile) { + UE_LOG(LogTemp, Log, TEXT("[Server] Projectile spawned successfully: %s"), *SpawnedProjectile->GetName()); + } else { + UE_LOG(LogTemp, Error, TEXT("[Server] Failed to spawn projectile!")); + } + + // 通知所有客户端播放特效 + MulticastPlayFireEffects(); +} + +void ASurviverPlayer::MulticastPlayFireEffects_Implementation() { + // 播放射击音效、粒子特效等 + // 例如: + // UGameplayStatics::PlaySoundAtLocation(this, FireSound, GetActorLocation()); + // UGameplayStatics::SpawnEmitterAttached(MuzzleFlash, CharacterMesh, MuzzleSocketName); +} diff --git a/Source/FirstPersonDemo/Surviver_FPS/Battle/SurviverPlayer.h b/Source/FirstPersonDemo/Surviver_FPS/SurviverPlayer.h similarity index 67% rename from Source/FirstPersonDemo/Surviver_FPS/Battle/SurviverPlayer.h rename to Source/FirstPersonDemo/Surviver_FPS/SurviverPlayer.h index 54eb096..0c26882 100644 --- a/Source/FirstPersonDemo/Surviver_FPS/Battle/SurviverPlayer.h +++ b/Source/FirstPersonDemo/Surviver_FPS/SurviverPlayer.h @@ -3,7 +3,7 @@ #pragma once #include "CoreMinimal.h" -#include "DamageableInterface.h" +#include "Battle/DamageableInterface.h" #include "GameFramework/Character.h" #include "SurviverPlayer.generated.h" @@ -11,6 +11,7 @@ struct FInputActionValue; class UInputAction; class UCameraComponent; class UHealthComponent; +class AProjectileBase; /** * @class ASurviverPlayer @@ -21,9 +22,9 @@ UCLASS() class FIRSTPERSONDEMO_API ASurviverPlayer : public ACharacter, public IDamageableInterface { GENERATED_BODY() - /** Pawn mesh: first person view */ - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true")) - USkeletalMeshComponent* CharacterMesh; + // /** Pawn mesh: first person view */ + // UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true")) + // USkeletalMeshComponent* CharacterMesh; /** First person camera */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true")) @@ -70,6 +71,19 @@ protected: /** Set up input action bindings */ virtual void SetupPlayerInputComponent(UInputComponent* InputComponent) override; + + /** 射击 Input Action */ + UPROPERTY(EditAnywhere, Category ="Input") + UInputAction* FireAction; + + /** 处理开始射击输入 */ + UFUNCTION(BlueprintCallable, Category="Input") + void DoStartFiring(); + + /** 处理结束射击输入 */ + UFUNCTION(BlueprintCallable, Category="Input") + void DoStopFiring(); + public: /** @@ -102,4 +116,34 @@ public: * @param InstigatorActor 造成伤害的Actor。 */ virtual void ReceiveDamage(float DamageAmount, AActor* InstigatorActor) override; + + // 射击相关属性 + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon") + TSubclassOf ProjectileClass; + + UPROPERTY(EditDefaultsOnly, Category = "Weapon") + float FireRate = 0.1f; // 射击间隔 + +private: + FTimerHandle FireTimerHandle; + bool bIsFiring = false; + + /** + * @brief 通知服务器开火的RPC + * @param SpawnLocation 客户端相机的位置 + * @param SpawnRotation 客户端相机的旋转(射击方向) + */ + UFUNCTION(Server, Reliable) + void RPC_ServerFire(FVector SpawnLocation, FRotator SpawnRotation); + + /** + * @brief 多播通知所有客户端播放射击特效(粒子、声音) + */ + UFUNCTION(NetMulticast, Unreliable) + void MulticastPlayFireEffects(); + + /** + * @brief 客户端触发输入后调用的开火函数 + */ + void Fire(); }; diff --git a/Source/FirstPersonDemo/Surviver_FPS/Battle/SurviverPlayerController.cpp b/Source/FirstPersonDemo/Surviver_FPS/SurviverPlayerController.cpp similarity index 100% rename from Source/FirstPersonDemo/Surviver_FPS/Battle/SurviverPlayerController.cpp rename to Source/FirstPersonDemo/Surviver_FPS/SurviverPlayerController.cpp diff --git a/Source/FirstPersonDemo/Surviver_FPS/Battle/SurviverPlayerController.h b/Source/FirstPersonDemo/Surviver_FPS/SurviverPlayerController.h similarity index 100% rename from Source/FirstPersonDemo/Surviver_FPS/Battle/SurviverPlayerController.h rename to Source/FirstPersonDemo/Surviver_FPS/SurviverPlayerController.h diff --git a/Source/FirstPersonDemo/Surviver_FPS/UI/BattleHUD.cpp b/Source/FirstPersonDemo/Surviver_FPS/UI/BattleHUD.cpp index 8ea1504..0042fd0 100644 --- a/Source/FirstPersonDemo/Surviver_FPS/UI/BattleHUD.cpp +++ b/Source/FirstPersonDemo/Surviver_FPS/UI/BattleHUD.cpp @@ -10,10 +10,10 @@ void UBattleHUD::UpdateHealth(float CurrentHealth, float MaxHealth) { if (HealthBar) { HealthBar->SetPercent(CurrentHealth / MaxHealth); } - if (HealthText) { HealthText->SetText(FText::FromString(FString::Printf(TEXT("%.0f / %.0f"), CurrentHealth, MaxHealth))); } + UE_LOG(LogTemp, Log, TEXT("[Client]UBattleHUD::UpdateHealth(): Health: %.2f"), CurrentHealth); } void UBattleHUD::NativeConstruct() {