第一部分从移动相关架构以及单机情况下移动的处理细节讲起 UE4移动组件详解(一)——移动框架与实现原理
而第二部分是关于移动组件同步解决方案的描述,里面有诸多细节来让移动的同步表现的更为流畅。关于移动网络同步这一块内容,博主还有一些地方还没有完全梳理清楚,会在之后的时间里慢慢完善。
四.移动同步解决方案
前面关于移动逻辑的细节处理都是在PerformMovement里面实现的,我们可以把函数PerformMovement当成一个完整的移动处理流程。这个流程无论是在客户端还是在服务器都必须要执行,或者作为一个单机游戏,这一个接口基本上可以满足我们的正常移动了。不过,在网络游戏中,为了让所有的玩家体验一个几乎相同的世界,需要保证一个具有绝对权威的服务器,这个服务器可以修正客户端的不正常移动行为,保证各个客户端的一致性。相关同步的操作都是基于UCharacterMovement组件实现的,所以我们的角色必须要使用这个移动组件。
移动组件的同步全都是基于RPC不可靠传输的,你会在UCharacterMovement头文件里面看到多个以Server或者Client开头的RPC函数。
关于移动组件的同步思路,建议选阅读一下官方文档的内容,https://docs.unrealengine.com/latest/CHN/Gameplay/Networking/CharacterMovementComponent/index.html 回头看可能更为清晰一点。现在我们把整个移动细节作为一个接口封装起来,宏观的研究移动组件的同步细节。
另外,如果还没有完全搞清ROLE_Authority,ROLE_AutonomousProxy,ROLE_SimulatedProxy的概念,请参考 UE4网络同步详解(一)——理解同步规则。这里举个例子,一个服务器上有一个玩家ServerA和一个NPC ServerB,客户端上拥有从服务器复制过来的这个玩家ClientA与NPC ClientB。由于ServerA与ServerB都是在服务器上生成的,所以他们两在服务器上的所有权Role都是ROLE_Authority。ClientA在客户端上由于被玩家控制,他的Role是ROLE_AutonomousProxy。ClientB在客户端是完全通过服务器同步来控制的,他的Role就是ROLE_SimulatedProxy。
4.1 服务器角色正常的移动流程
第三章节里面的图3-1就是单机或者ListenServer服务器执行的移动流程。作为一个本地控制的角色,他只需要认真的执行正常的移动(PerformMovement)逻辑处理即可,所以ListenServer服务器移动不再赘述。
但是对于DedicateServer,他的本地没有控制的角色,对移动的处理就有差异了。分为两种情况:
- 该角色在客户端是模拟(Simulate)角色,移动完全由服务器同步过去,如各类AI角色。这类移动一般是服务器上行为树主动触发的
- 该角色在客户端是拥有自治(Autonomous)权利的Character,如玩家控制的主角。这类移动一般是客户端接收玩家输入数据本地模拟后,再通过RPC发给服务器进行模拟的
从下面的代码可以了解到这两种情况的处理(注意注释):
// UCharacterMovementComponent:: TickComponent
// simulate的角色在服务器执行IsLocallyControlled也会返回true
// Allow root motion to move characters that have no controller.
if( CharacterOwner->IsLocallyControlled() || (!CharacterOwner->Controller && bRunPhysicsWithNoController) || (!CharacterOwner->Controller && CharacterOwner->IsPlayingRootMotion()) )
{
{
SCOPE_CYCLE_COUNTER(STAT_CharUpdateAcceleration);
// We need to check the jump state before adjusting input acceleration, to minimize latency
// and to make sure acceleration respects our potentially new falling state.
CharacterOwner->CheckJumpInput(DeltaTime);
// apply input to acceleration
Acceleration = ScaleInputAcceleration(ConstrainInputAcceleration(InputVector));
AnalogInputModifier = ComputeAnalogInputModifier();
}
if (CharacterOwner->Role == ROLE_Authority)
{
// 单机或者DedicateServer控制simulate角色移动
PerformMovement(DeltaTime);
}
else if (bIsClient)
{
ReplicateMoveToServer(DeltaTime, Acceleration);
}
}
else if (CharacterOwner->GetRemoteRole() == ROLE_AutonomousProxy)
{
//DedicateServer控制自治客户端角色移动