笃行
首页
个人 & 心法
互联网/硬件后台
游戏基础架构
UE 引擎
游戏业务
AI / 大模型
数据结构与算法
机器学习数学
通用基础
GitHub
首页
个人 & 心法
互联网/硬件后台
游戏基础架构
UE 引擎
游戏业务
AI / 大模型
数据结构与算法
机器学习数学
通用基础
GitHub
  • 引擎骨架

    • UE 引擎(客户端—服务器交互)
    • 引擎架构与一帧的时序
    • UObject / GC / 反射
    • Gameplay Framework 与端存在性矩阵
  • 复制栈底层

    • NetDriver / Channel / Bunch
    • 属性复制
    • RPC
  • 带宽与规模

    • Relevancy 与带宽预算
    • UE5 Iris 复制系统
  • 局内同步

    • 移动预测与回滚
    • 延迟补偿
    • 网络物理同步
  • 系统与运维

    • GAS 网络模型
    • 专用服务器 · Session · Travel
    • 服务器权威与反作弊

移动预测与回滚(CMC)

「网络有延迟,玩家按下 W 却要等服务器确认才动,操作感就毁了。」UE 的 CharacterMovementComponent(CMC)用一套本地预测 + 服务器复算 + 回滚重放的闭环解决这个矛盾:本地立刻动、服务器权威纠错、错了平滑拉回。这套是所有联机动作游戏手感的地基,也是面试最爱挖的深水区。


一句话结论

本地立即执行并缓存 move,上报服务器复算,服务器只在偏差超阈值时纠正,客户端回滚到纠正点重放未确认的 move。

场景问题

打个比方:你在餐厅点菜,可以选「点一道等厨房确认一道」(等服务器→卡顿),也可以「按记忆中的菜单先自己吃着,厨房账单来了再对账」(本地预测)。CMC 选后者:你按 W 角色立刻走(本地预测),同时把「我从第 100 帧开始按 W 走了 3 帧」记进小本本(SavedMove 队列)发给服务器;服务器按同样规则复算,如果结果和你自己走的差不多就点头,差太多就发回「你其实该在这个位置」(纠正),你回到那个位置,把小本本里还没对账的操作重新走一遍(回滚重放)——因为在等对账期间你可能又按了 A、按了空格。

实现方案

1. 完整闭环

  • 本地立即执行:AutonomousProxy 上输入直接驱动移动,0 延迟手感。
  • FSavedMove_Character:每步移动存一份(输入、Δ时间、结果位置、时间戳),进队列等服务器确认。
  • ServerMove(Server RPC):把 move 上报。服务器是权威,用相同的移动代码复算。
  • 确认或纠正:服务器复算结果与客户端上报比较,偏差在 MaxPositionError 内就确认(客户端丢弃已确认 move);超出就 ClientAdjustPosition 下发权威位置。
  • 回滚重放:客户端收到纠正,把位置设回权威值,然后重新执行队列里所有尚未确认的 move(因为这些操作服务器还没见过,得在纠正后的基础上重新算),得到新的当前位置。

2. 关键函数与参数

  • ReplicateMoveToServer:客户端打包 move 发给服务器。
  • ServerMoveHandleClientError:服务器端比较、判断是否要纠正的核心。
  • ClientUpdatePositionAfterServerUpdate:客户端收到纠正后触发回滚重放。
  • MaxPositionError(如 NetworkMaxSmoothUpdateDistance 等相关阈值):偏差容忍度,太小会频繁纠正(抖动),太大会给作弊留空间。
  • NetworkSmoothingMode:SimulatedProxy 表现平滑模式——Disabled(直接跳)/Linear(线性插值)/Exponential(指数逼近,默认,最平滑)。

3. 三种角色的表现差异

角色是谁怎么动
Authority(服务器)权威复算 move,结果是真相
AutonomousProxy(本人客户端)我操控的角色本地预测 + 回滚重放,0 延迟手感
SimulatedProxy(别人客户端)别人的角色收复制的位置/速度,做插值/外推平滑(SmoothClientPosition),不预测

