• 18uec++多人游戏【服务器为两个角色发枪,并能在线开枪】


    打开主角类,生成枪的代码逻辑在游戏开始函数里

    所以在生成之前,我们需要判断该对象是否在服务器端(服务器端视角)

    1. void ASCharacter::BeginPlay()
    2. {
    3. Super::BeginPlay();
    4. DefaultsFOV = CameraComp->FieldOfView;
    5. //判断是否在服务器端
    6. if (Role == ROLE_Authority)
    7. {
    8. //设置生成参数,当生成的actor碰到了其他物体,也要生成
    9. FActorSpawnParameters Parameters;
    10. Parameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
    11. //生成武器actor(类型、位置、方向、参数),并且其地址赋予到指针上
    12. CurrentWeapen1 = GetWorld()->SpawnActor(StartWeapen, FVector::ZeroVector, FRotator::ZeroRotator, Parameters);
    13. //设置武器的位置与骨骼的插槽中,并设置主人
    14. if (CurrentWeapen1)
    15. {
    16. CurrentWeapen1->SetOwner(this);
    17. CurrentWeapen1->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetNotIncludingScale, WeaponAttachSoketName);
    18. }
    19. }
    20. //受伤自定义事件绑定
    21. HealthComp->OnHealthChanged.AddDynamic(this, &ASCharacter::OnHealthChanged);
    22. }

    编译一下,看看是什么效果

    我们发现,左边的有武器,右边的没有武器

     所以我们要让武器可以进行网络复制

    打开武器类,在构造函数中进行设置

    1. //设置可以进行网络复制
    2. SetReplicates(true);

    编译,然后打开枪的蓝图,打上勾

    这一次他们都有枪了

     

     现在枪虽然是有了,左边的玩家可以开枪,但是右边的角色不可以开枪

    这是因为右边角色,指向武器的指针是空的

     所以我们要让武器指针可以同步,这样就可以同步了

    1. //目前玩家手中的武器
    2. UPROPERTY(Replicated)
    3. class ASWeapen * CurrentWeapen1;

     要实现网络同步,必须还要有一个函数,这个函数定义在actor类里面

     我们将其复制到玩家类里面

    1. //用于网络同步的函数
    2. void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override;

    导入头文件

    #include "Net/UnrealNetwork.h"

     定义这个函数

    1. void ASCharacter::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const
    2. {
    3. Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    4. //同步给所有的客户端和服务器
    5. DOREPLIFETIME(ASCharacter, CurrentWeapen1);
    6. }

    测试,两个角色都可以打枪了,但是互相看不见。

    下面解决这个问题。

    ==========================================

    给武器类添加成员函数serverfire()

    1. UFUNCTION(Server, Reliable, WithValidation)
    2. void ServerFire();

    然后实现该函数的方式比较特殊

    1. void ASWeapen::ServerFire_Implementation()
    2. {
    3. Fire();
    4. }
    5. bool ASWeapen::ServerFire_Validate()
    6. {
    7. return true;
    8. }

     然后我们修改一下Fire()函数

    1. void ASWeapen::Fire()
    2. {
    3. //如果不是服务器,就执行ServerFire()
    4. if (Role < ROLE_Authority)
    5. {
    6. ServerFire();
    7. return;
    8. }
    9. //创建一个撞击句柄,用来获取弹道相关信息
    10. FHitResult Hit;
    11. //弹道的起点,我们设置为角色眼睛的位置
    12. AActor * MyOwner = GetOwner();
    13. if (MyOwner)
    14. { //位置
    15. FVector EyeLocation;
    16. //方向
    17. FRotator EyeRotator;
    18. //得到眼睛的位置和角度
    19. MyOwner->GetActorEyesViewPoint(EyeLocation, EyeRotator);
    20. //弹道的终点就是起点+方向*10000
    21. FVector TraceEnd = EyeLocation + (EyeRotator.Vector() * 1000);
    22. //弹道特效的结束点
    23. FVector TraceEndPoint = TraceEnd;
    24. //设置碰撞通道为可见性通道
    25. FCollisionQueryParams QueryParams;
    26. //让射线忽略玩家和枪
    27. QueryParams.AddIgnoredActor(MyOwner);
    28. QueryParams.AddIgnoredActor(this);
    29. //符合追踪设为true,可以让射击更加精准
    30. QueryParams.bTraceComplex = true;
    31. //返回命中目标的表面材质
    32. QueryParams.bReturnPhysicalMaterial = true;
    33. //在创建一个单轨迹线来计算弹道
    34. //LineTraceSingleByChannel击中物体返回true
    35. if (GetWorld()->LineTraceSingleByChannel(Hit, EyeLocation, TraceEnd, COLLISION_WEAPON, QueryParams))
    36. {
    37. //命中对象
    38. AActor * HitActor = Hit.GetActor();
    39. //实际的伤害
    40. float ActualDamage = BaseDamage;
    41. //得到命中物体表面材质
    42. EPhysicalSurface SurfaceType = UPhysicalMaterial::DetermineSurfaceType(Hit.PhysMaterial.Get());
    43. //如果命中的是头部表面材质,伤害变成四倍
    44. if (SurfaceType == SURFACE_FLESHVULNERABLE)
    45. {
    46. ActualDamage *= 4;
    47. }
    48. //造成点伤害ApplyPointDamage
    49. //参数分别为命中对象、基础伤害、射击方向、命中信息(命中句柄)、MyOwner->GetInstigatorController(暂时不了解)
    50. //this(射击者) 和伤害类型
    51. UGameplayStatics::ApplyPointDamage(HitActor, ActualDamage, EyeRotator.Vector(), Hit, MyOwner->GetInstigatorController(), this, DamageType);
    52. //根据材质的不同,进行不同的处理
    53. UParticleSystem * SelectedEffect = nullptr;
    54. switch (SurfaceType)
    55. {
    56. //这两种情况是一个效果
    57. case SURFACE_FLESHDEFAULT:
    58. case SURFACE_FLESHVULNERABLE:
    59. SelectedEffect = FleshImpactEffect;
    60. break;
    61. default:
    62. SelectedEffect = DefaultImpactEffect;
    63. break;
    64. }
    65. //生成特效在命中点
    66. //ImpactEffect:特效 ImpactPoint:打击点 Rotation():打击方向
    67. if (SelectedEffect)
    68. {
    69. UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), SelectedEffect, Hit.ImpactPoint, Hit.ImpactNormal.Rotation());
    70. }
    71. //命中的时候,修改弹道特效的终点
    72. TraceEndPoint = Hit.ImpactPoint;
    73. }
    74. //方便debug
    75. //DrawDebugLine(GetWorld(), EyeLocation, TraceEnd, FColor::Red, false, 1, 0, 1);
    76. //射击特效
    77. PlayFireEffects(TraceEndPoint);
    78. //最后开火的时间
    79. FireLastTime = GetWorld()->TimeSeconds;
    80. }
    81. }

    编译,测试,服务器端开火,客户端看不到(不管控制哪个角色,都是在服务器端看到开火)

     

     修改一下代码,我们把return去掉

     编译测试

    服务器端开火,客户端看不到;客户端开火,同时可以看到

     

     给角色蓝图添加两个节点

    ===========================================

    目前

    操作客户端,两端都能看到特效

     操作服务器端,客户端看不到特效

    在武器类的头文件中,创建结构体

    1. USTRUCT()
    2. struct FHitScanTrace
    3. {
    4. GENERATED_BODY()
    5. public:
    6. //弹道的目的坐标
    7. UPROPERTY()
    8. FVector_NetQuantize TraceTo;
    9. //子弹数目:为了让该结构体内容发生变化,结构体才被不断得被网络复制
    10. UPROPERTY()
    11. uint8 BrustCounter;
    12. };

    创建该类中的结构体成员变量,和对应的网络复制函数

    1. //网络射击信息
    2. UPROPERTY(ReplicatedUsing = OnRep_HitScanTrace)
    3. FHitScanTrace HitScanTrace;
    4. //网络复制函数
    5. UFUNCTION()
    6. void OnRep_HitScanTrace();

    当结构体内容发生改变的时候,就会自动调用网络复制函数

    在fire函数里面,更新网络射击信息

    1. //更新网络射击信息
    2. if (Role == ROLE_Authority)
    3. {
    4. //子弹数量++
    5. HitScanTrace.BrustCounter++;
    6. //更新弹道目的坐标
    7. HitScanTrace.TraceTo = TraceEndPoint;
    8. }
    1. void ASWeapen::Fire()
    2. {
    3. //如果不是服务器,就执行ServerFire()
    4. if (Role < ROLE_Authority)
    5. {
    6. ServerFire();
    7. }
    8. //创建一个撞击句柄,用来获取弹道相关信息
    9. FHitResult Hit;
    10. //弹道的起点,我们设置为角色眼睛的位置
    11. AActor * MyOwner = GetOwner();
    12. if (MyOwner)
    13. { //位置
    14. FVector EyeLocation;
    15. //方向
    16. FRotator EyeRotator;
    17. //得到眼睛的位置和角度
    18. MyOwner->GetActorEyesViewPoint(EyeLocation, EyeRotator);
    19. //弹道的终点就是起点+方向*10000
    20. FVector TraceEnd = EyeLocation + (EyeRotator.Vector() * 1000);
    21. //弹道特效的结束点
    22. FVector TraceEndPoint = TraceEnd;
    23. //设置碰撞通道为可见性通道
    24. FCollisionQueryParams QueryParams;
    25. //让射线忽略玩家和枪
    26. QueryParams.AddIgnoredActor(MyOwner);
    27. QueryParams.AddIgnoredActor(this);
    28. //符合追踪设为true,可以让射击更加精准
    29. QueryParams.bTraceComplex = true;
    30. //返回命中目标的表面材质
    31. QueryParams.bReturnPhysicalMaterial = true;
    32. //在创建一个单轨迹线来计算弹道
    33. //LineTraceSingleByChannel击中物体返回true
    34. if (GetWorld()->LineTraceSingleByChannel(Hit, EyeLocation, TraceEnd, COLLISION_WEAPON, QueryParams))
    35. {
    36. //命中对象
    37. AActor * HitActor = Hit.GetActor();
    38. //实际的伤害
    39. float ActualDamage = BaseDamage;
    40. //得到命中物体表面材质
    41. EPhysicalSurface SurfaceType = UPhysicalMaterial::DetermineSurfaceType(Hit.PhysMaterial.Get());
    42. //如果命中的是头部表面材质,伤害变成四倍
    43. if (SurfaceType == SURFACE_FLESHVULNERABLE)
    44. {
    45. ActualDamage *= 4;
    46. }
    47. //造成点伤害ApplyPointDamage
    48. //参数分别为命中对象、基础伤害、射击方向、命中信息(命中句柄)、MyOwner->GetInstigatorController(暂时不了解)
    49. //this(射击者) 和伤害类型
    50. UGameplayStatics::ApplyPointDamage(HitActor, ActualDamage, EyeRotator.Vector(), Hit, MyOwner->GetInstigatorController(), this, DamageType);
    51. //根据材质的不同,进行不同的处理
    52. UParticleSystem * SelectedEffect = nullptr;
    53. switch (SurfaceType)
    54. {
    55. //这两种情况是一个效果
    56. case SURFACE_FLESHDEFAULT:
    57. case SURFACE_FLESHVULNERABLE:
    58. SelectedEffect = FleshImpactEffect;
    59. break;
    60. default:
    61. SelectedEffect = DefaultImpactEffect;
    62. break;
    63. }
    64. //生成特效在命中点
    65. //ImpactEffect:特效 ImpactPoint:打击点 Rotation():打击方向
    66. if (SelectedEffect)
    67. {
    68. UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), SelectedEffect, Hit.ImpactPoint, Hit.ImpactNormal.Rotation());
    69. }
    70. //命中的时候,修改弹道特效的终点
    71. TraceEndPoint = Hit.ImpactPoint;
    72. }
    73. //方便debug
    74. //DrawDebugLine(GetWorld(), EyeLocation, TraceEnd, FColor::Red, false, 1, 0, 1);
    75. //射击特效
    76. PlayFireEffects(TraceEndPoint);
    77. //更新网络射击信息
    78. if (Role == ROLE_Authority)
    79. {
    80. //子弹数量++
    81. HitScanTrace.BrustCounter++;
    82. //更新弹道目的坐标
    83. HitScanTrace.TraceTo = TraceEndPoint;
    84. }
    85. //最后开火的时间
    86. FireLastTime = GetWorld()->TimeSeconds;
    87. }
    88. }

    定义网络复制函数,让其执行射击特效

    1. void ASWeapen::OnRep_HitScanTrace()
    2. {
    3. //调用射击特效
    4. PlayFireEffects(HitScanTrace.TraceTo);
    5. }

    此时,网络射击信息结构体变量还没有实现网络的复制共享,我们让其共享。

    创建复制成员变量的函数

    void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override;

    定义这个函数

    1. void ASWeapen::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const
    2. {
    3. Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    4. //同步给所有的客户端和服务器(DOREPLIFETIME_CONDITION不用同步给自己)
    5. DOREPLIFETIME_CONDITION(ASWeapen, HitScanTrace,COND_SkipOwner);
    6. }

    编译,测试

    发现服务器端射击的时候,客户端可以看到弹道的特效,但看不到击中特效

     ============================================

    现在解决一下这个问题

    为网络射击结构体添加表面材质成员变量

    1. //表面材质
    2. UPROPERTY()
    3. TEnumAsByte SurfaceType;

    在fire函数里面为其赋值

    1. //更新网络射击信息
    2. if (Role == ROLE_Authority)
    3. {
    4. //子弹数量++
    5. HitScanTrace.BrustCounter++;
    6. //更新弹道目的坐标
    7. HitScanTrace.TraceTo = TraceEndPoint;
    8. //更新击中物体的材质
    9. HitScanTrace.SurfaceType = SurfaceType;
    10. }

    我们为击中特效的生成做成一个函数

    1. //击中特效
    2. void PlayImpactEffects(EPhysicalSurface SurfaceType , FVector ImpactPoint);

    定义这个函数

    1. void ASWeapen::PlayImpactEffects(EPhysicalSurface SurfaceType , FVector ImpactPoint)
    2. {
    3. //根据材质的不同,进行不同的处理
    4. UParticleSystem * SelectedEffect = nullptr;
    5. switch (SurfaceType)
    6. {
    7. //这两种情况是一个效果
    8. case SURFACE_FLESHDEFAULT:
    9. case SURFACE_FLESHVULNERABLE:
    10. SelectedEffect = FleshImpactEffect;
    11. break;
    12. default:
    13. SelectedEffect = DefaultImpactEffect;
    14. break;
    15. }
    16. //生成特效在命中点
    17. //ImpactEffect:特效 ImpactPoint:打击点 Rotation():打击方向
    18. if (SelectedEffect)
    19. {
    20. //计算枪口位置
    21. FVector MuzzleLocation = MeshComponent->GetSocketLocation("MuzzleSocket");
    22. //射击方向向量 = 打击点-枪口
    23. FVector ShotDirection = ImpactPoint - MuzzleLocation;
    24. UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), SelectedEffect, ImpactPoint, ShotDirection.Rotation());
    25. }
    26. }

    完善一下复制函数内容函数

    1. void ASWeapen::OnRep_HitScanTrace()
    2. {
    3. //调用射击特效
    4. PlayFireEffects(HitScanTrace.TraceTo);
    5. //生成命中特效
    6. PlayImpactEffects(HitScanTrace.SurfaceType, HitScanTrace.TraceTo);
    7. }

    完善fire函数

    1. void ASWeapen::Fire()
    2. {
    3. //如果不是服务器,就执行ServerFire(),服务器端就有响应
    4. if (Role < ROLE_Authority)
    5. {
    6. ServerFire();
    7. }
    8. //创建一个撞击句柄,用来获取弹道相关信息
    9. FHitResult Hit;
    10. //弹道的起点,我们设置为角色眼睛的位置
    11. AActor * MyOwner = GetOwner();
    12. //击中物体的材质
    13. EPhysicalSurface SurfaceType = EPhysicalSurface::SurfaceType_Default;
    14. if (MyOwner)
    15. { //位置
    16. FVector EyeLocation;
    17. //方向
    18. FRotator EyeRotator;
    19. //得到眼睛的位置和角度
    20. MyOwner->GetActorEyesViewPoint(EyeLocation, EyeRotator);
    21. //弹道的终点就是起点+方向*10000
    22. FVector TraceEnd = EyeLocation + (EyeRotator.Vector() * 1000);
    23. //弹道特效的结束点
    24. FVector TraceEndPoint = TraceEnd;
    25. //设置碰撞通道为可见性通道
    26. FCollisionQueryParams QueryParams;
    27. //让射线忽略玩家和枪
    28. QueryParams.AddIgnoredActor(MyOwner);
    29. QueryParams.AddIgnoredActor(this);
    30. //符合追踪设为true,可以让射击更加精准
    31. QueryParams.bTraceComplex = true;
    32. //返回命中目标的表面材质
    33. QueryParams.bReturnPhysicalMaterial = true;
    34. //在创建一个单轨迹线来计算弹道
    35. //LineTraceSingleByChannel击中物体返回true
    36. if (GetWorld()->LineTraceSingleByChannel(Hit, EyeLocation, TraceEnd, COLLISION_WEAPON, QueryParams))
    37. {
    38. //命中对象
    39. AActor * HitActor = Hit.GetActor();
    40. //实际的伤害
    41. float ActualDamage = BaseDamage;
    42. //得到命中物体表面材质
    43. EPhysicalSurface SurfaceType = UPhysicalMaterial::DetermineSurfaceType(Hit.PhysMaterial.Get());
    44. //如果命中的是头部表面材质,伤害变成四倍
    45. if (SurfaceType == SURFACE_FLESHVULNERABLE)
    46. {
    47. ActualDamage *= 4;
    48. }
    49. //造成点伤害ApplyPointDamage
    50. //参数分别为命中对象、基础伤害、射击方向、命中信息(命中句柄)、MyOwner->GetInstigatorController(暂时不了解)
    51. //this(射击者) 和伤害类型
    52. UGameplayStatics::ApplyPointDamage(HitActor, ActualDamage, EyeRotator.Vector(), Hit, MyOwner->GetInstigatorController(), this, DamageType);
    53. //生成命中特效
    54. PlayImpactEffects(SurfaceType, Hit.ImpactPoint);
    55. //命中的时候,修改弹道特效的终点
    56. TraceEndPoint = Hit.ImpactPoint;
    57. }
    58. //方便debug
    59. //DrawDebugLine(GetWorld(), EyeLocation, TraceEnd, FColor::Red, false, 1, 0, 1);
    60. //射击特效
    61. PlayFireEffects(TraceEndPoint);
    62. //更新网络射击信息
    63. if (Role == ROLE_Authority)
    64. {
    65. //子弹数量++
    66. HitScanTrace.BrustCounter++;
    67. //更新弹道目的坐标
    68. HitScanTrace.TraceTo = TraceEndPoint;
    69. //更新击中物体的材质
    70. HitScanTrace.SurfaceType = SurfaceType;
    71. }
    72. //最后开火的时间
    73. FireLastTime = GetWorld()->TimeSeconds;
    74. }
    75. }

    编译,测试,两边特效同步了 

    ============================

    现在是死亡不同步

    打开生命值组件

    首先在游戏开始函数里判断当前是否是服务器,如果是服务器,才绑定受伤

    1. // Called when the game starts
    2. void USHealthComponent::BeginPlay()
    3. {
    4. Super::BeginPlay();
    5. //这就意味着客户端不会响应受伤事件
    6. if (GetOwnerRole() == ROLE_Authority)
    7. {
    8. AActor * Owner = GetOwner();
    9. //将该函数绑定在角色的受伤事件上
    10. if (Owner)
    11. {
    12. Owner->OnTakeAnyDamage.AddDynamic(this, &USHealthComponent::HandleTakeAnyDamage);
    13. }
    14. }
    15. Health = DefaultHealth;
    16. }

    在构造函数中设为可网路复制

    1. USHealthComponent::USHealthComponent()
    2. {
    3. // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
    4. // off to improve performance if you don't need them.
    5. PrimaryComponentTick.bCanEverTick = true;
    6. DefaultHealth = 100;
    7. //网络复制
    8. SetIsReplicated(true);
    9. }

    将其中的生命值成员变量声明为网路可复制

    1. //当前生命值
    2. UPROPERTY(Replicated, BlueprintReadOnly, Category = "HealthComponent")
    3. float Health;

    并定义和声明相关同步函数

    1. //用于网络同步的函数
    2. void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override;
    #include "Net/UnrealNetwork.h"
    1. void USHealthComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const
    2. {
    3. Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    4. //同步给所有的客户端和服务器
    5. DOREPLIFETIME(USHealthComponent, Health);
    6. }

    打开角色类,将该变量设为网络可复制

    1. //是否死亡
    2. UPROPERTY(Replicated, BlueprintReadOnly, Category = "Player")
    3. bool bDied;

    网络同步函数中,也进行同步一下

    1. void ASCharacter::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const
    2. {
    3. Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    4. //同步给所有的客户端和服务器
    5. DOREPLIFETIME(ASCharacter, CurrentWeapen1);
    6. DOREPLIFETIME(ASCharacter, bDied);
    7. }

    测试

    同时倒地

     

     

  • 相关阅读:
    向虚拟机里拖文件,VMtools安装问题
    Tensoflow目标检测实战(4)训练模型转换至tflite并部署
    2013年-2018年上市公司审计数据
    @RequestParam注解的正确使用方式
    TCP三次握手四次挥手(幽默版)
    Git(6)——GitHub
    如何裁剪视频画面尺寸?快把这些方法收好
    循环神经网络 - 通过时间反向传播
    笔试强训48天——day8
    Docker笔记-docker搭建nginx及移植
  • 原文地址:https://blog.csdn.net/zhang2362167998/article/details/127861914