Games104学习笔记(23)
GAMES104:网络游戏的架构基础(下)
课程:GAMES104《现代游戏引擎:从入门到实践》
第 18 讲(下):网络游戏的架构基础
本地视频时长:约 1 小时 20 分钟
一、本讲概览
上一部分解决的是“数据如何通过网络传输”:TCP、UDP、可靠 UDP、时钟同步和 RPC。本部分进一步回答“多台机器如何共同运行同一场游戏”。
网络同步需要同时处理两个相互冲突的目标:
- 一致性:不同参与者最终看到兼容的游戏世界。
- 响应性:玩家输入后尽快看到反馈,不能一直等待网络。
课程从网络拓扑开始,重点比较三类同步思想:
- 快照同步:服务器发送完整或压缩后的世界状态。
- 确定性帧同步:各端接收相同输入,并独立运行相同模拟。
- 状态同步:权威服务器运行游戏逻辑,只复制客户端关心的状态。
后半部分继续讨论确定性、逻辑帧、断线重连、回放、观战、防作弊、客户端预测和服务器校正。
二、学习目标
- 区分原始 P2P、主机服务器和专用服务器。
- 理解快照同步、帧同步和状态同步各自传输什么。
- 掌握确定性帧同步对初始状态、输入顺序和模拟过程的要求。
- 理解浮点数、随机数、容器遍历和物理模拟为何会破坏确定性。
- 理解逻辑帧与渲染帧分离的价值。
- 掌握断线重连、回放和观战的共同基础。
- 理解权威服务器、客户端预测和服务器校正的协作方式。
- 能根据游戏规模、玩法和安全需求选择同步方案。
三、网络拓扑
1. 原始 P2P
Peer-to-Peer 架构中,每个玩家节点直接向其他节点广播游戏事件。若有 $N$ 个节点,并且每个节点都需要与其余节点通信,连接数量近似为:
$$ \frac{N(N-1)}{2} $$
其特点是:
- 不依赖中心服务器。
- 单个节点退出不一定让整局立即停止。
- 每个节点都要维护其他玩家的数据。
- 玩家数量增加时,连接和带宽开销快速增长。
- 缺少可信权威节点,作弊和一致性处理更困难。
- NAT、防火墙和公网寻址会增加连接复杂度。
原始 P2P 更适合参与者较少、信任关系较强或局域网环境中的游戏。
2. 主机服务器
主机服务器模式让某个玩家设备兼任服务器。其他客户端只与主机通信。
优点:
- 比全互联 P2P 更容易管理。
- 不需要为每局游戏持续提供专用服务器。
- 主机可以统一分发输入或状态。
问题:
- 主机具有延迟优势。
- 主机性能和上行带宽决定整局质量。
- 主机掉线可能导致游戏结束。
- 若支持主机迁移,需要选举新主机并恢复权威状态。
- 主机仍由玩家控制,难以作为完全可信的安全边界。
3. 专用服务器
专用服务器由运营方部署,主要职责包括:
- 运行或验证权威游戏逻辑。
- 维护房间和玩家连接。
- 汇总输入。
- 生成游戏状态。
- 将同步数据分发给客户端。
专用服务器没有本地玩家,因此不会天然偏向某一参与者,也更适合反作弊和持续运营。
代价是:
- 需要服务器算力和带宽。
- 需要部署、监控、扩缩容和故障恢复。
- 服务器位置会影响不同地区玩家的 RTT。
4. 边缘接入
当玩家与中心服务器相距很远时,可通过边缘网关和专线优化路径:
- 玩家先接入附近的边缘节点。
- 边缘节点通过质量更稳定的骨干网络连接中心服务。
- 接入层还可承担加密、连接保持、流量清洗和协议转发。
边缘节点不能突破光速限制,但可以减少公网绕路和不稳定链路带来的额外延迟。
四、同步问题的本质
单机游戏的典型循环是:
$$ S_{t+1}
F(S_t, I_t) $$
其中 $S_t$ 是当前状态,$I_t$ 是本地输入,$F$ 是游戏逻辑。
网络游戏有多个参与者,每台机器拿到输入和状态的时间不同。同步系统需要决定:
- 游戏逻辑在哪里运行。
- 网络传输输入、事件还是状态。
- 哪个节点拥有最终裁决权。
- 如何处理延迟、丢包和乱序。
- 客户端何时可以先行显示结果。
- 发生分歧时如何恢复。
同步不是简单复制对象,而是对权威、时间和数据语义的整体设计。
五、快照同步
1. 基本流程
快照同步把某个时刻的游戏世界状态序列化为快照:
- 客户端向服务器发送输入。
- 服务器运行游戏模拟。
- 服务器生成完整世界状态。
- 服务器向客户端发送状态快照。
- 客户端根据快照更新显示。
客户端不必复现完整模拟,只需展示服务器提供的结果。
2. 优点
- 实现概念直接。
- 客户端不需要完全确定性的逻辑。
- 服务器拥有明确权威。
- 不同平台可以使用不同内部实现。
- 客户端难以伪造最终状态。
3. 成本
完整世界快照可能非常大:
- 对象越多,单帧数据越大。
- 更新频率越高,带宽越高。
- 服务器需要为多个客户端生成和发送数据。
- 客户端本地算力可能没有充分利用。
若每次快照大小为 $B$,发送频率为 $f$,玩家数为 $N$,不考虑协议头和重传时,服务器下行带宽约为:
$$ \text{Bandwidth} \approx BfN $$
因此,实际系统很少持续发送整个世界的完整状态。
六、增量压缩
1. 只发送变化
相邻快照通常只有少量字段改变。增量压缩以一个双方都确认的基准快照 $S_b$ 为参考,只发送当前状态 $S_t$ 与基准之间的差异:
$$ \Delta S_t
\operatorname{Diff}(S_t, S_b) $$
客户端使用基准和增量重建新状态:
$$ S_t
\operatorname{Apply}(S_b, \Delta S_t) $$
差异可以表示为:
- 发生变化的实体集合。
- 每个实体发生变化的组件。
- 组件内部发生变化的字段。
- 新增和删除的对象。
2. 基准快照
服务器必须知道客户端已经拥有哪个基准。若基准包丢失,后续增量可能无法解析。
常见处理包括:
- 客户端确认已收到的快照编号。
- 服务器相对最近确认快照生成增量。
- 必要时重新发送完整关键快照。
增量压缩显著降低带宽,但增加了快照历史、确认和恢复逻辑。
七、确定性帧同步
1. 核心思想
帧同步不持续传输整个世界状态,而是同步每个逻辑帧中的玩家命令。所有节点从相同初始状态出发,按相同顺序执行相同输入:
$$ S_{t+1}^{(i)}
F(S_t^{(i)}, C_t) $$
若所有节点满足:
$$ S_0^{(1)}
S_0^{(2)}
\cdots
S_0 $$
并且模拟函数 $F$ 完全确定,那么:
$$ S_t^{(1)}
S_t^{(2)}
\cdots $$
也就是说,网络只需同步命令,各端就能独立得到相同世界状态。
2. 来源
传统 Lockstep 的思想可见于棋类和牌类游戏:所有参与者必须看到同一串、同一顺序的动作后,才能推进状态。
对电子游戏而言,关键要求是:
- 命令必须可靠到达。
- 所有节点必须使用相同顺序。
- 同一逻辑帧的输入集合必须一致。
- 不能让某个节点独自跳过尚未确认的步骤。
八、严格 Lockstep 的延迟问题
最严格的 Lockstep 要等所有玩家提交并确认本轮命令,才能推进下一轮。
其结果是:
- 游戏速度取决于最慢玩家。
- 某个玩家网络波动会拖慢所有人。
- 掉线可能让整局停住。
- 延迟波动会直接变成游戏节奏波动。
这种方式保证清晰的一致性,却不适合需要持续、即时操作的游戏。
九、Bucket 同步
1. 固定时间桶
Bucket 同步把时间划分为固定长度的逻辑区间,例如每个 Bucket 持续 $100\ \text{ms}$。服务器收集一个时间桶内的所有玩家命令,再将命令集合广播给参与者。
可以写为:
$$ C_k
\bigcup_{i=1}^{N} C_{k}^{(i)} $$
其中 $C_k^{(i)}$ 表示玩家 $i$ 在第 $k$ 个时间桶内提交的命令。
2. 相对严格等待的改进
服务器不必等待每个客户端都显式完成当前回合才转发,而是按固定节奏封装命令。这样:
- 网络抖动可被输入缓冲吸收。
- 模拟以稳定逻辑帧推进。
- 命令仍能按统一帧号和顺序执行。
但缓冲区越大,输入延迟越高;缓冲区越小,对抖动越敏感。
十、确定性的必要条件
1. 初始状态一致
开始模拟前,各端必须拥有完全相同的数据:
- 地图和关卡。
- 单位属性与规则配置。
- 初始对象状态。
- 随机种子。
- 游戏版本与资源版本。
- 逻辑时钟和起始帧。
任何静态数据差异都会在后续模拟中被放大。
2. 输入一致
各端必须执行:
- 相同命令。
- 相同帧号。
- 相同命令顺序。
- 相同缺省输入规则。
即使两个命令在业务上看似同时发生,执行先后也可能改变碰撞、伤害或资源分配结果。
3. 模拟过程一致
相同输入必须产生相同输出。风险来源包括:
- 浮点计算。
- 随机数。
- 容器遍历顺序。
- 排序算法。
- 并行任务调度。
- 物理模拟。
- 未初始化内存。
- 平台和编译器差异。
- 逻辑代码执行顺序。
确定性是整个执行环境的性质,不是单个函数加一个开关就能获得。
十一、浮点数与跨平台确定性
1. 为什么 IEEE 754 仍不够
即使平台都支持 IEEE 754,结果仍可能受到以下因素影响:
- CPU 指令集和中间精度。
- 是否使用融合乘加。
- 编译器优化。
- 数学库实现。
- 舍入模式。
- 表达式重排。
- 不同平台的三角函数与超越函数实现。
初始误差可能很小,但在大量迭代、碰撞和分支判断后,会导致完全不同的状态。
2. 定点数
一种方案是使用定点数。若保留 $f$ 个小数位比特,可把实际值表示为:
$$ x
\frac{n}{2^f} $$
其中 $n$ 是整数。加减法可直接用整数运算;乘除法则需要显式缩放和溢出处理。
定点数的优点是结果更容易跨平台复现,代价是:
- 需要自行实现数学类型与运算。
- 表示范围和精度固定。
- 乘除法更复杂。
- 向量、四元数和几何算法需要配套实现。
- 性能与溢出必须仔细评估。
另一种选择是限制平台、编译器和数学库,并对浮点环境进行严格控制,但仍需大量验证。
十二、随机数确定性
游戏中的暴击、出生点、掉落和 AI 行为经常使用随机数。伪随机数生成器在种子和调用序列相同时可以产生相同结果:
$$ r_{n+1}
G(r_n) $$
要保持一致,必须保证:
- 所有节点使用相同算法。
- 初始种子相同。
- 调用次数相同。
- 调用顺序相同。
- 不相关系统不要共享一个容易被扰动的随机流。
更稳健的设计是按系统、实体或用途拆分随机流,并将关键随机结果纳入日志。否则,一个仅在某客户端多执行一次的粒子随机调用,都可能改变之后的 Gameplay 随机结果。
十三、容器、算法和执行顺序
无序容器的遍历顺序可能因平台、哈希实现或插入顺序不同而变化。若遍历顺序会影响伤害结算、碰撞或目标选择,就会破坏确定性。
常见约束包括:
- 关键对象使用稳定唯一编号。
- 结算前按稳定键排序。
- 相等元素使用稳定的次级规则。
- 禁止依赖指针地址决定顺序。
- 并行任务的结果按确定顺序归并。
- 明确对象创建与删除的生效帧。
物理系统尤其困难,因为接触点排序、迭代求解和浮点误差都可能导致分歧。帧同步项目通常需要确定性物理实现或大幅限制物理系统的使用方式。
十四、一致性检测与调试
1. Checksum
各客户端可定期对游戏状态计算校验值:
$$ h_t
H(S_t) $$
服务器收集同一帧的校验值。如果:
$$ h_t^{(1)} \neq h_t^{(2)} $$
则说明至少有一个客户端已经产生状态分歧。
可以对全部状态计算校验,也可以只覆盖决定 Gameplay 的关键数据。后者成本更低,但必须避免漏掉会影响未来模拟的状态。
2. 自动定位
发现第 $N$ 帧不一致后,可以让客户端上传 $N$ 前后一段时间的完整日志,例如:
- 每帧输入命令。
- 随机数调用。
- 实体创建和销毁。
- 关键属性变化。
- 系统执行顺序。
- 分模块校验值。
比较日志可以确定最早出现差异的帧和系统。分层校验比只保存一个全局哈希更容易定位问题。
3. 回放验证
确定性模拟可以把同一输入日志在离线环境中重复执行。若结果无法复现,说明运行时仍存在未受控的非确定因素。
十五、逻辑帧与渲染帧分离
1. 两种频率
逻辑模拟和画面渲染不必使用相同频率:
- 逻辑帧按固定时间步推进。
- 渲染帧根据设备性能独立运行。
- 渲染层在相邻逻辑状态之间插值。
如果逻辑状态为 $S_k$ 和 $S_{k+1}$,渲染状态可表示为:
$$ S_{\text{render}}
\operatorname{Interpolate} (S_k, S_{k+1}, \alpha) $$
其中 $0 \leq \alpha \leq 1$。
2. 分离的价值
- 逻辑帧冻结时,渲染层仍可更新界面和动画。
- 渲染卡顿不会改变逻辑执行次数。
- 低频网络同步可通过插值获得平滑画面。
- 服务器可以只运行逻辑,不承担完整渲染。
- 固定逻辑帧更利于回放和确定性验证。
表现层的粒子、相机抖动和纯视觉动画通常不应参与 Gameplay 校验。
十六、输入缓冲与延迟
帧同步常在客户端维护一个命令缓冲区,用来吸收网络抖动:
- 大缓冲更稳定,但输入延迟更高。
- 小缓冲响应更快,但更容易因晚到命令停顿。
如果逻辑帧长度为 $\Delta t$,预缓冲 $K$ 帧,则仅缓冲本身引入的延迟约为:
$$ T_{\text{buffer}}
K\Delta t $$
缓冲大小可以根据网络质量动态调整,但变化应平滑,避免游戏速度突然跳变。
十七、断线重连
1. 仅重放全部命令的问题
帧同步可以保存从开局开始的命令序列。掉线玩家重新连接后,理论上能从初始状态重放全部命令。
但对长时间对局而言:
- 重放耗时越来越长。
- 客户端需要保存完整命令历史。
- 版本或数据差异会导致重放失败。
2. 关键帧快照
更实用的方案是定期保存逻辑关键帧:
- 加载最近的状态快照。
- 获取快照之后的命令日志。
- 关闭或简化渲染。
- 加速执行命令,追赶当前帧。
- 追平后恢复正常速度。
可概括为:
$$ S_{\text{now}}
\operatorname{Replay} (S_k, C_{k+1}, \ldots, C_{\text{now}}) $$
关键帧间隔越短,重连越快,但存储和生成成本越高。
十八、回放与观战
1. 回放
对于确定性帧同步,回放文件主要保存:
- 初始版本和静态数据标识。
- 初始随机种子。
- 按帧排序的玩家命令。
- 少量关键帧快照。
命令日志通常比每帧保存完整世界状态小得多。若要支持“回到前一分钟”,可以跳转到最近关键帧,再重放后续命令。
2. 观战
观战与断线重连在数据流程上高度相似:
- 观战客户端获得某个关键帧。
- 接收之后的命令流。
- 加速追赶到指定观战时间。
- 按正常速度继续播放。
竞技游戏常让观战画面落后真实比赛一段时间,以降低窥屏和信息泄露风险。
十九、帧同步的安全问题
帧同步客户端通常需要运行完整游戏逻辑并保存完整世界状态。这意味着:
- 战争迷雾下的敌人数据可能已经存在于内存中。
- 第三方程序可读取隐藏位置和属性。
- 客户端可能篡改命令、速度或逻辑。
- 仅比较最终命令不一定能发现信息型作弊。
可采用的缓解手段包括:
- 服务器验证命令合法性和频率。
- 服务器运行影子模拟或关键规则验证。
- 对状态校验和异常行为进行检测。
- 不向客户端分发完全不需要的数据。
- 将敏感判定保留在权威服务器。
但若客户端必须拥有完整隐藏状态,信息泄露风险很难彻底消除。
二十、帧同步总结
1. 优势
- 网络主要传输输入,流量通常较低。
- 玩家数量增加时,状态广播压力相对可控。
- 天然支持命令回放。
- 在确定性成立时,各端能运行完整模拟。
- 适合 RTS、MOBA 等命令驱动、单位数量多的游戏。
2. 难点
- 跨平台确定性非常困难。
- 状态分歧调试成本高。
- 输入缓冲会增加操作延迟。
- 慢客户端和掉线会影响推进。
- 重连需要快照和追帧。
- 客户端拥有大量游戏数据,防作弊更困难。
二十一、状态同步
1. 权威服务器
状态同步中,服务器运行权威游戏逻辑。客户端发送输入或意图,服务器计算合法结果,再把状态变化复制给客户端:
$$ S_{t+1}^{s}
F(S_t^{s}, I_t^{1}, I_t^{2}, \ldots) $$
客户端展示的是服务器权威状态的本地副本。
2. 按客户端定制数据
服务器不必向所有客户端发送相同内容。可以根据每个玩家的:
- 视野。
- 距离。
- 队伍。
- 权限。
- 网络质量。
- 对象优先级。
生成不同复制集合。这既节省带宽,也能减少隐藏信息泄露。
3. AOI
AOI,即 Area of Interest,用来筛选当前客户端关心的对象。远离玩家、不可见或无关的实体可以:
- 降低更新频率。
- 使用更低精度。
- 暂停复制。
- 完全不发送。
AOI 是大世界和高并发游戏控制服务器计算量与带宽的关键机制。
二十二、授权对象与复制对象
在状态同步架构中,可区分:
- Authorized Object:本地玩家直接控制的对象。
- Replicated Object:由服务器同步到本地的其他对象。
即使是本地授权对象,最终状态通常仍由服务器裁决。客户端可以先预测其运动,但不能自行决定穿墙、伤害或资源变化。
对远端复制对象,客户端通常根据收到的状态进行插值,而不是直接执行对方原始输入。
二十三、状态同步的响应性问题
若客户端必须等待服务器状态后才显示本地操作,按键到画面反馈至少会经历一次往返的一部分:
- 输入发送到服务器。
- 服务器执行。
- 新状态返回客户端。
- 客户端渲染。
高 RTT 下,角色会显得迟钝。这就是“纯哑客户端”的问题。
解决方案是:
- 客户端预测。
- 服务器校正。
二十四、客户端预测
1. 立即响应
本地玩家输入后,客户端不等待服务器,先用同一套移动逻辑预测结果:
$$ \hat{S}_{n+1}^{c}
F(\hat{S}_{n}^{c}, I_n) $$
这样按键可以立即产生画面反馈。
2. 保存未确认输入
客户端需要保存:
- 输入序号。
- 输入内容。
- 预测前后的状态。
- 服务器最后确认的输入序号。
这些记录用于在权威结果到达后重新计算。
3. 客户端领先
在持续预测时,客户端画面通常领先于服务器当前已确认的模拟位置。课件示例中,领先量与半个 RTT 及一个缓冲命令帧有关。
预测减少的是感知延迟,不会改变真实网络延迟,也不会让客户端获得最终权威。
二十五、服务器校正
1. 无分歧
若服务器权威状态与客户端对同一确认帧的预测一致,客户端只需丢弃已确认输入并继续预测。
2. 有分歧
若服务器判定移动被障碍阻挡,或认为客户端状态非法,客户端必须接受服务器结果:
- 回到服务器确认状态 $S_a^s$。
- 删除已被服务器处理的输入。
- 按顺序重放尚未确认的输入。
- 得到新的当前预测状态。
可表示为:
$$ \hat{S}_{\text{now}}^{c}
\operatorname{Replay} (S_a^s, I_{a+1}, \ldots, I_{\text{now}}) $$
3. 视觉修正
直接跳到校正位置会产生“拉回”或“橡皮筋”现象。客户端可在不影响逻辑权威的前提下,让表现层平滑靠近新位置。
但误差过大、碰撞结果改变或角色处于非法位置时,必须立即修正,不能为了视觉平滑继续显示错误状态。
二十六、远端对象插值
远端玩家的状态通过网络离散到达。若收到一个包就立即跳到新位置,抖动会非常明显。
客户端通常故意落后服务器一小段时间,在两个已知快照之间插值:
$$ S_{\text{remote}}(t)
\operatorname{Interpolate} (S_i, S_{i+1}, \alpha) $$
插值需要额外显示延迟,但能换来稳定运动。若后续快照未及时到达,可以短暂外推;外推时间过长则容易产生大幅纠正。
本地授权对象通常使用预测,远端复制对象通常使用插值,两者解决的是不同问题。
二十七、状态同步与帧同步对比
| 维度 | 状态同步 | 确定性帧同步 |
|---|---|---|
| 网络主要传输 | 状态变化 | 玩家输入或命令 |
| 权威位置 | 通常是服务器 | 可由服务器排序,所有端共同模拟 |
| 确定性要求 | 不要求客户端完全一致 | 必须高度确定 |
| 带宽 | 通常较高 | 通常较低 |
| 响应性 | 可通过预测获得较好体验 | 常受输入缓冲影响 |
| 玩家规模 | 常用于较少高频互动角色 | 可支持大量单位和较多玩家 |
| 跨平台 | 相对容易 | 相对困难 |
| 重连 | 获取当前状态即可,较容易 | 需要快照与追帧 |
| 回放体积 | 状态记录较大 | 命令日志较小 |
| 防作弊 | 权威服务器更有利 | 客户端完整状态带来风险 |
| 开发难点 | 复制、预测、校正和带宽 | 确定性、查错和延迟 |
表中的结论不是绝对规则。现代游戏常混合两种方法:部分系统同步命令,关键判定由服务器权威执行,表现状态则使用复制和插值。
二十八、如何选择同步方案
1. 更适合帧同步的条件
- 单局参与者和单位较多。
- 玩家输入比完整世界状态小得多。
- 游戏逻辑可以做成确定性。
- 回放和观战需求强。
- 输入允许增加少量缓冲延迟。
- 可严格控制平台、版本和数学实现。
2. 更适合状态同步的条件
- 角色数量有限,但运动和碰撞频率高。
- 服务器必须拥有强权威。
- 跨平台差异较大。
- 世界可以按 AOI 切分。
- 客户端预测和校正可以接受。
- 隐藏信息和反作弊要求高。
3. 混合方案
常见混合方式包括:
- 战略命令使用帧同步,角色表现使用状态插值。
- 移动采用客户端预测,伤害由服务器权威计算。
- 服务器保存低频关键帧,客户端之间同步高频命令。
- 大世界使用状态同步,局部小游戏使用 Lockstep。
选择依据应是玩法语义和工程约束,而不是某种方案是否“更先进”。
二十九、网络同步的工程原则
- 先确定权威归属,再设计数据流。
- 区分输入、事件、状态和表现数据。
- 逻辑帧使用固定步长,渲染帧独立运行。
- 快照尽量使用增量、量化、优先级和 AOI。
- 帧同步必须控制初始数据、随机数、浮点和执行顺序。
- 定期计算状态校验,并保留可重放日志。
- 重连、回放和观战共享“关键帧加命令流”的基础设施。
- 本地玩家使用预测,远端对象使用插值。
- 服务器校正必须以权威状态为准。
- 客户端永远不能决定对其他玩家有影响的最终结果。
- 同步方案必须考虑跨平台、更新版本和防作弊。
- 混合架构通常比纯粹采用单一方案更符合实际项目。
三十、复习要点
- P2P 成本低但缺少可信权威;主机模式依赖玩家设备;专用服务器更公平且可控。
- 快照同步传输世界状态,简单但带宽和服务器压力较高。
- 增量压缩只传相对已确认基准发生变化的字段。
- 确定性帧同步传输玩家命令,各端从相同初态运行相同模拟。
- 严格 Lockstep 会让所有人等待最慢节点,Bucket 同步用固定时间窗吸收抖动。
- 浮点、随机数、容器顺序、并发和物理模拟都可能破坏确定性。
- Checksum 可发现状态分歧,分帧日志和回放用于定位最早差异。
- 逻辑帧与渲染帧分离有利于确定性、平滑显示和服务端运行。
- 关键帧快照加命令日志可以同时支持重连、回放和观战。
- 状态同步由权威服务器运行逻辑,并按 AOI 向客户端复制相关状态。
- 客户端预测提供即时反馈,服务器校正确保最终一致。
- 本地对象适合预测,远端对象适合插值。
- 帧同步与状态同步没有绝对优劣,应根据玩法、规模、带宽和安全需求选择。