约 22 分钟技术学习游戏引擎

Games104学习笔记(24)

GAMES104:网络游戏的进阶架构(上)

课程:GAMES104《现代游戏引擎:从入门到实践》
第 19 讲(上):网络游戏的进阶架构
本地视频时长:约 1 小时 09 分钟

一、本讲概览

网络同步的难点不只是“把状态发给客户端”,而是如何在有限带宽和不可避免的延迟下,让画面看起来连续、操作感觉及时,同时保留服务器权威。

上一讲已经建立了状态同步、客户端预测和服务器校正的基本框架。本讲进一步研究两个直接影响玩家体验的问题:

  • 角色移动复制:远端角色的状态更新频率远低于渲染频率,如何让运动保持平滑。
  • 命中判定:玩家看到的是延迟后的世界,服务器如何公平判断“这一枪是否命中”。

课程围绕插值、外推、Dead Reckoning、速度混合、碰撞问题、客户端与服务器命中判定、Hitscan、Projectile 和 Lag Compensation 展开。

二、学习目标

  • 理解网络快照、模拟状态和渲染状态之间的区别。
  • 掌握插值与外推的基本公式、用途和局限。
  • 理解 Dead Reckoning 如何根据速度和加速度预测远端对象。
  • 掌握新快照到达后的误差修正与速度混合思想。
  • 理解预测对象与真实物理世界发生碰撞时为何容易产生异常。
  • 区分客户端命中检测和服务器命中检测。
  • 区分 Hitscan 武器和 Projectile 武器的网络语义。
  • 理解服务器状态回溯式延迟补偿的完整过程。
  • 认识掩体后中弹、探头优势等无法被算法完全消除的取舍。

三、网络角色为什么会抖动

服务器通常不会以渲染帧率发送角色状态。例如:

  • 服务器每秒发送 10 到 30 个状态快照。
  • 客户端可能每秒渲染 60 到 144 帧。
  • 快照还会受延迟、抖动、丢包和乱序影响。

如果客户端每收到一个快照就把角色瞬移到新位置,远端对象会表现为:

  • 停顿后跳跃。
  • 速度忽快忽慢。
  • 转向不连续。
  • 丢包时长时间静止。

因此,网络状态不能直接等同于最终渲染状态。客户端需要在稀疏快照之间构造平滑运动。

四、角色状态复制的三种时间

网络角色通常同时涉及三条时间线:

时间 含义
服务器模拟时间 权威游戏世界实际推进到的位置
客户端接收时间 快照经过网络后抵达客户端的时间
客户端渲染时间 客户端真正向玩家显示的时间

渲染时间可以落后服务器一小段,以保证手中同时拥有两个已知快照;也可以尝试预测服务器当前或未来状态。

由此产生两类基本技术:

  • 插值:在两个已知状态之间计算中间状态。
  • 外推:根据旧状态预测尚未收到的新状态。

五、插值

1. 基本思想

插值不猜测未知未来,而是在两个已经收到的快照之间生成中间状态。

设两个快照为:

$$ S_A = (t_A, \mathbf{p}_A) $$

$$ S_B = (t_B, \mathbf{p}_B) $$

渲染时刻 $t$ 满足 $t_A \leq t \leq t_B$,插值参数为:

$$ \alpha

\frac{t-t_A}{t_B-t_A} $$

线性位置插值为:

$$ \mathbf{p}(t)

(1-\alpha)\mathbf{p}_A +\alpha\mathbf{p}_B $$

也可写为:

$$ \mathbf{p}(t)

\mathbf{p}_A +\alpha (\mathbf{p}_B-\mathbf{p}_A) $$

2. 插值缓冲

为了确保 $S_B$ 已经到达,客户端故意把远端对象显示在过去:

$$ t_{\text{render}}

t_{\text{local}}-T_{\text{buffer}} $$

$T_{\text{buffer}}$ 越大,越能吸收抖动和丢包;但远端角色在画面中也越落后于服务器真实状态。

3. 旋转与其他属性

位置可以线性插值,旋转通常使用四元数球面插值或归一化线性插值。动画参数、速度、生命值和姿态也需要按各自语义处理。

离散事件不能简单插值。例如开枪、跳跃开始或死亡必须以事件语义触发。

4. 优点

  • 不会基于错误趋势持续预测。
  • 运动稳定,突变较少。
  • 对高加速度和频繁转向更可靠。
  • 适合 FPS、MOBA 等方向变化快的角色。

5. 缺点

  • 必然增加一段显示延迟。
  • 缓冲耗尽时仍会卡顿。
  • 玩家看到的是远端对象的过去位置。
  • 后续命中判定必须考虑这段插值偏移。

