打开主角类,生成枪的代码逻辑在游戏开始函数里
所以在生成之前,我们需要判断该对象是否在服务器端(服务器端视角)
- void ASCharacter::BeginPlay()
- {
- Super::BeginPlay();
- DefaultsFOV = CameraComp->FieldOfView;
- //判断是否在服务器端
- if (Role == ROLE_Authority)
- {
- //设置生成参数,当生成的actor碰到了其他物体,也要生成
- FActorSpawnParameters Parameters;
- Parameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
- //生成武器actor(类型、位置、方向、参数),并且其地址赋予到指针上
- CurrentWeapen1 = GetWorld()->SpawnActor
(StartWeapen, FVector::ZeroVector, FRotator::ZeroRotator, Parameters); - //设置武器的位置与骨骼的插槽中,并设置主人
- if (CurrentWeapen1)
- {
- CurrentWeapen1->SetOwner(this);
- CurrentWeapen1->AttachToComponent(GetMesh(), FAttachmentTransformRules::SnapToTargetNotIncludingScale, WeaponAttachSoketName);
- }
- }
-
- //受伤自定义事件绑定
- HealthComp->OnHealthChanged.AddDynamic(this, &ASCharacter::OnHealthChanged);
-
- }
编译一下,看看是什么效果
我们发现,左边的有武器,右边的没有武器
所以我们要让武器可以进行网络复制
打开武器类,在构造函数中进行设置
- //设置可以进行网络复制
- SetReplicates(true);
编译,然后打开枪的蓝图,打上勾
这一次他们都有枪了
现在枪虽然是有了,左边的玩家可以开枪,但是右边的角色不可以开枪
这是因为右边角色,指向武器的指针是空的
所以我们要让武器指针可以同步,这样就可以同步了
- //目前玩家手中的武器
- UPROPERTY(Replicated)
- class ASWeapen * CurrentWeapen1;
要实现网络同步,必须还要有一个函数,这个函数定义在actor类里面
我们将其复制到玩家类里面
- //用于网络同步的函数
- void GetLifetimeReplicatedProps(TArray
& OutLifetimeProps) const override;
导入头文件
#include "Net/UnrealNetwork.h"
定义这个函数
- void ASCharacter::GetLifetimeReplicatedProps(TArray
& OutLifetimeProps) const - {
- Super::GetLifetimeReplicatedProps(OutLifetimeProps);
- //同步给所有的客户端和服务器
- DOREPLIFETIME(ASCharacter, CurrentWeapen1);
- }
测试,两个角色都可以打枪了,但是互相看不见。
下面解决这个问题。
==========================================
给武器类添加成员函数serverfire()
- UFUNCTION(Server, Reliable, WithValidation)
- void ServerFire();
然后实现该函数的方式比较特殊
- void ASWeapen::ServerFire_Implementation()
- {
- Fire();
- }
-
- bool ASWeapen::ServerFire_Validate()
- {
- return true;
- }
然后我们修改一下Fire()函数
- void ASWeapen::Fire()
- {
- //如果不是服务器,就执行ServerFire()
- if (Role < ROLE_Authority)
- {
- ServerFire();
- return;
- }
- //创建一个撞击句柄,用来获取弹道相关信息
- FHitResult Hit;
- //弹道的起点,我们设置为角色眼睛的位置
- AActor * MyOwner = GetOwner();
- if (MyOwner)
- { //位置
- FVector EyeLocation;
- //方向
- FRotator EyeRotator;
- //得到眼睛的位置和角度
- MyOwner->GetActorEyesViewPoint(EyeLocation, EyeRotator);
- //弹道的终点就是起点+方向*10000
- FVector TraceEnd = EyeLocation + (EyeRotator.Vector() * 1000);
- //弹道特效的结束点
- FVector TraceEndPoint = TraceEnd;
- //设置碰撞通道为可见性通道
- FCollisionQueryParams QueryParams;
- //让射线忽略玩家和枪
- QueryParams.AddIgnoredActor(MyOwner);
- QueryParams.AddIgnoredActor(this);
- //符合追踪设为true,可以让射击更加精准
- QueryParams.bTraceComplex = true;
- //返回命中目标的表面材质
- QueryParams.bReturnPhysicalMaterial = true;
-
-
- //在创建一个单轨迹线来计算弹道
- //LineTraceSingleByChannel击中物体返回true
- if (GetWorld()->LineTraceSingleByChannel(Hit, EyeLocation, TraceEnd, COLLISION_WEAPON, QueryParams))
- {
- //命中对象
- AActor * HitActor = Hit.GetActor();
- //实际的伤害
- float ActualDamage = BaseDamage;
- //得到命中物体表面材质
- EPhysicalSurface SurfaceType = UPhysicalMaterial::DetermineSurfaceType(Hit.PhysMaterial.Get());
- //如果命中的是头部表面材质,伤害变成四倍
- if (SurfaceType == SURFACE_FLESHVULNERABLE)
- {
- ActualDamage *= 4;
- }
-
- //造成点伤害ApplyPointDamage
- //参数分别为命中对象、基础伤害、射击方向、命中信息(命中句柄)、MyOwner->GetInstigatorController(暂时不了解)
- //this(射击者) 和伤害类型
- UGameplayStatics::ApplyPointDamage(HitActor, ActualDamage, EyeRotator.Vector(), Hit, MyOwner->GetInstigatorController(), this, DamageType);
-
- //根据材质的不同,进行不同的处理
- UParticleSystem * SelectedEffect = nullptr;
- switch (SurfaceType)
- {
- //这两种情况是一个效果
- case SURFACE_FLESHDEFAULT:
- case SURFACE_FLESHVULNERABLE:
- SelectedEffect = FleshImpactEffect;
- break;
- default:
- SelectedEffect = DefaultImpactEffect;
- break;
-
- }
-
- //生成特效在命中点
- //ImpactEffect:特效 ImpactPoint:打击点 Rotation():打击方向
- if (SelectedEffect)
- {
- UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), SelectedEffect, Hit.ImpactPoint, Hit.ImpactNormal.Rotation());
- }
- //命中的时候,修改弹道特效的终点
- TraceEndPoint = Hit.ImpactPoint;
-
-
-
- }
- //方便debug
- //DrawDebugLine(GetWorld(), EyeLocation, TraceEnd, FColor::Red, false, 1, 0, 1);
- //射击特效
- PlayFireEffects(TraceEndPoint);
- //最后开火的时间
- FireLastTime = GetWorld()->TimeSeconds;
- }
-
- }
编译,测试,服务器端开火,客户端看不到(不管控制哪个角色,都是在服务器端看到开火)
修改一下代码,我们把return去掉
编译测试
服务器端开火,客户端看不到;客户端开火,同时可以看到
给角色蓝图添加两个节点
===========================================
目前
操作客户端,两端都能看到特效
操作服务器端,客户端看不到特效
在武器类的头文件中,创建结构体
- USTRUCT()
- struct FHitScanTrace
- {
- GENERATED_BODY()
- public:
- //弹道的目的坐标
- UPROPERTY()
- FVector_NetQuantize TraceTo;
- //子弹数目:为了让该结构体内容发生变化,结构体才被不断得被网络复制
- UPROPERTY()
- uint8 BrustCounter;
-
- };
创建该类中的结构体成员变量,和对应的网络复制函数
- //网络射击信息
- UPROPERTY(ReplicatedUsing = OnRep_HitScanTrace)
- FHitScanTrace HitScanTrace;
-
- //网络复制函数
- UFUNCTION()
- void OnRep_HitScanTrace();
当结构体内容发生改变的时候,就会自动调用网络复制函数
在fire函数里面,更新网络射击信息
- //更新网络射击信息
- if (Role == ROLE_Authority)
- {
- //子弹数量++
- HitScanTrace.BrustCounter++;
- //更新弹道目的坐标
- HitScanTrace.TraceTo = TraceEndPoint;
-
- }
- void ASWeapen::Fire()
- {
- //如果不是服务器,就执行ServerFire()
- if (Role < ROLE_Authority)
- {
- ServerFire();
-
- }
- //创建一个撞击句柄,用来获取弹道相关信息
- FHitResult Hit;
- //弹道的起点,我们设置为角色眼睛的位置
- AActor * MyOwner = GetOwner();
- if (MyOwner)
- { //位置
- FVector EyeLocation;
- //方向
- FRotator EyeRotator;
- //得到眼睛的位置和角度
- MyOwner->GetActorEyesViewPoint(EyeLocation, EyeRotator);
- //弹道的终点就是起点+方向*10000
- FVector TraceEnd = EyeLocation + (EyeRotator.Vector() * 1000);
- //弹道特效的结束点
- FVector TraceEndPoint = TraceEnd;
- //设置碰撞通道为可见性通道
- FCollisionQueryParams QueryParams;
- //让射线忽略玩家和枪
- QueryParams.AddIgnoredActor(MyOwner);
- QueryParams.AddIgnoredActor(this);
- //符合追踪设为true,可以让射击更加精准
- QueryParams.bTraceComplex = true;
- //返回命中目标的表面材质
- QueryParams.bReturnPhysicalMaterial = true;
-
-
- //在创建一个单轨迹线来计算弹道
- //LineTraceSingleByChannel击中物体返回true
- if (GetWorld()->LineTraceSingleByChannel(Hit, EyeLocation, TraceEnd, COLLISION_WEAPON, QueryParams))
- {
- //命中对象
- AActor * HitActor = Hit.GetActor();
- //实际的伤害
- float ActualDamage = BaseDamage;
- //得到命中物体表面材质
- EPhysicalSurface SurfaceType = UPhysicalMaterial::DetermineSurfaceType(Hit.PhysMaterial.Get());
- //如果命中的是头部表面材质,伤害变成四倍
- if (SurfaceType == SURFACE_FLESHVULNERABLE)
- {
- ActualDamage *= 4;
- }
-
- //造成点伤害ApplyPointDamage
- //参数分别为命中对象、基础伤害、射击方向、命中信息(命中句柄)、MyOwner->GetInstigatorController(暂时不了解)
- //this(射击者) 和伤害类型
- UGameplayStatics::ApplyPointDamage(HitActor, ActualDamage, EyeRotator.Vector(), Hit, MyOwner->GetInstigatorController(), this, DamageType);
-
- //根据材质的不同,进行不同的处理
- UParticleSystem * SelectedEffect = nullptr;
- switch (SurfaceType)
- {
- //这两种情况是一个效果
- case SURFACE_FLESHDEFAULT:
- case SURFACE_FLESHVULNERABLE:
- SelectedEffect = FleshImpactEffect;
- break;
- default:
- SelectedEffect = DefaultImpactEffect;
- break;
-
- }
-
- //生成特效在命中点
- //ImpactEffect:特效 ImpactPoint:打击点 Rotation():打击方向
- if (SelectedEffect)
- {
- UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), SelectedEffect, Hit.ImpactPoint, Hit.ImpactNormal.Rotation());
- }
- //命中的时候,修改弹道特效的终点
- TraceEndPoint = Hit.ImpactPoint;
-
-
-
- }
- //方便debug
- //DrawDebugLine(GetWorld(), EyeLocation, TraceEnd, FColor::Red, false, 1, 0, 1);
- //射击特效
- PlayFireEffects(TraceEndPoint);
- //更新网络射击信息
- if (Role == ROLE_Authority)
- {
- //子弹数量++
- HitScanTrace.BrustCounter++;
- //更新弹道目的坐标
- HitScanTrace.TraceTo = TraceEndPoint;
-
- }
- //最后开火的时间
- FireLastTime = GetWorld()->TimeSeconds;
- }
-
- }
定义网络复制函数,让其执行射击特效
- void ASWeapen::OnRep_HitScanTrace()
- {
- //调用射击特效
- PlayFireEffects(HitScanTrace.TraceTo);
- }
此时,网络射击信息结构体变量还没有实现网络的复制共享,我们让其共享。
创建复制成员变量的函数
void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override;
定义这个函数
- void ASWeapen::GetLifetimeReplicatedProps(TArray
& OutLifetimeProps) const - {
- Super::GetLifetimeReplicatedProps(OutLifetimeProps);
- //同步给所有的客户端和服务器(DOREPLIFETIME_CONDITION不用同步给自己)
- DOREPLIFETIME_CONDITION(ASWeapen, HitScanTrace,COND_SkipOwner);
- }
编译,测试
发现服务器端射击的时候,客户端可以看到弹道的特效,但看不到击中特效
============================================
现在解决一下这个问题
为网络射击结构体添加表面材质成员变量
- //表面材质
- UPROPERTY()
- TEnumAsByte
SurfaceType;
在fire函数里面为其赋值
- //更新网络射击信息
- if (Role == ROLE_Authority)
- {
- //子弹数量++
- HitScanTrace.BrustCounter++;
- //更新弹道目的坐标
- HitScanTrace.TraceTo = TraceEndPoint;
- //更新击中物体的材质
- HitScanTrace.SurfaceType = SurfaceType;
-
- }
我们为击中特效的生成做成一个函数
- //击中特效
- void PlayImpactEffects(EPhysicalSurface SurfaceType , FVector ImpactPoint);
定义这个函数
- void ASWeapen::PlayImpactEffects(EPhysicalSurface SurfaceType , FVector ImpactPoint)
- {
-
- //根据材质的不同,进行不同的处理
- UParticleSystem * SelectedEffect = nullptr;
- switch (SurfaceType)
- {
- //这两种情况是一个效果
- case SURFACE_FLESHDEFAULT:
- case SURFACE_FLESHVULNERABLE:
- SelectedEffect = FleshImpactEffect;
- break;
- default:
- SelectedEffect = DefaultImpactEffect;
- break;
-
- }
-
- //生成特效在命中点
- //ImpactEffect:特效 ImpactPoint:打击点 Rotation():打击方向
- if (SelectedEffect)
- {
- //计算枪口位置
- FVector MuzzleLocation = MeshComponent->GetSocketLocation("MuzzleSocket");
- //射击方向向量 = 打击点-枪口
- FVector ShotDirection = ImpactPoint - MuzzleLocation;
- UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), SelectedEffect, ImpactPoint, ShotDirection.Rotation());
- }
- }
完善一下复制函数内容函数
- void ASWeapen::OnRep_HitScanTrace()
- {
- //调用射击特效
- PlayFireEffects(HitScanTrace.TraceTo);
- //生成命中特效
- PlayImpactEffects(HitScanTrace.SurfaceType, HitScanTrace.TraceTo);
- }
完善fire函数
- void ASWeapen::Fire()
- {
- //如果不是服务器,就执行ServerFire(),服务器端就有响应
- if (Role < ROLE_Authority)
- {
- ServerFire();
-
- }
- //创建一个撞击句柄,用来获取弹道相关信息
- FHitResult Hit;
- //弹道的起点,我们设置为角色眼睛的位置
- AActor * MyOwner = GetOwner();
- //击中物体的材质
- EPhysicalSurface SurfaceType = EPhysicalSurface::SurfaceType_Default;
-
- if (MyOwner)
- { //位置
- FVector EyeLocation;
- //方向
- FRotator EyeRotator;
- //得到眼睛的位置和角度
- MyOwner->GetActorEyesViewPoint(EyeLocation, EyeRotator);
- //弹道的终点就是起点+方向*10000
- FVector TraceEnd = EyeLocation + (EyeRotator.Vector() * 1000);
- //弹道特效的结束点
- FVector TraceEndPoint = TraceEnd;
- //设置碰撞通道为可见性通道
- FCollisionQueryParams QueryParams;
- //让射线忽略玩家和枪
- QueryParams.AddIgnoredActor(MyOwner);
- QueryParams.AddIgnoredActor(this);
- //符合追踪设为true,可以让射击更加精准
- QueryParams.bTraceComplex = true;
- //返回命中目标的表面材质
- QueryParams.bReturnPhysicalMaterial = true;
-
-
- //在创建一个单轨迹线来计算弹道
- //LineTraceSingleByChannel击中物体返回true
- if (GetWorld()->LineTraceSingleByChannel(Hit, EyeLocation, TraceEnd, COLLISION_WEAPON, QueryParams))
- {
- //命中对象
- AActor * HitActor = Hit.GetActor();
- //实际的伤害
- float ActualDamage = BaseDamage;
- //得到命中物体表面材质
- EPhysicalSurface SurfaceType = UPhysicalMaterial::DetermineSurfaceType(Hit.PhysMaterial.Get());
- //如果命中的是头部表面材质,伤害变成四倍
- if (SurfaceType == SURFACE_FLESHVULNERABLE)
- {
- ActualDamage *= 4;
- }
-
- //造成点伤害ApplyPointDamage
- //参数分别为命中对象、基础伤害、射击方向、命中信息(命中句柄)、MyOwner->GetInstigatorController(暂时不了解)
- //this(射击者) 和伤害类型
- UGameplayStatics::ApplyPointDamage(HitActor, ActualDamage, EyeRotator.Vector(), Hit, MyOwner->GetInstigatorController(), this, DamageType);
- //生成命中特效
- PlayImpactEffects(SurfaceType, Hit.ImpactPoint);
-
- //命中的时候,修改弹道特效的终点
- TraceEndPoint = Hit.ImpactPoint;
-
- }
- //方便debug
- //DrawDebugLine(GetWorld(), EyeLocation, TraceEnd, FColor::Red, false, 1, 0, 1);
- //射击特效
- PlayFireEffects(TraceEndPoint);
- //更新网络射击信息
- if (Role == ROLE_Authority)
- {
- //子弹数量++
- HitScanTrace.BrustCounter++;
- //更新弹道目的坐标
- HitScanTrace.TraceTo = TraceEndPoint;
- //更新击中物体的材质
- HitScanTrace.SurfaceType = SurfaceType;
-
- }
- //最后开火的时间
- FireLastTime = GetWorld()->TimeSeconds;
- }
-
- }
编译,测试,两边特效同步了
============================
现在是死亡不同步
打开生命值组件
首先在游戏开始函数里判断当前是否是服务器,如果是服务器,才绑定受伤
- // Called when the game starts
- void USHealthComponent::BeginPlay()
- {
- Super::BeginPlay();
- //这就意味着客户端不会响应受伤事件
- if (GetOwnerRole() == ROLE_Authority)
- {
- AActor * Owner = GetOwner();
- //将该函数绑定在角色的受伤事件上
- if (Owner)
- {
- Owner->OnTakeAnyDamage.AddDynamic(this, &USHealthComponent::HandleTakeAnyDamage);
- }
- }
-
- Health = DefaultHealth;
-
-
- }
在构造函数中设为可网路复制
- USHealthComponent::USHealthComponent()
- {
- // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
- // off to improve performance if you don't need them.
- PrimaryComponentTick.bCanEverTick = true;
-
- DefaultHealth = 100;
- //网络复制
- SetIsReplicated(true);
- }
将其中的生命值成员变量声明为网路可复制
- //当前生命值
- UPROPERTY(Replicated, BlueprintReadOnly, Category = "HealthComponent")
- float Health;
并定义和声明相关同步函数
- //用于网络同步的函数
- void GetLifetimeReplicatedProps(TArray
& OutLifetimeProps) const override;
#include "Net/UnrealNetwork.h"
- void USHealthComponent::GetLifetimeReplicatedProps(TArray
& OutLifetimeProps) const - {
- Super::GetLifetimeReplicatedProps(OutLifetimeProps);
- //同步给所有的客户端和服务器
- DOREPLIFETIME(USHealthComponent, Health);
- }
打开角色类,将该变量设为网络可复制
- //是否死亡
- UPROPERTY(Replicated, BlueprintReadOnly, Category = "Player")
- bool bDied;
网络同步函数中,也进行同步一下
- void ASCharacter::GetLifetimeReplicatedProps(TArray
& OutLifetimeProps) const - {
- Super::GetLifetimeReplicatedProps(OutLifetimeProps);
- //同步给所有的客户端和服务器
- DOREPLIFETIME(ASCharacter, CurrentWeapen1);
- DOREPLIFETIME(ASCharacter, bDied);
- }
测试
同时倒地