feat: 客户端子弹同步,Player内实现简单的射击Weapon逻辑
This commit is contained in:
Binary file not shown.
BIN
Content/Input/IMC_Default.uasset
LFS
BIN
Content/Input/IMC_Default.uasset
LFS
Binary file not shown.
BIN
Content/Surviver/Blueprints/BP_ProjectileBase.uasset
LFS
Normal file
BIN
Content/Surviver/Blueprints/BP_ProjectileBase.uasset
LFS
Normal file
Binary file not shown.
Binary file not shown.
BIN
Content/__ExternalActors__/Surviver/Surviver/5/84/N4MOHHCT7VJR8LHTAP84EH.uasset
LFS
Normal file
BIN
Content/__ExternalActors__/Surviver/Surviver/5/84/N4MOHHCT7VJR8LHTAP84EH.uasset
LFS
Normal file
Binary file not shown.
BIN
Content/__ExternalActors__/Surviver/Surviver/7/5K/9C6MU3HE74FP84Y52PFT33.uasset
LFS
Normal file
BIN
Content/__ExternalActors__/Surviver/Surviver/7/5K/9C6MU3HE74FP84Y52PFT33.uasset
LFS
Normal file
Binary file not shown.
@@ -41,7 +41,8 @@ void UHealthComponent::BeginPlay() {
|
|||||||
* @brief 当CurrentHealth属性被复制时在客户端上调用。
|
* @brief 当CurrentHealth属性被复制时在客户端上调用。
|
||||||
*/
|
*/
|
||||||
void UHealthComponent::OnRep_CurrentHealth() {
|
void UHealthComponent::OnRep_CurrentHealth() {
|
||||||
// 在客户端上,当CurrentHealth被复制时调用
|
// 在客户端上,当CurrentHealth被复制时调用、
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("[Client]UHealthComponent::OnRep_CurrentHealth(), CurrentHealth: %f"), CurrentHealth);
|
||||||
OnHealthChanged.Broadcast(CurrentHealth, MaxHealth);
|
OnHealthChanged.Broadcast(CurrentHealth, MaxHealth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +64,7 @@ void UHealthComponent::HandleDamage(float DamageAmount) {
|
|||||||
// 仅在服务器上处理伤害逻辑
|
// 仅在服务器上处理伤害逻辑
|
||||||
if (GetOwner()->HasAuthority()) {
|
if (GetOwner()->HasAuthority()) {
|
||||||
CurrentHealth = FMath::Clamp(CurrentHealth - DamageAmount, 0.0f, MaxHealth);
|
CurrentHealth = FMath::Clamp(CurrentHealth - DamageAmount, 0.0f, MaxHealth);
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("[Server]UHealthComponent::HandleDamage(), CurrentHealth: %f"), CurrentHealth);
|
||||||
// 在服务器上直接广播事件
|
// 在服务器上直接广播事件
|
||||||
OnHealthChanged.Broadcast(CurrentHealth, MaxHealth);
|
OnHealthChanged.Broadcast(CurrentHealth, MaxHealth);
|
||||||
|
|
||||||
|
|||||||
@@ -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<USphereComponent>(TEXT("SphereComp"));
|
||||||
|
CollisionComp->InitSphereRadius(10.0f);
|
||||||
|
CollisionComp->SetCollisionProfileName("Projectile");
|
||||||
|
CollisionComp->SetNotifyRigidBodyCollision(true); // 必须开启以触发 Hit 事件
|
||||||
|
|
||||||
|
// 这里很关键:如果是物理模拟子弹,客户端也可以开启碰撞以进行预测,
|
||||||
|
// 但实际伤害逻辑只由服务器判定。
|
||||||
|
CollisionComp->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
|
||||||
|
|
||||||
|
RootComponent = CollisionComp;
|
||||||
|
|
||||||
|
// 2. 网格设置
|
||||||
|
ProjectileMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComp"));
|
||||||
|
ProjectileMesh->SetupAttachment(CollisionComp);
|
||||||
|
ProjectileMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); // 网格无碰撞
|
||||||
|
// 设置相对缩放,根据你的模型调整
|
||||||
|
ProjectileMesh->SetRelativeScale3D(FVector(0.5f));
|
||||||
|
|
||||||
|
// 3. 移动组件设置
|
||||||
|
ProjectileMovement = CreateDefaultSubobject<UProjectileMovementComponent>(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<IDamageableInterface>(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) {
|
||||||
|
}
|
||||||
75
Source/FirstPersonDemo/Surviver_FPS/Battle/ProjectileBase.h
Normal file
75
Source/FirstPersonDemo/Surviver_FPS/Battle/ProjectileBase.h
Normal file
@@ -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);
|
||||||
|
};
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
#include "Surviver_FPS/SurviverGameMode.h"
|
#include "Surviver_FPS/SurviverGameMode.h"
|
||||||
|
|
||||||
#include "Battle/SurviverPlayer.h"
|
#include "SurviverPlayer.h"
|
||||||
|
|
||||||
ASurviverGameMode::ASurviverGameMode() {
|
ASurviverGameMode::ASurviverGameMode() {
|
||||||
DefaultPawnClass = ASurviverPlayer::StaticClass();
|
DefaultPawnClass = ASurviverPlayer::StaticClass();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
|
|
||||||
#include "SurviverPlayer.h"
|
#include "SurviverPlayer.h"
|
||||||
#include "HealthComponent.h"
|
#include "Battle/HealthComponent.h"
|
||||||
#include "GameFramework/Actor.h"
|
#include "GameFramework/Actor.h"
|
||||||
|
|
||||||
#include "Animation/AnimInstance.h"
|
#include "Animation/AnimInstance.h"
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "InputActionValue.h"
|
#include "InputActionValue.h"
|
||||||
#include "GameFramework/CharacterMovementComponent.h"
|
#include "GameFramework/CharacterMovementComponent.h"
|
||||||
#include "FirstPersonDemo.h"
|
#include "FirstPersonDemo.h"
|
||||||
|
#include "Battle/ProjectileBase.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief ASuriverPlayer的构造函数。
|
* @brief ASuriverPlayer的构造函数。
|
||||||
@@ -32,15 +33,16 @@ ASurviverPlayer::ASurviverPlayer() {
|
|||||||
GetCapsuleComponent()->InitCapsuleSize(55.f, 96.0f);
|
GetCapsuleComponent()->InitCapsuleSize(55.f, 96.0f);
|
||||||
|
|
||||||
// Create the first person mesh that will be viewed only by this character's owner
|
// Create the first person mesh that will be viewed only by this character's owner
|
||||||
CharacterMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("角色 Mesh"));
|
// CharacterMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("角色 Mesh"));
|
||||||
CharacterMesh->SetupAttachment(GetMesh());
|
// CharacterMesh->SetupAttachment(GetMesh());
|
||||||
// CharacterMesh->SetOnlyOwnerSee(true);
|
// CharacterMesh->SetOnlyOwnerSee(true);
|
||||||
// CharacterMesh->FirstPersonPrimitiveType = EFirstPersonPrimitiveType::FirstPerson;
|
// CharacterMesh->FirstPersonPrimitiveType = EFirstPersonPrimitiveType::FirstPerson;
|
||||||
CharacterMesh->SetCollisionProfileName(FName("NoCollision"));
|
// CharacterMesh->SetCollisionProfileName(FName("NoCollision"));
|
||||||
|
|
||||||
// Create the Camera Component
|
// Create the Camera Component
|
||||||
CharacterCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("First Person Camera"));
|
CharacterCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("First Person Camera"));
|
||||||
CharacterCameraComponent->SetupAttachment(CharacterMesh, FName("head"));
|
// CharacterCameraComponent->SetupAttachment(CharacterMesh, FName("head"));
|
||||||
|
// CharacterCameraComponent->SetupAttachment(this->GetMesh(), FName("head"));
|
||||||
CharacterCameraComponent->
|
CharacterCameraComponent->
|
||||||
SetRelativeLocationAndRotation(FVector(-2.8f, 5.89f, 0.0f), FRotator(0.0f, 90.0f, -90.0f));
|
SetRelativeLocationAndRotation(FVector(-2.8f, 5.89f, 0.0f), FRotator(0.0f, 90.0f, -90.0f));
|
||||||
CharacterCameraComponent->bUsePawnControlRotation = true;
|
CharacterCameraComponent->bUsePawnControlRotation = true;
|
||||||
@@ -97,6 +99,9 @@ void ASurviverPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComp
|
|||||||
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &ASurviverPlayer::LookInput);
|
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &ASurviverPlayer::LookInput);
|
||||||
EnhancedInputComponent->BindAction(MouseLookAction, ETriggerEvent::Triggered, this,
|
EnhancedInputComponent->BindAction(MouseLookAction, ETriggerEvent::Triggered, this,
|
||||||
&ASurviverPlayer::LookInput);
|
&ASurviverPlayer::LookInput);
|
||||||
|
// Firing
|
||||||
|
EnhancedInputComponent->BindAction(FireAction, ETriggerEvent::Started, this, &ASurviverPlayer::DoStartFiring);
|
||||||
|
EnhancedInputComponent->BindAction(FireAction, ETriggerEvent::Completed, this, &ASurviverPlayer::DoStopFiring);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
UE_LOG(LogFirstPersonDemo, Error,
|
UE_LOG(LogFirstPersonDemo, Error,
|
||||||
@@ -167,3 +172,65 @@ void ASurviverPlayer::ReceiveDamage(float DamageAmount, AActor* InstigatorActor)
|
|||||||
void ASurviverPlayer::OnPlayerDied() {
|
void ASurviverPlayer::OnPlayerDied() {
|
||||||
UE_LOG(LogTemp, Warning, TEXT("你死了"));
|
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<AProjectileBase>(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);
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "DamageableInterface.h"
|
#include "Battle/DamageableInterface.h"
|
||||||
#include "GameFramework/Character.h"
|
#include "GameFramework/Character.h"
|
||||||
#include "SurviverPlayer.generated.h"
|
#include "SurviverPlayer.generated.h"
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@ struct FInputActionValue;
|
|||||||
class UInputAction;
|
class UInputAction;
|
||||||
class UCameraComponent;
|
class UCameraComponent;
|
||||||
class UHealthComponent;
|
class UHealthComponent;
|
||||||
|
class AProjectileBase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class ASurviverPlayer
|
* @class ASurviverPlayer
|
||||||
@@ -21,9 +22,9 @@ UCLASS()
|
|||||||
class FIRSTPERSONDEMO_API ASurviverPlayer : public ACharacter, public IDamageableInterface {
|
class FIRSTPERSONDEMO_API ASurviverPlayer : public ACharacter, public IDamageableInterface {
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
/** Pawn mesh: first person view */
|
// /** Pawn mesh: first person view */
|
||||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
|
// UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
|
||||||
USkeletalMeshComponent* CharacterMesh;
|
// USkeletalMeshComponent* CharacterMesh;
|
||||||
|
|
||||||
/** First person camera */
|
/** First person camera */
|
||||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
|
||||||
@@ -71,6 +72,19 @@ protected:
|
|||||||
/** Set up input action bindings */
|
/** Set up input action bindings */
|
||||||
virtual void SetupPlayerInputComponent(UInputComponent* InputComponent) override;
|
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:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief 构造函数,设置角色的默认属性。
|
* @brief 构造函数,设置角色的默认属性。
|
||||||
@@ -102,4 +116,34 @@ public:
|
|||||||
* @param InstigatorActor 造成伤害的Actor。
|
* @param InstigatorActor 造成伤害的Actor。
|
||||||
*/
|
*/
|
||||||
virtual void ReceiveDamage(float DamageAmount, AActor* InstigatorActor) override;
|
virtual void ReceiveDamage(float DamageAmount, AActor* InstigatorActor) override;
|
||||||
|
|
||||||
|
// 射击相关属性
|
||||||
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
|
||||||
|
TSubclassOf<AProjectileBase> 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();
|
||||||
};
|
};
|
||||||
@@ -10,10 +10,10 @@ void UBattleHUD::UpdateHealth(float CurrentHealth, float MaxHealth) {
|
|||||||
if (HealthBar) {
|
if (HealthBar) {
|
||||||
HealthBar->SetPercent(CurrentHealth / MaxHealth);
|
HealthBar->SetPercent(CurrentHealth / MaxHealth);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HealthText) {
|
if (HealthText) {
|
||||||
HealthText->SetText(FText::FromString(FString::Printf(TEXT("%.0f / %.0f"), CurrentHealth, MaxHealth)));
|
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() {
|
void UBattleHUD::NativeConstruct() {
|
||||||
|
|||||||
Reference in New Issue
Block a user