六、外推

1. 基本思想

外推根据最后一个已知状态预测未来。若已知位置 $\mathbf{p}_0$、速度 $\mathbf{v}_0$ 和加速度 $\mathbf{a}_0$,经过 $\Delta t$ 后可估计:

$$ \hat{\mathbf{p}}(t)

\mathbf{p}_0 +\mathbf{v}_0\Delta t +\frac{1}{2} \mathbf{a}_0\Delta t^2 $$

速度估计为:

$$ \hat{\mathbf{v}}(t)

\mathbf{v}_0 +\mathbf{a}_0\Delta t $$

2. 优点

  • 不必故意落后于服务器。
  • 网络更新之间仍可持续运动。
  • 对速度稳定、轨迹可预测的对象效果好。
  • 可降低低频同步造成的停顿感。

3. 缺点

外推依赖运动趋势保持不变。一旦角色突然:

  • 转向。
  • 急停。
  • 跳跃。
  • 受到碰撞。
  • 被控制技能打断。

预测就会偏离真实状态。预测时间越长,误差通常越大。

4. 适用场景

外推更适合:

  • 车辆、飞机和船只。
  • 惯性较强、轨迹连续的对象。
  • 加速度变化较慢的运动。

高机动角色通常更适合插值,或只进行非常短时间的受限外推。

七、插值与外推对比

维度 插值 外推
使用的数据 两个已知快照 最后一个已知快照及运动量
时间位置 显示过去 预测现在或未来
平滑性 通常稳定 预测正确时平滑
固有延迟 较高 较低
误差风险 较低 随预测时间增加
适合对象 高频转向角色 惯性明显的载具
丢包表现 缓冲足够时稳定 可短时继续运动

实际系统常混合使用:优先插值,缓冲暂时断裂时进行短时外推,超过上限后冻结或使用其他降级策略。

八、Dead Reckoning

1. 定义

Dead Reckoning 根据最近收到的位置、速度、加速度和方向,估计对象当前应处于的位置。

它不仅是一条运动公式,还包含完整策略:

  • 何时发送新状态。
  • 客户端如何预测。
  • 误差多大时开始修正。
  • 修正持续多久。
  • 哪些状态变化必须立即同步。

2. 基于误差的更新

服务器或发送方可以预测接收方当前大概显示的位置。当真实状态与预测状态的误差超过阈值时,发送新快照:

$$ \left| \mathbf{p}_{\text{real}}

\mathbf{p}_{\text{predicted}} \right|

\varepsilon_p $$

旋转和速度也可设置独立阈值。这样可以避免按照固定高频率发送所有对象。

3. 误差阈值的取舍

  • 阈值小:更准确,但带宽更高。
  • 阈值大:带宽更低,但纠正更明显。
  • 高速或近距离对象应使用更严格阈值。
  • 远处或不重要对象可以降低更新精度。

九、新快照到达后的修正

1. 立即跳转

最简单的方法是直接把预测对象设置到权威位置。其优点是逻辑误差立刻消失,缺点是画面会发生 Warp 或 Snap。

2. 平滑位置修正

可以把当前显示位置 $\mathbf{p}_c$ 平滑拉向权威位置 $\mathbf{p}_s$:

$$ \mathbf{p}_{\text{display}}

\operatorname{Lerp} (\mathbf{p}_c, \mathbf{p}_s, \beta) $$

小误差适合平滑修正;大误差或非法位置则应立即校正,避免对象继续穿墙或影响 Gameplay。

3. 速度修正

只修改位置会让速度突然变化。更自然的方案是在一段时间内混合旧速度和新快照推导的速度。

设修正开始于 $t_0$,计划在 $t_B$ 完成,则:

$$ \lambda

\frac{t-t_0}{t_B-t_0} $$

混合速度可表示为:

$$ \mathbf{v}_t

\mathbf{v}_0 +\lambda (\mathbf{v}_0’-\mathbf{v}_0) $$

其中 $\mathbf{v}_0$ 是当前预测速度,$\mathbf{v}_0’$ 是根据新快照得到的目标速度。

用混合速度继续投影:

$$ \mathbf{p}_t

\mathbf{p}_0 +\mathbf{v}_t\Delta t +\frac{1}{2} \mathbf{a}_0’\Delta t^2 $$

再将投影位置与目标轨迹进行平滑组合:

$$ \mathbf{p}_d

\mathbf{p}_t +\lambda (\mathbf{p}_t’-\mathbf{p}_t) $$

这种 Projective Velocity Blending 同时调整位置和运动趋势,可减少明显跳变。

十、修正系统需要区分逻辑与表现

