UE4源码阅读_移动同步

0. 写在前面

本文为个人学习的笔记整理,如有错误,望不吝指出。

1. 概述

UE4的移动及同步代码主体在UCharacterMovementComponent
本篇将分为客户端本地移动流程、服务端简要移动流程、以及移动同步三块进行描述。

一些重要的枚举

  1. 移动类型:ENetRole
    – ROLE_SimulatedProxy:模拟代理,即由服务端控制的本地角色,或者第三方玩家
    – ROLE_AutonomousProxy:自主代理,主客户端
    – ROLE_Authority:权威,服务端用,如NPC,怪物
  2. 模拟的插值类型:ENetworkSmoothingMode
    – Disabled
    – Linear:线性插值
    – Exponential:指数插值
    – Replay
  3. 当前游戏跑的网络模式:ENetMode
    – NM_Standalone:单机客户端
    – NM_DedicatedServer:服务端模式
    – NM_ListenServer:服务端模式
    – NM_Client:联网客户端模式
  4. RootMotion模式:ERootMotionMode
    – NoRootMotionExtraction
    – IgnoreRootMotion:提取RootMotion数据但不控制位移
    – RootMotionFromEverything:所有的RootMotion动画都会被应用且控制位移
    – RootMotionFromMontagesOnly:只有Montage下的RootMotion会被应用且控制位移
    只有RootMotionMontage适合进行网络同步

2. 客户端本地移动流程

本段描述客户端本地的移动流程,主要描述AutonomousProxy(自主代理)以及SimulatedProxy(模拟代理)的本地移动流程。
其中UCharacterMovementComponent::TickComponent是移动组件每帧迭代的入口,其根据移动类型(ENetRole)、网络模式(ENetMode)进到不同的计算逻辑。

2.1 SimulatedProxy移动流程

1.SimulatedTick:当本地角色类型(ACharacter->LocalRole)是模拟代理时,由TickComponent调用。其中逻辑主要分为4块

  • NetworkSmoothingMode == ENetworkSmoothingMode::Replay:和回放相关,暂略
  • CharacterOwner->IsPlayingNetworkedRootMotionMontage():RootMotionMontage同步,主要流程为TickCharacterPose、SimulateRootMotion、SimulatedRootMotionPositionFixup。在后面同步篇章详细描述。
  • CurrentRootMotion.HasActiveRootMotionSources:
  • 最后是常规的模拟代理的移动同步,服务端同步速度、旋转、时间戳,客户端根据这些属性进行移动模拟。根据是否包含RootMotion动画(区别RootMotionMontage),分为两个模拟流程:SimulateMovement和PerformMovement。

下面详细描述模拟代理的最后一个部分

2.SimulateMovement:

  • FScopedMovementUpdate
  • bNetworkUpdateReceived:该帧是否有收到网络更新的数据,进一步检查,是否需要切换MoveMode,或者是否有瞬移或强制检查地面的标识,需要调FindFloor重新检查地面。ApplyNetworkMovementMode/UpdateFloorFromAdjustment
  • UpdateBasedMovement
  • MoveSmooth:在没有及时收到服务器数据时尝试以当前的速度方向进行预测,判断当前是否在地面上移动,是的话调MoveAlongFloor尝试移动。否则调SafeMoveUpdatedComponent进行一系列的物理检测后移动
  • 预测后如果是还在地面移动,需要尝试寻找新的地面信息。如果是掉落(Fall)则需要处理掉落速度。
  • SaveBaseLocation、UpdateComponentVelocity
  • 最后调SmoothClientPosition,胶囊体先行,Mesh平滑插值。其中SmoothClientPosition_Interpolate负责插值数据,SmoothClientPosition_UpdateVisuals负责更新Mesh

3.PerformMovement:基于当前速度或者当前RootMotion数据提取的速度,进行一系列的移动模拟

  • FScopedMovementUpdate
  • StartNewPhysics:关键函数,根据MoveMode进行不同的物理计算。有Walking、NavWalking、Falling、Flying、Swimming等几种模式,跑各自的物理逻辑,允许在一个tick内,当移动模式发生变化时,重新进行新模式的计算,但是有个迭代次数上限MaxSimulationIterations

2.2 PhysWalking移动逻辑

