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

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

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);

}

测试

同时倒地

 

 

查看原文