网络复制常至少维护两种状态:

  • 逻辑状态:参与碰撞、判定和 Gameplay。
  • 表现状态:用于画面平滑。

权威修正应先让逻辑状态回到合法位置;表现状态可以在短时间内跟随逻辑状态。

若把一个平滑中的表现对象直接作为真实刚体参与模拟,可能产生:

  • 重复碰撞。
  • 预测副本把本地权威对象推开。
  • 修正速度被物理系统放大。
  • 刚体能量异常。
  • 来回振荡。

因此,远端复制对象常使用运动学刚体、查询专用碰撞体或简化碰撞,而不是与本地权威刚体进行完全对称的动态物理交互。

十一、碰撞与复制对象

1. Master 与 Replica

可以把权威模拟中的对象视为 Master,把客户端上的网络副本视为 Replica。

Replica 的目标是展示 Master,而不是独立改变 Master 的真实轨迹。如果双方都按完整动力学相互作用,就可能形成反馈环:

  1. Replica 预测到错误位置。
  2. Replica 与其他对象发生碰撞。
  3. 新快照要求 Replica 回到正确位置。
  4. 高速修正又产生更大碰撞冲量。

2. 常见处理

  • 远端副本使用 Kinematic 模式。
  • 表现碰撞与 Gameplay 碰撞分层。
  • 只让权威端决定碰撞结果。
  • 客户端只做无副作用的查询。
  • 对大误差直接传送,不通过物理速度拉回。
  • 将网络修正从物理解算中隔离。

十二、更新频率与带宽分配

并非所有对象都应使用同一复制频率。可根据:

  • 与玩家距离。
  • 屏幕占比。
  • 相对速度。
  • 是否处于战斗。
  • 是否被瞄准。
  • 对 Gameplay 的重要性。

调整快照频率和精度。

一个常见思路是:高价值、近距离、高速对象高频同步;远距离、静止或不可见对象低频同步。插值与 Dead Reckoning 用来填补低频更新之间的空隙。

十三、从移动复制到命中判定

移动复制让画面中的敌人看起来平滑,但也制造了一个重要事实:

玩家瞄准的敌人,往往是服务器过去某个时刻的敌人。

延迟来源包括:

  • 敌方状态从服务器到本地的传输时间。
  • 客户端插值缓冲。
  • 本地输入传到服务器的时间。
  • 服务器处理和逻辑帧等待。

如果服务器只在“当前世界”检查射击,玩家明明瞄准了画面中的敌人,却可能被判未命中。

十四、命中判定

Hit Registration 的目标是在多个参与者之间形成关于“是否命中”的权威结论。

系统需要回答:

  • 射击发生在什么时间。
  • 射手当时看到什么。
  • 目标在服务器当时或现在在哪里。
  • 武器是瞬时射线还是飞行投射物。
  • 哪个节点执行碰撞检测。
  • 哪个节点有权修改生命值。

命中判定不是单纯的几何问题,而是几何、时间、网络和安全共同决定的问题。

十五、客户端命中检测

1. 流程

客户端在玩家按下射击时,直接使用本地画面中的目标位置做碰撞检测,并向服务器报告命中结果。

2. 优点

  • 响应立即。
  • 命中结果与玩家画面高度一致。
  • 服务器碰撞检测压力较低。
  • 容易提供像素级视觉反馈。

3. 风险

客户端不可信。攻击者可以:

  • 伪造命中消息。
  • 修改弹药数量。
  • 篡改射速。
  • 关闭后坐力。
  • 使用延迟开关。
  • 报告超出视野或射程的目标。

因此,客户端可以预测命中特效,但通常不应独自决定永久伤害。

十六、服务器命中检测

1. 权威流程

客户端发送射击命令,服务器在权威世界中进行碰撞检测并决定结果。

优点:

  • 客户端不能直接伪造最终伤害。
  • 规则和碰撞环境统一。
  • 更容易审计和回放。

缺点:

  • 服务器承担更多计算。
  • 若直接使用收到命令时的当前状态,高延迟玩家会频繁打不中。
  • 玩家看到的目标位置与服务器当前目标位置不同。

因此,权威判定还需要时间补偿。

十七、Hitscan 与 Projectile

1. Hitscan

Hitscan 武器在射击瞬间发出射线或形状查询:

$$ \mathbf{r}(u)

\mathbf{o} +u\mathbf{d}, \qquad u \geq 0 $$

其中 $\mathbf{o}$ 是射线起点,$\mathbf{d}$ 是方向。命中结果几乎在同一逻辑时刻产生。

这类武器通常直接使用状态回溯检查目标历史 Hitbox。