关键区别:只有 AutonomousProxy 做预测回滚,SimulatedProxy 只做插值。你看别人是「过去的平滑回放」,看自己是「即时预测」。这也是 COND_AutonomousOnly/COND_SimulatedOnly 存在的原因,见 属性复制。

4. 移动包带宽优化

移动上报是最高频的网络流量(每帧或近每帧),必须省:

  • CombineMove:把连续的、方向一致的小 move 合并成一个上报,减少包数。
  • ServerMovePacked(UE5):位打包压缩 move 数据(输入、时间戳、位置),比逐字段发省很多。UE4.26+ 起逐步引入 packed movement RPC。
  • 上报频率与时间戳告警:客户端上报带时间戳,服务器检测时间戳异常(跳变、过快过慢)——既是防作弊(加速外挂),也是异常连接告警。

5. 症状—根因对照表

症状根因排查方向
橡皮筋(走回去)服务器频繁纠正,客户端预测和服务器复算长期不一致移动逻辑客户端/服务器路径不一致;MaxPositionError 太小;网络丢包高
抖动/顿挫SimulatedProxy 插值不平滑,或纠正太频繁检查 NetworkSmoothingMode;NetUpdateFrequency 太低;纠正阈值太敏感
穿墙/瞬移预测与权威差异被强行拉回,或碰撞在两端不一致客户端/服务器碰撞体不一致;重放逻辑有副作用

自定义移动的铁律

任何自定义移动(钩爪、冲刺、飞行)都必须保证客户端预测路径和服务器复算路径完全一致——同样的输入、同样的代码、同样的结果。路径不一致 = 服务器每帧都纠正 = 橡皮筋。这是自定义 CMC 最常见的翻车点:只写了客户端表现,没走 SavedMove/复算那套,或两端逻辑有分支差异。

6. 一句话记住这几组容易混的概念

一组差别一句话类比
预测 vs 插值预测是「我自己的角色,先动再对账」;插值是「别人的角色,把收到的几个点连成平滑轨迹」自己先吃着 vs 把邮寄来的几张快照连成动画
纠正 vs 回滚重放纠正是服务器说「你该在这」;回滚重放是回到那个位置后把还没对账的操作重走一遍改掉账面数字 vs 从那笔账起把后面几笔重记
橡皮筋 vs 抖动橡皮筋是位置被反复拉回(两端逻辑不一致);抖动是画面不平滑(插值/更新频率问题)走两步被拽回去 vs 走得一格一格跳
预测 vs 延迟补偿预测解决「我的动作要立刻响应」;补偿解决「我打别人打不打得中」先自己走 vs 回看录像判越位(见 延迟补偿)

排障口径也就清楚了:位置被拉回去 → 查两端逻辑是否一致(橡皮筋);位置对但看着卡 → 查插值与 NetUpdateFrequency(抖动)。这两个症状根因完全不同,方向搞反了会白查很久。

为什么这么做

本地预测:延迟是物理现实(光速 + 路由),等服务器确认再动必然有 RTT 的手感延迟。本地立即执行把延迟从操作感里消除——你按键角色马上动。

服务器权威复算:客户端不可信(作弊、丢包、时钟漂移)。服务器用相同逻辑复算并持有真相,才能既给手感又防作弊。

只在超阈值时纠正 + 回滚重放:大多数时候客户端预测和服务器复算一致(同样的代码同样的输入),没必要纠正。只在偏差超阈值才拉回,且回滚后重放未确认 move,保证纠正不丢失玩家在延迟窗口内的后续操作——否则你按的 A、跳跃会凭空消失。

为什么别的选择不行

  • 不预测,等服务器确认再动:每次操作都有 RTT 延迟,动作游戏手感直接死。
  • 纯客户端权威(信客户端位置):瞬移、加速、穿墙外挂全放行,见 反作弊。
  • 每次都纠正(哪怕偏差极小):频繁拉回 = 橡皮筋和抖动,且浪费带宽。
  • 纠正后不重放未确认 move:延迟窗口内玩家的后续操作全丢,表现为「回退 + 卡顿」。
  • 自定义移动两端逻辑不一致:服务器复算结果永远和客户端不同,持续纠正=永久橡皮筋。