StartNewPhysics中贴地移动的计算

1.PhysWalking:

  • 切时间片计算,为的是在比TickTime更小的时间间隔内计算移动逻辑。否则对细节要求比较高的情况来说,可能出现TickTime间隔过大的问题。
  • 切时间片:
    timeTick = GetSimulationTimeStep(remainingTime, Iterations);
    remainingTime -= timeTick;
    当remainingTime过小或者迭代次数过多时(达到MaxSimulationIterations)退出

该函数具体的计算逻辑如下:

  • MaintainHorizontalGroundVelocity:在上下坡时,是维持水平分量速度,还是将完整的速度映射到斜面
  • CalcVelocity(timeTick, GroundFriction, false, GetMaxBrakingDeceleration()):没有RootMotion控制时,基于当前移动模式更新速度和加速度,应用摩擦力以及加速减速效果,但不应用重力。
  • ApplyRootMotionToVelocity:尝试应用RootMotion数据到速度,有可能会导致Falling,如果导致Falling则重新跑StartNewPhysics
  • MoveAlongFloor:更新完速度后,尝试移动,做一系列的碰撞检测
  • FindFloor:通过物理(胶囊体)尝试寻找地面

2.MoveAlongFloor(MoveVelocity, timeTick, &StepDownResult):

  • ComputeGroundMovementDelta:处理斜坡时的速度方向,将水平速度投影到斜坡上,计算一个平行于斜坡的速度
  • 调SafeMoveUpdatedComponent进行Sweep检测
  • 根据碰撞结果Hit.bStartPenetrating,解决渗透问题:SlideAlongSurface
  • 如果碰撞,对碰撞点做可行走面的检查,如果是可行走面,则计算新的移动方向,重新跑SafeMoveUpdatedComponent
  • 如果碰撞,试着延着障碍物移动:SlideAlongSurface

MoveAlongFloor结束后,移动状态可能会改变,判断Falling和Swimming(是否掉落和落水),如果有改变的话重新跑StartNewPhysics

3.SafeMoveUpdatedComponent:UPrimitiveComponent::MoveComponentImpl

  • 碰撞检测:从TraceStart(Vec3)到TraceEnd(Vec3)进行Sweep检测
    MyWorld->ComponentSweepMulti(Hits, this, TraceStart, TraceEnd, InitialRotationQuat, Params)
    =>FPhysicsInterface::GeomSweepMulti,返回TArray,碰撞结果列表,存到Hits中
    =>对Hits中的每个碰撞结果执行PullBackHit:所有Hit需要拉回微小的距离(缩小hit.time),避免因为浮点数精度的问题导致跟碰撞物重叠
    =>如果Hits中有多个碰撞结果,选择切面法线和移动方向最相反的碰撞结果

  • 坐标设置:
    如果第一步检测中没有碰撞,则Location设为TraceEnd
    如果发生了碰撞,根据比例Hit.Time和检测方向,算一个挨墙的位置
    更新坐标:InternalSetWorldLocationAndRotation(NewLocation, NewRotationQuat, bSkipPhysicsMove, Teleport)
    =>NewLocation、NewRotationQuat:碰撞检测后算出来的新位置和旋转
    =>SetRelativeLocation_Direct、SetRelativeRotation_Direct,根据位置和旋转是否有变化,设置数据
    =>UpdateComponentToWorldWithParent:更新ComponentToWorld Transform矩阵(TODO)

  • 更新重叠的状态:UpdateOverlap

  • 返回检测结果Hits

4.StepUp:爬台阶的逻辑

  • 三次调用MoveUpdatedComponent(最后调的UPrimitiveComponent::MoveComponentImpl,和SafeMoveUpdatedComponent一样),尝试先向上移动,再向前移动,最后向下移动。
  • 其中每次移动计算结束,都会检查碰撞、渗透、掉落等一系列逻辑。
  • 向上如果有碰撞,直接退出StepUp,如果有渗透,执行ScopedStepUpMovement.RevertMove()回退这次移动。
    向前如果有碰撞,执行SlideAlongSurface尝试延墙移动
    向下如果有渗透,也回退。如果是碰撞,还要检查是否可行走面、边缘判断等,也要进行RevertMove

5.FindFloor:

  • 垂直向下Sweep查找地面,将地面信息整合后向外输出FFindFloorResult