2. Projectile

Projectile 是具有飞行时间的实体,可受速度、重力和碰撞影响:

$$ \mathbf{p}(t)

\mathbf{p}_0 +\mathbf{v}_0t +\frac{1}{2} \mathbf{g}t^2 $$

投射物的网络处理更复杂:

  • 何时生成。
  • 谁负责模拟。
  • 如何复制轨迹。
  • 是否允许客户端先显示。
  • 命中时使用哪一时刻的世界。

3. 不能统一处理

瞬时射线、慢速火箭、抛物线手雷和范围技能具有不同时间语义,应分别设计同步与判定策略。

十八、服务器回溯式延迟补偿

1. 核心思想

服务器保留最近一段时间内的角色碰撞状态。当收到带时间戳的射击命令时:

  1. 验证命令和时间戳是否合法。
  2. 估计玩家实际射击时看到的服务器时间。
  3. 从历史缓冲中恢复目标 Hitbox。
  4. 在历史状态中执行射线或碰撞检测。
  5. 得出权威命中结果。
  6. 恢复当前世界,不让回溯改变持续状态。

2. 回溯时间

课件给出的估计关系是:

$$ T_{\text{rewind}}

T_{\text{server-now}}

T_{\text{packet-latency}}

T_{\text{interpolation-offset}} $$

其中:

  • $T_{\text{packet-latency}}$ 是射击命令从客户端到服务器的估计单程延迟。
  • $T_{\text{interpolation-offset}}$ 是客户端显示远端对象时主动落后的时间。

这两项都必须被补偿,否则服务器恢复的时间会比玩家实际看到的状态更晚。

3. 历史状态缓冲

服务器可按逻辑帧保存:

  • 角色位置与旋转。
  • Hitbox 姿态。
  • 生死状态。
  • 关键遮挡物状态。
  • 碰撞层和必要动画骨骼。

不必保存完整世界,只需保存重建命中查询所需的数据。

4. 时间插值

射击时间往往落在两个历史样本之间。服务器可对历史 Hitbox 做插值:

$$ \alpha

\frac{T_{\text{shot}}-T_i} {T_{i+1}-T_i} $$

$$ \mathbf{p}_{\text{hitbox}}

\operatorname{Lerp} (\mathbf{p}i,\mathbf{p}{i+1},\alpha) $$

这样无需为每个可能时刻保存状态。

十九、延迟补偿的安全边界

服务器不能无条件相信客户端声称的时间,否则高延迟或恶意客户端可以要求回溯到很久以前。

应验证:

  • 时间戳是否落在允许窗口内。
  • 射速是否合理。
  • 弹药和冷却是否有效。
  • 射击起点是否接近权威角色位置。
  • 瞄准方向变化是否可能。
  • 目标是否处于可交互范围。
  • 客户端 RTT 估计是否异常。

通常会设置最大回溯窗口:

$$ 0 \leq T_{\text{server-now}}-T_{\text{rewind}} \leq T_{\max} $$

超过窗口的请求可被拒绝、截断到最早可用状态或按当前状态判定。

二十、掩体后中弹

回溯保证的是射手当时所见画面的合理性,不保证目标当前画面的直觉。

可能出现:

  1. 目标已经躲到墙后。
  2. 射手在较旧画面中仍看到目标。
  3. 射手开枪,命令随后到达服务器。
  4. 服务器回溯到目标尚未进掩体的时刻。
  5. 服务器判定命中。

于是目标感觉自己“已经躲好却仍然中弹”。

这是两种玩家体验之间的冲突:

  • 不回溯:射手觉得准星对准却打不中。
  • 完全回溯:目标觉得进入掩体后仍被击中。

系统只能通过限制补偿窗口、匹配相近延迟玩家和设计武器规则来平衡,无法同时满足所有观察者。

二十一、探头优势

Peeker’s Advantage 指主动从掩体后探出的玩家,可能比防守方更早看到并攻击对手。

原因包括:

  • 探头者本地立即看到自己的移动。
  • 防守方要等待探头者状态经过服务器和网络抵达。
  • 防守方还可能使用插值缓冲。
  • 防守方的反应再返回服务器又需要时间。

较高 RTT、较大插值缓冲和较低状态更新率都会扩大这种优势。

缓解方式包括:

  • 提高服务器 Tick Rate。
  • 优化状态优先级和传输路径。
  • 缩短合理范围内的插值延迟。
  • 限制极高延迟玩家的补偿。
  • 在匹配系统中考虑网络质量。

二十二、本地预测特效与权威结果

