同步系列15-Pitch的同步bug原因和解决方法


在前面的内容里已经创建好了多人游戏中瞄准的动画,但是在测试中会发现一个问题,角色本地持枪后上下瞄准动画是正常的,但是在其他客户端上会产生明显的异常效果,如下:

image-20230505202159447

当玩家持枪低于水平0度的时候,自己看动画是正常的,但是在别人看来缺突然把武器举上天了。我们在动画类中的AimOffset函数加上如下测试代码:

1
2
3
4
5
6
7
8
void ABlasterCharacter::AimOffset(float DeltaTime)
{
AO_Pitch = GetBaseAimRotation().Pitch;//**控制持枪动画的上下,不管是第一人称还是第三人称都需要
if(HasAuthority() && !IsLocallyControlled())
{
UE_LOG(LogTemp, Warning,TEXT("%.0F"),AO_Pitch);
}
}

这段代码可以看到在其他客户端上你所控制的对应的角色的Pitch值,当我将武器不断向下瞄准直至低于水平面时,可以看到角度从1直接到了359而不是负数,又因为角度变成了359,所以在其他客户端看来我所控制的角色才突然朝天看。而问题出现在虚幻网络传输过程中。

在虚幻的网络传输过程中,因为在联机游戏中往往有大量的信息需要传送,为了避免信息的拥堵,在网络传输过程中往往要对信息进行压缩,我们的Pitch数据正是在这一过程中因为数据压缩而发生了变化。从之前的博客我们可以知道,玩家的AO_Pitch值是从GetBaseAimRotation().Pitch拿到的,这里的Pitch值是写在CharacterMovementComponent组件里的,而我们知道,CharacterMovementComponent组件已经帮我们处理好了网络同步的问题,我们不需要手动同步关于角色移动组件的属性。不过这里问题就出在它帮我们处理的过程中。在虚幻5.2版本中,CharacterMovementComponent.h有一个叫PackYawAndPitchTo32的函数,在2766行。

1
2
3
4
5
6
7
FORCEINLINE uint32 UCharacterMovementComponent::PackYawAndPitchTo32(const float Yaw, const float Pitch)
{
const uint32 YawShort = FRotator::CompressAxisToShort(Yaw);
const uint32 PitchShort = FRotator::CompressAxisToShort(Pitch);
const uint32 Rotation32 = (YawShort << 16) | PitchShort;
return Rotation32;
}

这个函数就是压缩角色Pitch和Yaw在网络中传输的数据。我们点进CompressAxisToShort函数,这个函数名字很直观,压缩成更短的。

1
2
3
4
5
FORCEINLINE uint16 TRotator<T>::CompressAxisToShort( T Angle )
{
// map [0->360) to [0->65536) and mask off any winding
return FMath::RoundToInt(Angle * (T)65536.f / (T)360.f) & 0xFFFF;
}

可以看到这个函数可以将你传入的float类型的Pitch乘以65536再/360后与0xFFFF这个16个1的十六进制数进行相与,从而得到一个uint16类型,从而压缩了数据长度。更方便的在网络中进行传输,但这样做得到的结果则是一个没有符号的值,于是别的客户端得到的你的角色就是一个永远不会朝地上看,你一朝地上看角色就会看天的情况。

解决的办法是在AO_Pitch拿到值后进行一次检测,如果发现角度比90度大,那这个数据就是经过转换的,因为我们上下看,以水平为轴,角度只会从-90到90,而不是0到360。我们进行如下转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void ABlasterCharacter::AimOffset(float DeltaTime)
{
AO_Pitch = GetBaseAimRotation().Pitch;*//**控制持枪动画的上下,不管是第一人称还是第三人称都需要
if(HasAuthority() && !IsLocallyControlled())
{
UE_LOG(LogTemp, Warning,TEXT("%.0F"),AO_Pitch);
}
if(AO_Pitch > 90.f && !IsLocallyControlled())//**在网络传输过程中,**Pitch**会从**float**压缩成**unsigned**进行传输从而丢失正负,需要进行转换
{
//**将**AO_Pitch**从**[270,360)**映射到**[-90,0];
FVector2D InRange(270.f,360.f);
FVector2d OutRange(-90.f,0.f);
AO_Pitch = FMath::GetMappedRangeValueClamped(InRange,OutRange,AO_Pitch);
}
}

从而得到正确的值

image-20230505202630560

现在我们来看一下log和结果

image-20230505205007532

现在log中Pitch值正常从正变负,从其他客户端看动画也正常了。


文章作者: John Doe
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 John Doe !
  目录