如果你在测试的时候发现Client和Server中的子弹没有同步,or你扔一枚炸弹却听到重叠的爆炸声…
撞了空气?
刚刚接触网络同步的时候,很有可能会出现这样的现象,开了一个Listen Server,然后在Client中跑来跑去,撞撞箱子,打打箱子…
然后发现人物撞着撞着撞不到箱子了,像是被什么看不见的东西挡住了,然后试着开枪打,发现箱子被你打飞了,但是在Server中箱子竟然还在原地。
这是因为你的抛射物子弹没有同步到Server,Server并不知道你用projectile将箱子打飞了,这时,你需要使用远程过程调用(RPC)进行Client和Server之间的通信。
RPC的声明有三种:
- 将某个函数声明为在服务器上调用,但在客户端执行
- 将某个函数声明为在客户端上调用,但在服务器执行
- 从服务器调用,在服务器和当前所有连接的n个客户端上执行(共n+1)
分别对应在函数声明前添加
UFUNCTION(Client)
UFUNCTION(Server)
UFUNCTION(NetMulticast)
RPC默认为不可靠,如果要在远端保证调用,则添加关键字Reliable
UFUNCTION(Server, Reliable)
如果需要添加验证,添加关键字WithValidation:
UFUNCTION(Server, Reliable, WithValidation)
让抛射物在Server同样显示出来
对于刚才提出的问题:Client的projectile没有同步到Server具体该如何解决呢?
在角色头文件MultiplayerCharacter.h中,添加函数的Server RPC声明。
void OnFire(); //原本的OnFire()声明
UFUNCTION(Server, Reliable, WithValidation)
void Server_OnFire(FVector Location, FRotator Rotation);
// bool Server_OnFire_Validate(FVector Location, FRotator Rotation);
void Server_OnFire_Implementation(FVector Location, FRotator Rotation);
在Server中生成projectile的任务交给Server_OnFire_Implementation完成,在OnFire()函数中调用Server_OnFire(),这样当Client执行OnFire()时,也会通过RPC使Server同样完成OnFire()。
void MultiplayerCharacter::OnFire()
{
if(ProjectileClass != NULL)
{
if(GetWorld())
{
// 声明位置,旋转,ActorSpawnParams...
// Spawn一个projectile
GetWorld()->SpawnActor<AMultiplayerProjectile>(ProjectileClass, SpawnLocation, SpawnRotation, ActorSpawnParams)
// 在OnFire()函数中调用Server_OnFire()
Server_OnFire(SpawnLocation, SpawnRotation);
}
}
}
void MultiplayerCharacter::Server_OnFire_Implementation(FVector Location, FRotator Rotation)
{
// 设置ActorSpawnParams...
// Implementation中同样Spawn一个projectile,在服务端显示
GetWorld()->SpawnActor<AMultiplayerProjectile>(ProjectileClass, Location, Rotation, ActorSpawnParams)
}
但是这时编译运行会发现,在Client中开了一枪,Server上确实是显示出来了,但是Client中却产生了两个子弹,这是因为Client调用OnFire()时,不仅OnFire()本身会Spawn一个projectile,其中调用的Server_OnFire()会在Server中也同样Spawn一个projectile,这个projectile会通过我们原本勾选的replicates复制一份回Client。
所以要记得把OnFire()原本Spawn projectile的逻辑删掉,此任务交给Server_OnFire_Implementation()。
防止Server端调用Server_OnFire()
然后进行测试会发现,不仅在Client开一枪会调用Server_OnFire_Implementation(),在Server开一枪,也会调用Server_OnFire_Implementation()…
解决这个问题的方法就是在执行Server_OnFire()之前进行判断,判断是在客户端还是在服务端,如果确定是在客户端,才继续调用Server_OnFire()。
图片来源:页游http://www.hp91.cn/页游
判断方式有三种:
- 进行权威(Authority)判断,在UE4中,对Actor的拥有权限分为三种:权威、主控、模拟;比如现有客户端A,客户端B,和一个服务器,服务器拥有最高权限Authority,那么对于服务器来说,其权限为“权威A”,“权威B”,对于A和B来说,它们对自己的权限为“主控”,对另一方的权限为“模拟”,据此可进行这样的判断,保证只有Client会调用到Server_OnFire():
if(!HasAuthority())
{
Server_OnFire(SpawnLocation, SpawnRotation);
}
- 利用GetWorld()->IsServer():
if(!GetWorld()->IsServer())
{
Server_OnFire(SpawnLocation, SpawnRotation);
}
- 利用Role和RemoteRole的特点,因为只有服务器能够向已连接的客户端同步Actor,而客户端不能够向服务器同步,所以只有服务器才能看到
Role ==
ROLE_Authority,并且在UE4中GetLocalRole()返回的枚举类型中ROLE_Authority为最高值,利用此特点可进行判断:
if(GetLocalRole() < ROLE_Authority)
{
Server_OnFire(SpawnLocation, SpawnRotation);
}
此三种方式均能区分当前执行位置为Client还是Server。