6.SlideAlongSurface:

  • 原先的移动方向会碰墙,改变移动方向为贴墙走
    新的移动方向计算:SlideDelta = ComputeSlideVector(Delta, Time, Normal, Hit)
    用新的移动方向跑SafeMoveUpdatedComponent
    如果碰墙(移动方向有两面墙)则需要进行TwoWallAdjust,计算在两面墙下的新的移动方向(移动方向和两面墙的夹角相关)

7.TwoWallAdjust
8.ComputeGroundMovementDelta

2.4 AutonomousProxy移动流程

1.ReplicateMoveToServer:
主客户端在TickComponent时,通过该接口调用PerformMovement进行本地移动,并且记录移动数据,提交到服务端

  • 检查PendingMove是否有值,和NewMove是否可以合并
  • PerformMovement,执行客户端移动
  • 将NewMove加入SavedMoves,检查是否符合立即上报的条件,如果不符合,则先缓存到PendingMoves,下次再发送。如果符合,则调用CallServerMove将移动数据发送给服务端。

自主代理的移动主要也是依赖于PerformMovement,其中PendingMove、SavedMoves是和同步相关的变量,在后面详细描述。

3. 服务端简要流程

在这里插入图片描述

4. 客户端同步流程

4.1 SimulatedProxy同步

1.值复制:
属性Acharacter::ReplicatedBasedMovement被服务器修改时,会回调OnRep_ReplicatedBasedMovement进行处理

2.OnRep_ReplicatedBasedMovement:

  • IsPlayingNetworkedRootMotionMontage时直接退出不处理,走OnRep_RootMotion接口
  • 赋值BasedMovement = ReplicatedBasedMovement
  • 计算新的目标位置和旋转
    • 服务端传相对差值:
      NewLocation = CharacterMovement->OldBaseLocation + ReplicatedBasedMovement.Location
      NewRotation = (FRotationMatrix(ReplicatedBasedMovement.Rotation) * FQuatRotationMatrix(CharacterMovement->OldBaseQuat)).Rotator();
    • 绝对值:
      NewRotation = ReplicatedBasedMovement.Rotation;
  • 做位置纠正:
    CharacterMovement->SmoothCorrection(OldLocation, OldRotation, NewLocation, NewRotation.Quaternion());
    • 计算ClientData(FNetworkPredictionData_Client_Character)里的位置offset(MeshTranslationOffset)和旋转offset(MeshRotationOffset),后面在SmoothClientPosition进行Mesh插值时会用到。
    • 时间相关处理:
      SmoothingClientTimeStamp、SmoothingServerTimeStamp、LastCorrectionDelta
      也是在SmoothClientPosition中用到,SmoothingClientTimeStamp和SmoothingServerTimeStamp用来处理内插还是外插
      LastCorrectionDelta用于在内插时计算比例

4.2 RootMotionMontage同步

1.RepRootMotion和OnRep_RootMotion:

  • Acharacter->RepRootMotion:
    FRepRootMotionMontage的结构体对象,双端进行蒙太奇同步的属性,主要包含蒙太奇轨道数据,位置和旋转数据等。
  • FSimulatedRootMotionReplicatedMove:
    结构体,其中包含一个时间戳(客户端收到蒙太奇同步数据时的本地时间)和一个FRepRootMotionMontage对象
  • OnRep_RootMotion:
    蒙太奇同步属性修改时的回调函数,只有角色类型为模拟代理(SimulatedProxy)时,将本次数据加入到一个同步数据列表(RootMotionRepMoves)的末尾。
  • Acharacter- >RootMotionRepMoves:FSimulatedRootMotionReplicatedMove数据列表

2.SimulatedTick中Montage同步流程

  • TickCharacterPose:更新动画姿态,并且尝试提取RootMotion数据到位移组件中
    • 调SkeletalMeshComp->TickPose进行姿态更新,提取RootMotion数据到动画系统
    • 调SkeletalMeshComp->ConsumeRootMotion,返回一个FRootMotionMovementParams数据对象,其中存着更新动画后,动画系统提取的RootMotion的数据,并且根据角色设置的RootMotionScale(缩放数据),调整上述的RootMotion数据
  • SimulateRootMotion:
    • 将第一步提取的局部坐标下的RootMotion运动数据转换到世界坐标
    • 根据世界坐标下的RootMotion数据计算当前速度,并调用StartNewPhysics进行移动模拟
    • 注意,这里在StartNewPhysics中只会处理位移,在移动结束后才会继续调MoveUpdatedComponent处理旋转
  • SimulatedRootMotionPositionFixup:

4.3 AutonomousProxy同步

1.服务端的SendClientAdjustment通过RPC告诉客户端是否需要矫正,或者确认某次移动有效性。
如果需要矫正,客户端RPC接口都会更新ClientPredictionData 的bUpdatePosition 为True,在下一次TickComponent时会执行矫正。

2.FNetworkPredictionData_Client_Character:客户端预测数据Comp->ClientPredictionData
负责记录移动情况和处理来自服务器的矫正,在客户端执行本地移动、准备要发送到服务器的移动,以及处理矫正时,其参数将被频繁引用和更改。

  • 主要数据:
    1)各种时间戳
    2)各种移动数据列表:SavedMoves、FreeMoves、PendingMove、LastAckedMove
  • TArray SavedMoves:将客户端每次执行移动的关键数据保存下来,并且后续会上报给服务端,当服务端确认过时间戳的移动数据合法后,将该数据及之前的数据从SavedMoves移除。如果服务端需要矫正客户端移动,则会从SavedMoves中最早未被确认过的数据开始重播。
  • FSavedMovePtr PendingMove:
    1)移动系统网络带宽的一种优化,当一次NewMove结束后会判断是否立即发送给服务器(网络速度问题、发送间隔短等)。如果不立即发送,则会赋值给PendingMove,在客户端下一次Tick后,判断两次移动是否可以合并,如果可以,则只发送一次数据,如果不行,则连发两次数据(PendingMove和NewMove)
    2)PendingMove->CanCombineWith(NewMove),是否可以合并
    3)NewMove->CombineWith(PendingMove),执行合并
    4)ServerMoveDual、ServerMoveDualHybridRootMotion:一次发送两个数据
  • TArray FreeMoves:
  • FSavedMovePtr LastAckedMove:

4.ClientUpdatePositionAfterServerUpdate:

  • 在处理玩家输入之前,需要先调用该函数,检查服务器是否下发了纠正数据需要处理,有的话则优先处理
    对SavedMoves中未被服务端确认的数据,从头开始加速重播
    重播结束后,才执行当帧本该执行的移动逻辑:ReplicateMoveToServer

5. 参考资料

https://zhuanlan.zhihu.com/p/33529865
https://zhuanlan.zhihu.com/p/34257208
https://docs.unrealengine.com/4.27/zh-CN/InteractiveExperiences/Networking/CharacterMovementComponent/

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
UE4_API_Reference 是一个非常宝贵的资源,它提供了 Unreal Engine 4 引擎的全部 API 详细文档。这些离线文档对开发者来说非常重要,因为它们可以在没有网络连接的情况下提供对 UE4 引擎的完整了解和查询支持。 UE4_API_Reference 全部离线文档包括了对 UE4 的各个模块、类、函数和属性的详细描述和用法示例。开发者可以通过这些文档深入了解各个功能模块的实现原理以及如何正确使用它们。这对于开发者学习 UE4 引擎以及进行游戏开发非常有帮助。 离线文档的好处在于可以在没有网络的环境中使用。开发者不需要依赖互联网来查找关于 UE4 API 的信息,而是可以直接在本地进行搜索和查询。这不仅提高了开发效率,还可以避免由于网络问题导致的信息获取困难。 UE4_API_Reference 全部离线文档的编制工作需要花费大量的时间和精力。文档中的每一个类、函数和属性都需要进行详细描述和示例演示,以便开发者能够更好地理解和使用。这就要求文档编写人员具备广泛的知识和丰富的经验,以确保文档的完整性和准确性。 总之,UE4_API_Reference 全部离线文档对于 UE4 开发者来说是一个非常宝贵的资源。它提供了对 UE4 引擎的全面了解和查询支持,可以帮助开发者更好地学习和使用 UE4 引擎进行游戏开发。同时,它也方便了开发者在没有网络连接的环境中进行开发工作,提高了开发效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值