为了让射击手感及时,客户端可以在本地先显示:

  • 枪口火焰。
  • 弹道曳光。
  • 击中火花。
  • 血花。
  • 临时命中标记。
  • 后坐力和声音。

但永久结果必须等待服务器确认:

  • 扣除目标生命值。
  • 击杀。
  • 记分。
  • 掉落。
  • 任务进度。

如果服务器否定预测,客户端应撤销或淡化临时反馈。预测层负责即时感受,权威层负责真实规则。

二十三、移动复制与命中判定的统一视角

两类问题都在处理“本地体验与权威事实之间的时间差”:

问题 本地处理 权威处理
本地角色移动 客户端预测 服务器校正
远端角色显示 插值或短时外推 服务器快照
射击反馈 本地特效或预测 Hit 服务器命中判定
延迟命中 玩家瞄准旧画面 服务器回溯历史状态

共同原则是:

  • 表现可以预测。
  • 规则必须验证。
  • 误差必须可修正。
  • 历史必须可追踪。

二十四、常见失败模式

1. 把网络位置直接写进渲染对象

结果:对象跳跃、抖动,丢包时冻结。

改进:建立快照缓冲,在网络状态与渲染状态之间插值。

2. 无限外推

结果:对象沿错误方向持续移动,随后发生巨大拉回。

改进:限制外推时间,误差过大时降级或冻结。

3. 所有误差都缓慢修正

结果:对象可能长时间处于非法位置并影响 Gameplay。

改进:按误差和语义分类,小误差平滑,大误差立即校正。

4. 远端副本参与完整动力学

结果:预测误差通过碰撞反作用于本地权威对象。

改进:区分查询碰撞、表现碰撞和权威物理碰撞。

5. 完全信任客户端命中

结果:伪造命中、无限弹药和射速作弊。

改进:服务器验证并最终裁决。

6. 服务器只检查当前状态

结果:高延迟玩家瞄准画面中的目标却总被判未命中。

改进:保存历史 Hitbox,进行受限制的状态回溯。

7. 无限回溯

结果:高延迟玩家可对很久以前的位置造成伤害,掩体体验严重恶化。

改进:设置最大补偿窗口并验证请求时间。

二十五、调试与可观测性

移动复制应记录:

  • 快照序列号与服务器时间。
  • 客户端接收时间。
  • 插值缓冲长度。
  • 当前插值区间。
  • 外推持续时间。
  • 位置和速度误差。
  • 修正方式与修正时长。

命中判定应记录:

  • 射击命令序号。
  • 客户端时间戳。
  • 估计网络延迟。
  • 插值偏移。
  • 实际回溯时间。
  • 回溯前后的 Hitbox。
  • 射线起点、方向和命中对象。
  • 最终接受或拒绝原因。

没有这些信息,网络问题往往只能表现为“偶尔拉回”或“明明打中了却没伤害”,难以复现。

二十六、设计原则

  1. 网络状态、逻辑状态和渲染状态必须分离。
  2. 本地角色使用预测,远端角色主要使用插值。
  3. 外推只适用于短时间和可预测运动。
  4. 新快照到达后按误差大小选择平滑或立即修正。
  5. 远端复制对象不应无条件参与完整权威物理。
  6. 同步频率应按对象重要性动态分配。
  7. 客户端可以预测反馈,但服务器决定永久结果。
  8. 命中判定必须明确使用哪个时间点的世界。
  9. 延迟补偿需要同时考虑网络延迟和插值偏移。
  10. 历史回溯只恢复查询所需状态,不修改当前世界。
  11. 回溯窗口必须有限,并验证客户端时间戳。
  12. 公平性是多方体验的折中,不存在让所有观察者都满意的绝对方案。

二十七、复习要点

  • 网络快照频率低于渲染频率,直接套用快照会导致角色跳跃。
  • 插值在两个已知状态之间计算,稳定但引入显示延迟。
  • 外推预测未知未来,响应快但误差会随时间增长。
  • Dead Reckoning 根据位置、速度和加速度预测,并按误差决定何时更新。
  • 速度与位置混合可以减轻新快照到达时的突变。
  • 表现修正必须与权威逻辑和物理碰撞隔离。
  • 客户端命中检测即时但不可信;服务器命中检测权威但需要补偿延迟。
  • Hitscan 是瞬时查询,Projectile 具有飞行时间和轨迹。
  • 服务器回溯使用历史 Hitbox,在玩家实际看到的时间点重新做命中检测。
  • 回溯时间需要扣除单程网络延迟和客户端插值偏移。
  • 延迟补偿会产生掩体后中弹和探头优势等体验冲突。
  • 本地特效可以预测,生命值和击杀等永久结果必须由服务器确认。

二十八、参考资料