沉淀结论

CMC 闭环:本地立即执行 → SavedMove 入队 → ServerMove 上报 → 服务器相同逻辑复算 → 偏差超 MaxPositionError 才 ClientAdjustPosition → 客户端回滚到权威位置并重放未确认 move。三角色:Authority 复算权威、AutonomousProxy 预测回滚、SimulatedProxy 插值。移动包靠 CombineMove/packed 省带宽。铁律:自定义移动两端逻辑必须一致,否则橡皮筋。

记忆口诀

闭环:本地立即动 / SavedMove 入队 / ServerMove 上报 / 服务器复算 / 超阈值纠正 / 回滚重放未确认
函数:ReplicateMoveToServer / ServerMoveHandleClientError / ClientUpdatePositionAfterServerUpdate / MaxPositionError
三角色:Authority 权威复算 / AutonomousProxy 预测回滚 / SimulatedProxy 插值(SmoothClientPosition)
省带宽:CombineMove 合并 / ServerMovePacked 位打包 / 时间戳防加速外挂
症状:橡皮筋=两端不一致 / 抖动=插值/纠正问题 / 穿墙=碰撞不一致
铁律:自定义移动客户端服务器路径必须一致

内容来源

综合整理。主要参考:Epic 官方文档 Character Movement Component、Networked Movement、UE 源码 CharacterMovementComponent.cpp(FSavedMove_Character/ServerMove/ClientAdjustPosition)、社区经典 GDC 分享与 Cedric Neukirchen《Multiplayer Compendium》。

相关专题:AutonomousProxy/SimulatedProxy 角色定义见 Gameplay Framework;ServerMove 作为 Server RPC 的调用前提见 RPC;COND_AutonomousOnly/COND_SimulatedOnly 见 属性复制;命中判定的延迟补偿见 延迟补偿;作弊防护见 服务器权威与反作弊。

自测:合上资料能说清楚吗?

CMC 的预测—纠正—回滚闭环完整流程是什么?

参考答案

① 客户端输入本地立即移动;② 每步存入 FSavedMove_Character 队列(未确认);③ 通过 ServerMove RPC 上报(时间戳+输入+结果位置);④ 服务器用相同移动代码复算;⑤ 比较客户端结果与复算结果,偏差在 MaxPositionError 内就确认(客户端丢弃已确认 move),超出则 ClientAdjustPosition 下发权威位置;⑥ 客户端回滚到权威位置,重放队列里所有未确认 move 得到新位置。

为什么纠正后要「重放未确认 move」?

参考答案

从上报到收到纠正有一个 RTT 的延迟窗口,这期间客户端又执行了新的 move(继续走、跳、拐弯)。如果只是把位置设回服务器权威值就停,这些后续操作会凭空消失,表现为回退+卡顿。所以要在权威位置基础上把队列里所有服务器还没确认的 move 重新执行一遍,保留玩家意图。

AutonomousProxy 和 SimulatedProxy 的移动表现有什么本质区别?

参考答案

AutonomousProxy(你自己的角色)做本地预测 + 回滚重放,0 延迟手感;SimulatedProxy(别人的角色)不预测,只收复制的位置/速度做插值/外推平滑(SmoothClientPosition,NetworkSmoothingMode)。所以你看自己是即时预测,看别人是「过去的平滑回放」。

玩家反馈「橡皮筋」,怎么排查?

参考答案

橡皮筋 = 服务器频繁纠正,即客户端预测和服务器复算长期不一致。查:① 移动逻辑客户端/服务器路径是否一致(自定义移动最常见,只写了客户端表现没走复算);② 碰撞体两端是否一致;③ MaxPositionError 是否太小导致过度敏感;④ 网络丢包是否过高。铁律是自定义移动两端逻辑必须完全一致。

最近更新: 2026/9/10 11:38
Next
延迟补偿