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 的真实轨迹。如果双方都按完整动力学相互作用,就可能形成反馈环:
- Replica 预测到错误位置。
- Replica 与其他对象发生碰撞。
- 新快照要求 Replica 回到正确位置。
- 高速修正又产生更大碰撞冲量。
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. 核心思想
服务器保留最近一段时间内的角色碰撞状态。当收到带时间戳的射击命令时:
- 验证命令和时间戳是否合法。
- 估计玩家实际射击时看到的服务器时间。
- 从历史缓冲中恢复目标 Hitbox。
- 在历史状态中执行射线或碰撞检测。
- 得出权威命中结果。
- 恢复当前世界,不让回溯改变持续状态。
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} $$
超过窗口的请求可被拒绝、截断到最早可用状态或按当前状态判定。
二十、掩体后中弹
回溯保证的是射手当时所见画面的合理性,不保证目标当前画面的直觉。
可能出现:
- 目标已经躲到墙后。
- 射手在较旧画面中仍看到目标。
- 射手开枪,命令随后到达服务器。
- 服务器回溯到目标尚未进掩体的时刻。
- 服务器判定命中。
于是目标感觉自己“已经躲好却仍然中弹”。
这是两种玩家体验之间的冲突:
- 不回溯:射手觉得准星对准却打不中。
- 完全回溯:目标觉得进入掩体后仍被击中。
系统只能通过限制补偿窗口、匹配相近延迟玩家和设计武器规则来平衡,无法同时满足所有观察者。
二十一、探头优势
Peeker’s Advantage 指主动从掩体后探出的玩家,可能比防守方更早看到并攻击对手。
原因包括:
- 探头者本地立即看到自己的移动。
- 防守方要等待探头者状态经过服务器和网络抵达。
- 防守方还可能使用插值缓冲。
- 防守方的反应再返回服务器又需要时间。
较高 RTT、较大插值缓冲和较低状态更新率都会扩大这种优势。
缓解方式包括:
- 提高服务器 Tick Rate。
- 优化状态优先级和传输路径。
- 缩短合理范围内的插值延迟。
- 限制极高延迟玩家的补偿。
- 在匹配系统中考虑网络质量。
二十二、本地预测特效与权威结果
为了让射击手感及时,客户端可以在本地先显示:
- 枪口火焰。
- 弹道曳光。
- 击中火花。
- 血花。
- 临时命中标记。
- 后坐力和声音。
但永久结果必须等待服务器确认:
- 扣除目标生命值。
- 击杀。
- 记分。
- 掉落。
- 任务进度。
如果服务器否定预测,客户端应撤销或淡化临时反馈。预测层负责即时感受,权威层负责真实规则。
二十三、移动复制与命中判定的统一视角
两类问题都在处理“本地体验与权威事实之间的时间差”:
| 问题 | 本地处理 | 权威处理 |
|---|---|---|
| 本地角色移动 | 客户端预测 | 服务器校正 |
| 远端角色显示 | 插值或短时外推 | 服务器快照 |
| 射击反馈 | 本地特效或预测 Hit | 服务器命中判定 |
| 延迟命中 | 玩家瞄准旧画面 | 服务器回溯历史状态 |
共同原则是:
- 表现可以预测。
- 规则必须验证。
- 误差必须可修正。
- 历史必须可追踪。
二十四、常见失败模式
1. 把网络位置直接写进渲染对象
结果:对象跳跃、抖动,丢包时冻结。
改进:建立快照缓冲,在网络状态与渲染状态之间插值。
2. 无限外推
结果:对象沿错误方向持续移动,随后发生巨大拉回。
改进:限制外推时间,误差过大时降级或冻结。
3. 所有误差都缓慢修正
结果:对象可能长时间处于非法位置并影响 Gameplay。
改进:按误差和语义分类,小误差平滑,大误差立即校正。
4. 远端副本参与完整动力学
结果:预测误差通过碰撞反作用于本地权威对象。
改进:区分查询碰撞、表现碰撞和权威物理碰撞。
5. 完全信任客户端命中
结果:伪造命中、无限弹药和射速作弊。
改进:服务器验证并最终裁决。
6. 服务器只检查当前状态
结果:高延迟玩家瞄准画面中的目标却总被判未命中。
改进:保存历史 Hitbox,进行受限制的状态回溯。
7. 无限回溯
结果:高延迟玩家可对很久以前的位置造成伤害,掩体体验严重恶化。
改进:设置最大补偿窗口并验证请求时间。
二十五、调试与可观测性
移动复制应记录:
- 快照序列号与服务器时间。
- 客户端接收时间。
- 插值缓冲长度。
- 当前插值区间。
- 外推持续时间。
- 位置和速度误差。
- 修正方式与修正时长。
命中判定应记录:
- 射击命令序号。
- 客户端时间戳。
- 估计网络延迟。
- 插值偏移。
- 实际回溯时间。
- 回溯前后的 Hitbox。
- 射线起点、方向和命中对象。
- 最终接受或拒绝原因。
没有这些信息,网络问题往往只能表现为“偶尔拉回”或“明明打中了却没伤害”,难以复现。
二十六、设计原则
- 网络状态、逻辑状态和渲染状态必须分离。
- 本地角色使用预测,远端角色主要使用插值。
- 外推只适用于短时间和可预测运动。
- 新快照到达后按误差大小选择平滑或立即修正。
- 远端复制对象不应无条件参与完整权威物理。
- 同步频率应按对象重要性动态分配。
- 客户端可以预测反馈,但服务器决定永久结果。
- 命中判定必须明确使用哪个时间点的世界。
- 延迟补偿需要同时考虑网络延迟和插值偏移。
- 历史回溯只恢复查询所需状态,不修改当前世界。
- 回溯窗口必须有限,并验证客户端时间戳。
- 公平性是多方体验的折中,不存在让所有观察者都满意的绝对方案。
二十七、复习要点
- 网络快照频率低于渲染频率,直接套用快照会导致角色跳跃。
- 插值在两个已知状态之间计算,稳定但引入显示延迟。
- 外推预测未知未来,响应快但误差会随时间增长。
- Dead Reckoning 根据位置、速度和加速度预测,并按误差决定何时更新。
- 速度与位置混合可以减轻新快照到达时的突变。
- 表现修正必须与权威逻辑和物理碰撞隔离。
- 客户端命中检测即时但不可信;服务器命中检测权威但需要补偿延迟。
- Hitscan 是瞬时查询,Projectile 具有飞行时间和轨迹。
- 服务器回溯使用历史 Hitbox,在玩家实际看到的时间点重新做命中检测。
- 回溯时间需要扣除单程网络延迟和客户端插值偏移。
- 延迟补偿会产生掩体后中弹和探头优势等体验冲突。
- 本地特效可以预测,生命值和击杀等永久结果必须由服务器确认。