导语:「远港 Online」第一次内部测试,一位策划在公屏刷屏:我明明滚出了那支箭的落点,为什么还是死了?查了一晚上日志,结论是他本地确实躲开了,服务器那边他却还站在原地——中间隔着 110ms 的往返和 100ms 的插值缓冲。那一晚我意识到,团队里一半人把复制系统当黑魔法用,而黑魔法迟早要收利息。这一章不交利息,先把本金讲清:延迟从哪来,真相放哪里,以及为什么 MMO 在网络模型上根本没有选择权。
本章地图
mindmap
root((网络原理速成))
网络三害
延迟 RTT:距离与光速
抖动 Jitter:WiFi 的原罪
丢包 Loss:TCP 放大器
同步模型
帧同步:带宽 O(N²)
状态同步:MMO 唯一解
权威归属
客户端权威=作弊温床
服务器权威=唯一真相
时间三件套
插值:多看 100ms
外推:猜错就瞬移
延迟补偿:替射手回溯
拓扑选型
P2P
Listen Server
Dedicated Server
分布式 Zone网络三害有三个必须背下来的数字
写单机的时候,时间是你的朋友:每一帧 16.6ms,输入进来,画面出去,因果闭合成环。联机之后,时间变成债主。延迟、抖动、丢包这三样东西,每一个都足以让一套设计精美的同步方案当场散架,而它们的量级直接决定了后面所有架构决策。
先说延迟(Latency),工程上统一用 RTT(Round-Trip Time,往返时间)记账,因为游戏里的每一次请求-确认都走一个来回。RTT 的物理下限是光速:光纤里信号传播速度约为真空中光速的三分之二,也就是每公里约 5 微秒。听起来很快,但太平洋海底光缆一个来回就是一亿多米的路程,物理极限摆在那,谁也优化不掉。
下面是国内玩家常见的实测量级(光纤入户、晚高峰、跨网段取偏保守值):
| 链路 | RTT 量级 | 对游戏的含义 |
|---|---|---|
| 同城同运营商 | 1–10ms | 格斗游戏级,几乎无感 |
| 同城跨运营商 | 15–40ms | 竞技射击可接受 |
| 跨省(北京—上海) | 25–50ms | 主流网游常态 |
| 国内—美西 | 130–180ms | MMO 能玩,射击痛苦 |
| 国内—欧洲 | 160–250ms | 必须靠预测与补偿硬扛 |
150ms 是个心理分界线:低于它,玩家把延迟归因于网络;高于它,玩家把延迟归因于你的游戏。跨国部署「远港 Online」就意味着一个技能从按下到服务器确认,乐观也要 200ms 上下,这个数会反复出现在后面每一节的推导里。
再说抖动(Jitter),即延迟的方差。平均延迟决定你慢多少,抖动决定你能不能预测这个"慢"。有线宽带的抖动通常在 1ms 以内,调度算法喜欢这种链路。WiFi 是另一回事:空闲时 2–10ms,信道拥挤或隔壁开了微波炉时,单包尖刺冲到 50–100ms 不稀奇。移动网络介于两者之间,4G 常态 10–50ms 抖动,5G 好一些但切换基站时同样会抽风。抖动无法消除,只能吸收——第四节讲的插值缓冲,本质上就是拿固定延迟换抖动免疫。
最后是丢包(Packet Loss)。UDP 本身不保证送达,1% 丢包听起来无害,放到具体场景里算一笔账:服务器以 20Hz 发快照,1% 丢包意味着每 5 秒丢一帧快照,插值缓冲吃得下;但如果你的关键事件(技能释放、拾取确认)也裸奔在 UDP 上不做可靠性处理,每 5 秒就有一次"我按了没反应"。TCP 更糟——它是丢包的放大器。让它治丢包?那只是错觉:丢一个包,后面所有包排队等重传,队头阻塞让延迟瞬间飙到几百毫秒。吞吐量的经典近似公式是:
其中 是单包大小, 是丢包率。 从 0.01% 涨到 1%, 放大 10 倍,吞吐直接砍到十分之一。这就是联机游戏主通道一律走 UDP、自己造选择性重传的原因,也是 UE 的 NetDriver 栈站在 UDP 之上的原因。
三害还有一个合体形态值得单独提防:延迟尖刺(Lag Spike)。均值变差?不,它是偶发的单包几百毫秒延迟,来源五花八门——WiFi 重传、路由切换、运营商拥塞、家里有人开始刷视频。均值 30ms 的链路可能每十秒来一次 400ms 尖刺。对状态同步来说,尖刺表现为快照断供:插值缓冲被抽空,外推预算顶满,然后冻结或瞬移。所以评估链路质量只看平均 Ping 是不够的,必须看 P95、P99 分位。「远港 Online」的链路监控指标里,P99 延迟权重比均值还高——均值决定手感,尾部分位决定事故率。
顺带澄清一个常见误解:选 UDP 实际上是把可靠性拆成可选项,而非放弃可靠性。UE 的复制系统里,属性复制走"最新值覆盖旧值"的语义,丢一个旧快照无所谓,下一个快照自带新状态,天然免疫丢包;而 RPC 可以声明为 Reliable,引擎在 UDP 之上自己做确认与重传——但因为重传会占通道,Reliable RPC 滥用会把带宽烧穿,这是第 21 章要算的账。原则先立在这:状态类信息容忍丢失,事件类信息必须可靠,两条通道的脾气完全不同。
给你一个可执行的直觉训练:今晚就用 ping 打三个目标——同城市的服务器、跨省节点、境外节点,各打 200 个包,看平均值、标准差、丢包率。三个数字各差一个数量级的体验,比读十篇文章管用。
帧同步的账算完,MMO 只剩状态同步一条路
同步模型只有两大流派。帧同步(Lockstep):服务器只转发输入,所有客户端各自跑完全确定的模拟,大家输入相同、结果必然相同。状态同步(State Replication):服务器跑唯一的模拟,把世界状态的快照或增量发给客户端,客户端只负责显示。
帧同步的优点确实诱人:带宽极省(只传输入)、回放天然、观战天然。 RTS 和格斗游戏爱用它,不是没道理。但它有三条刻在基因里的约束,条条掐在 MMO 脖子上。
约束一,带宽随人数平方增长。每个客户端要拿到全员的输入,服务器出口带宽是:
是玩家数, 是单帧输入大小, 是锁帧频率。取一组对帧同步相当友好的参数: 字节(按键掩码加两个浮点朝向),Hz。10 人小战场,服务器出口约 96KB/s,岁月静好。100 人团战,出口涨到 9.6MB/s(约 77Mbps),开始肉疼。1000 人同屏的 MMO 战场,出口约 960MB/s,接近 8Gbps——这还只是输入广播,世界状态一个比特都没传。天文数字不是说说的。
约束二,确定性是个瓷器活。浮点运算顺序、物理引擎版本、编译器优化、甚至数学库的平台差异,都会让"相同输入"在不同机器上跑出不同结果。RTS 用定点数加统一物理库勉强压住,而 UE 的 Chaos 物理和整套浮点管线从未承诺跨平台确定性,在 5.8 里依然如此。一个 MMO 客户端要在 Windows、主机、移动端全部保持逐比特一致,等于给每个平台套绞索。
约束三,最慢的玩家决定所有人的节奏。Lockstep 要求全员到齐才能推进下一帧,一个人卡住 500ms,99 个人陪他看幻灯片。MMO 里玩家随时进出、跨区迁移、弱网挂机,这个模型从第一天就不成立。
确定性的坑值得再展开半页,因为几乎每个转型团队都在这里交过学费。假设两个客户端同时模拟一次火球爆炸:A 机器上 cos(1.0000001) 和 B 机器上 cos(1.0) 在第七位小数分叉,弹片溅射角度差一丝,五秒后两只哥布林的站位差半米,十秒后两边的战斗结果完全不同——模拟已经不可逆地分道扬镳,而帧同步协议里没有任何机制能发现或修复这种分叉,除了定期校验哈希然后绝望地踢人。单机玩家不在乎,因为单机只有一个世界;帧同步把"全世界只有一个世界"的责任摊给了每台客户端,而浮点硬件根本担不起这个责任。
状态同步把账全翻过来:带宽随"你看得见多少实体"线性增长,与全服总人数无关;客户端不需要确定性,渲染即所得;任何人掉线只影响自己。代价是真相只在服务器,客户端看到的永远是旧世界——这正是后面两节要处理的麻烦。还有一个常被忽略的收益:状态同步允许不同客户端收不同精度的世界。远处的怪给你 5Hz 低清快照,脸上的怪给你 20Hz 高清快照,带宽花在刀刃上。帧同步做不到这种分级——所有人必须收全量输入,没有"远处模糊"这个选项。这个能力对应 UE 里的相关性(Relevancy)与更新频率分级,第 21 章会把它拆成可调参数。
quadrantChart
title "同步模型:一致性 vs 响应速度"
x-axis "响应慢" --> "响应快"
y-axis "一致性弱" --> "一致性强"
quadrant-1 "理想区(代价在带宽)"
quadrant-2 "学术区"
quadrant-3 "不可用区"
quadrant-4 "手感区(代价在作弊面)"
"帧同步": [0.3, 0.9]
"纯客户端权威": [0.9, 0.2]
"服务器权威+插值": [0.45, 0.85]
"服务器权威+预测回滚": [0.75, 0.8]图上没有免费的格子。帧同步占住强一致性但响应被最慢者拖死;纯客户端权威手感无敌但等于没有真相;MMO 能选的只有右半边那两种服务器权威方案,用客户端预测把响应速度买回来(预测的细节见第 23 章)。
真相只能放服务器:客户端权威等于给外挂开门
早期网游干过把伤害数值交给客户端上报的事。结局毫无悬念:内存修改器一抓一把,伤害数字随便填,运营半年就死在工作室手里。教训浓缩成一条行业公理:客户端发来的每一个字节,服务器只当它是"声称"。事实得服务器自己算——客户端说"我打中了",服务器必须自己算一遍到底中没中。
服务器权威(Server Authoritative)模型的分工是:客户端发意图(我要朝这个方向移动、我要释放这个技能),服务器做裁决(你能不能移、技能中没中、掉多少血),然后把裁决结果作为唯一真相广播回去。客户端本地显示的世界,只是真相的一份延迟副本——行话叫影子世界。
补课:分布式系统的 CAP 直觉。CAP 说的是网络分区(P)必然发生时,一致性(C)和可用性(A)只能二选一。游戏的答案很统一:选 A,然后靠"单一真相源加最终一致"来补救。服务器权威世界就是 Git 里的 main 分支,所有客户端都是本地 fork,可以暂时分叉(预测),但最终必须 rebase 回 main(服务器校正)。MMO 里跨 Zone 的边边角角允许短暂不一致,但每个实体在任意时刻只有一个进程说了算。这套思路会在第 25 章的架构拆分里反复出现。
服务器收到一个技能请求后,裁决是一条校验链,任何一个环节不过就驳回:
flowchart TD
A[收到技能释放请求] --> B{玩家存活且未被控制?}
B -- 否 --> R1["驳回: 状态非法"]
B -- 是 --> C{冷却结束且资源足够?}
C -- 否 --> R2["驳回: 消耗校验失败"]
C -- 是 --> D{目标在射程与视线内?}
D -- 否 --> R3["驳回: 目标非法"]
D -- 是 --> E[延迟补偿回滚目标位置]
E --> F{命中判定通过?}
F -- 否 --> G["广播: 未命中表现"]
F -- 是 --> H[结算伤害/效果]
H --> I["广播: 命中结果与状态增量"]
I --> J["回执给施法者: 确认或修正预测"]链上每一环的排布有明确的代价考量:便宜的检查(存活、冷却)在前,贵的检查(视线射线、回滚重演)在后。外挂最常见的攻击就是把"贵检查"用非法数据绕过,所以链上每一环都必须独立成立,不能依赖客户端已经"自检过"。
客户端与服务器各自持有的世界,用类图表示是这样一种关系——两边对象同名,权力天差地别:
classDiagram
class ServerWorld {
+AuthoritativeState 唯一真相
+ValidateIntent() 校验链
+RewindForLagComp() 延迟补偿
+BroadcastDelta() 增量广播
}
class ClientWorld {
+RenderTime: "渲染时刻(落后)"
+SampleAndRender() 采样影子
}
class SnapshotBuffer {
+Snapshots 快照环形缓冲
+Sample(t) 双快照插值
}
class PredictedSelf {
+PendingInputs 未确认输入
+ReplayAfterCorrection() 校正后重放
}
class InterpolatedProxy {
+RemoteActors 其他玩家影子
}
ServerWorld --> ClientWorld : 快照/增量 (20Hz)
ClientWorld *-- SnapshotBuffer
ClientWorld *-- PredictedSelf
ClientWorld *-- InterpolatedProxy
PredictedSelf ..> ServerWorld : 输入意图 (上行)自己的角色走 PredictedSelf 通道(按下去立刻动,事后对账),别人的角色走 InterpolatedProxy 通道(老老实实等快照插值)。同一个客户端上两套时间并存,这是 UE CharacterMovementComponent 在 5.8 里替你实现的骨架,下一章拆它之前,你得先在概念上接受这个分裂的世界观。
插值、外推、延迟补偿:用 100ms 延迟换不瞬移的世界
服务器真相以固定频率广播,这个频率叫快照率(Snapshot Rate),「远港 Online」按 UE 默认思路定档 20Hz,即快照间隔 ms。问题来了:客户端 60fps 渲染,每 16.6ms 就要画一帧,而真相 50ms 才到一份。中间那些帧,其他玩家的位置从哪来?
两个选择。外推(Extrapolation):根据速度向量往前猜,猜多远取决于网络多慢。插值(Interpolation):故意活在过去,渲染时刻固定在"最新快照之前两个间隔",这样任意一帧渲染,手里永远有前后两个真实快照夹着,做个线性混合就是精确位置。
工程界的共识是选插值,而且把外推压到最小。原因用一次实战事故就能说清:外推 50ms,一个急停转向的角色在其他人屏幕上会冲出真实位置半米到一米,然后被下一个快照硬生生拽回去——玩家看到的是"瞬移"。瞬移一次,射击游戏毁一次手感,MMO 毁一次技能判定观感。而插值的代价只是固定的 100ms 渲染延迟:世界是"晚"了一点,但它连续、平滑、永远可信。宁可稳定地晚 100ms,不可偶尔错 50ms,这是手感工程的铁律。
两个间隔而非一个——事情没有看起来那么简单。推导很短。设快照按间隔 到达(忽略抖动),渲染时刻为 。插值要求任意渲染时刻都存在快照 。 时,渲染时刻卡在最新快照与"还没到的下一个快照"之间,缓冲里只有一个快照可依赖——只要网络抖动让某个快照晚到哪怕 10ms,或干脆丢一个,渲染时刻就越过缓冲边界,被迫外推。 时,缓冲里常备两个以上快照,丢一个、晚一个都不影响插值连续性。于是有:
这 100ms 是拿抖动免疫和抗丢包换来的保险费。快照率提到 30Hz,保费降到约 67ms,但服务器出口带宽涨 50%——又一笔要算的账。
下面这张时间轴把缓冲区画出来。上面一轨是服务器快照抵达客户端的时刻(假设单向传输 25ms),下面一轨是客户端渲染游标,它始终落后最新快照两个间隔:
第三件是延迟补偿(Lag Compensation),它解决的是射手视角的公平:你瞄的是 100ms 前的敌人(插值所致),开火消息再过半个 RTT 到服务器,服务器"当下"的敌人早不在那了。不做补偿,玩家会觉得"瞄得准却打不中"。做法是让服务器为每个玩家保存最近几百毫秒的位置环形缓冲,命中判定时把目标回滚到射手开枪那一瞬看到的位置。回滚窗口的推导同样朴素——射手看到的是延迟了 的世界,开火指令回传又花 ,所以:
RTT 100ms、 50ms 时,ms。这个窗口必须设上限(业界常见钳制在 250ms 上下):窗口越大对高延迟玩家越友好,但对被击中者越残忍——你躲进掩体半秒后还可能被"回溯枪"打死。公平饼就那么大——补偿刀切下去,切给射手多一点,被击中者就得少一点。把补偿叫成"慈善",等于假装刀口不存在。补偿的本质是在两个玩家的公平之间切蛋糕,刀口位置就是设计决策。
三件套的工程落地,核心就是一个快照环形缓冲加一次回滚重演,伪代码如下:
# 实体插值缓冲:其他玩家影子位置的唯一来源
class SnapshotBuffer:
def __init__(self, capacity=32):
self.snaps = RingBuffer(capacity) # 按服务器时间戳排序
def add(self, snap):
self.snaps.insert_sorted(snap) # UDP 乱序到达是常态
def sample(self, render_time):
newer = self.snaps.first_after(render_time)
older = self.snaps.last_before(render_time)
if older and newer: # 正常路径:双快照夹逼
t = (render_time - older.time) / (newer.time - older.time)
return lerp(older.state, newer.state, clamp(t, 0, 1))
if self.snaps.newest().age() < EXTRAPOLATE_BUDGET_MS:
return extrapolate(self.snaps.newest()) # 短暂外推,封顶 50ms
return self.snaps.newest().state # 超预算:冻结,拒绝瞬移# 简单延迟补偿:命中判定前把目标回滚到射手开枪时刻
def server_validate_hit(shooter, target, fire_msg):
rtt = shooter.conn.smoothed_rtt()
rewind = rtt + INTERP_DELAY # W = RTT + 2Δ
rewind = min(rewind, MAX_COMP_MS) # 钳制 250ms,防回溯枪滥用
shot_time = server_now() - rewind
saved = target.position_history.at(shot_time) # 环形缓冲取值
with target.temporarily_at(saved):
hit = trace(fire_msg.origin, fire_msg.dir) # 在旧世界上重演射线
if hit:
apply_damage(target, fire_msg.weapon)
return hit要点只有三行:回滚深度由 RTT 加插值延迟决定,与帧率无关;重演必须可逆,改完位置立刻还原,否则一个判定的副作用会污染下一个判定;历史缓冲至少覆盖 MAX_COMP_MS,存不够就直接拒赔,不能让高延迟玩家把窗口顶穿。
把三件套串起来,一次技能命中的完整往返长这样。以 RTT 100ms、快照 20Hz、服务器 tick 50ms 为例,各段耗时标在图上:
sequenceDiagram
participant A as 施法者客户端
participant S as 服务器·权威
participant B as 旁观者客户端
Note over A: t=0 按下技能键
A->>A: 本地预测: 动画/特效立刻播放 (16ms 内)
A->>S: 技能请求上行 (RTT/2 ≈ 50ms)
Note over S: t≈50ms 收到请求
S->>S: 校验链 + 延迟补偿回滚 + 结算 (1 tick ≈ 50ms)
S->>A: 确认回执: 预测成立 (t≈150ms 到达)
A->>A: 预测与真相一致: 无感; 不一致: 校正重放
S->>B: 命中结果随下一快照广播 (t≈150ms 到达)
Note over B: 进入插值缓冲, 再等 100ms
B->>B: t≈250ms 渲染出这次命中三个数字值得记住。施法者 0ms 就有视觉反馈,这是预测买的手感;施法者 150ms 拿到权威确认,这是"子弹有没有中"的确定时刻;旁观者 250ms 才看到全程,其中 100ms 是插值保险。所以 MMO 里"我死了才看到他抬手"是有据可查的——预算表上早就写好了 250ms。玄学?不存在的。
测量这条链路的健康度,客户端侧最直接的两个指标是 Ping 和丢包。UE C++ 里读法很薄:
// 调试 HUD 的链路质量采样(UE 5.8)
void UNetDebugWidget::SampleLinkQuality(const APlayerController* PC)
{
if (!PC) return;
const float PingMs = PC->GetPingInMilliseconds(); // 平滑后 RTT
float AvgLagSec = 0.f;
if (const UNetConnection* Conn = PC->GetNetConnection())
AvgLagSec = Conn->AvgLag; // 秒, 移动平均
// 丢包看 STAT NET 的 In/Out Loss; 抖动自采 Ping 求标准差
UE_LOG(LogTemp, Verbose, TEXT("Ping=%.0fms AvgLag=%.0fms"),
PingMs, AvgLagSec * 1000.f);
}GetPingInMilliseconds 对高帧率客户端做过修正,比裸除 RPC 时间戳准;抖动要靠自己采样算标准差,引擎不直接给。这两个数字接进调试 HUD,劣网排查时先看它们再怀疑代码,能省掉一半的瞎忙。
拓扑选型没有银弹:四种架构各拿什么换什么
同步模型解决"数据怎么流",拓扑解决"真相住在哪台机器上"。四档方案,一致性、成本、防作弊、容量四个维度,每上一档都用钱和复杂度换权力:
| 拓扑 | 一致性 | 单局成本 | 防作弊 | 容量天花板 | 典型用途 |
|---|---|---|---|---|---|
| P2P | 弱(各自为政) | 几乎为零 | 裸奔 | 4–8 人 | 派对小游戏 |
| Listen Server | 中(主机是真相) | 零(玩家自掏) | 主机玩家免疫 | 8–16 人 | 合作联机 |
| Dedicated Server | 强 | 每房间一台进程 | 好 | 16–200 人 | 竞技射击/战场 |
| 分布式 Zone | 强(分片真相) | 一个服务器集群 | 好 | 数千人同世界 | MMO |
quadrantChart
title "拓扑选型:成本 vs 权威性(气泡越大容量越高)"
x-axis "成本低" --> "成本高"
y-axis "权威性弱" --> "权威性强"
quadrant-1 "商业游戏主战场"
quadrant-2 "独立游戏舒适区"
quadrant-3 "别住这里"
quadrant-4 "钞能力特区"
"P2P": [0.15, 0.2]
"Listen Server": [0.25, 0.45]
"Dedicated Server": [0.6, 0.8]
"分布式 Zone 集群": [0.95, 0.9]P2P 最致命的其实是 NAT 打洞的成功率和主机玩家退出的雪崩效应——房主一拔网线,全房陪葬。作弊反倒排在后面。Listen Server 本质是把 P2P 的房主扶正成权威,朋友间联机够用,但房主天然拥有 0ms 延迟加篡改豁免,拿去办公开匹配等于请外挂进门。
有一个现实的折中值得知道:不少合作类游戏(四人刷本那类)用的就是 Listen Server,靠"朋友之间不会开挂"的社会契约兜底。这个选择在熟人场景完全成立,成本为零、架构一天搭完。错的是把它平移到陌生人对局——社会契约换成公开匹配,房主优势立刻变成公平性事故。选型之前先回答一个问题:你的玩家之间互相认识吗?
Dedicated Server 是 UE 网络栈的主场:服务器不渲染、只跑逻辑与真相,一个进程扛几十到两百人(取决于 tick 开销,第 24 章专门拆它的构建与部署)。再往上,单进程撞墙——一张无缝大地图几万个实体、几千玩家,没有任何单机 tick 得动。分布式架构把世界切成 Zone,每个 Zone 一个进程,网关管连接,角色跨区做进程间交接。这是第 25 章的主题,本章只需要记住一句:分布式的每一层都是在给"单点真相"打补丁,补丁的代价是跨区边界上那一大堆最终一致的边角案例。
「远港 Online」的同步预算:先把数字写死在纸上
补课到此为止,收拢成一份可以贴在工位上的预算表。「远港 Online」对标 WOW 式大世界,不追求格斗游戏的严苛手感,但必须保证技能判定公平、大世界千人同图不崩:
| 预算项 | 目标值 | 依据 |
|---|---|---|
| 可玩 RTT 上限 | 150ms | 超此值补偿窗口顶穿 |
| 服务器快照率 | 20Hz(战斗区可提 30Hz) | 带宽与平滑的折中 |
| 客户端插值延迟 | 100ms(2Δ) | 抗一帧丢包加常规抖动 |
| 外推预算 | ≤50ms,超时冻结 | 宁可定住,不可瞬移 |
| 延迟补偿窗口 | RTT+100ms,钳制 250ms | 公平蛋糕的刀口 |
| 单玩家下行带宽 | ≤64KB/s | 相关性剔除后可达 |
| 关键事件可靠性 | 100%(可靠 UDP 通道) | 技能/交易不走裸 UDP |
这张表就是验收标准,愿望清单没有这么硬的门槛。后面每一章——复制的相关性调优(第 21 章)、GAS 预测窗口(第 23 章)、服务器 tick 预算(第 24 章)——都会回来对这张表报账。预算立不住,架构再漂亮也是沙上盖楼。
定预算时还有两条经验法则。其一,按最差的用户画像定档,不按平均值。「远港 Online」的画像里有三四线城市的 WiFi 玩家,也有网吧百兆光纤玩家,预算表必须让前者能玩——否则你就等于放弃了一大块 MMO 的核心用户盘。其二,预算要留余量给运营期。上线后总会有人提议"把这个技能的快照率加一倍手感更好",如果 64KB/s 的下行预算被零零碎碎的新需求啃光,劣网玩家会成批流失。带宽预算和帧时间预算一样,是制度——超支要走评审,不能靠热情。
至于这张表怎么验证,答案藏在 UE 自带的劣网模拟里:编辑器 PIE 和控制台都支持注入人工延迟、抖动与丢包,后边上手任务第 2 条用的就是这套开关。任何同步方案没过"100ms 延迟加 1% 丢包"这一关,都不配进真机测试。
小结
延迟、抖动、丢包各有量级:同城 10ms 内、跨省 50ms 内、跨国 150ms 起;WiFi 抖动是有线的十倍起步;1% 丢包能让 TCP 吞吐砍到十分之一——这三个数决定一切上层设计。帧同步带宽随人数平方增长且要求全平台确定性,1000 人场景服务器出口逼近 8Gbps,MMO 只能状态同步——算术早就把答案写好了。客户端发来的每个字节都是声称;服务器权威加一条"便宜检查在前、昂贵重演在后"的校验链,是防作弊的第一道也是最后一道墙。插值延迟 = 2×快照间隔,用固定 100ms 换抖动免疫与抗丢包;延迟补偿窗口 = RTT + 插值延迟,钳制在 250ms,补偿的是射手的公平,切割的是被击中者的体验。拓扑四档逐级加码:P2P、Listen Server、Dedicated、分布式 Zone,每一档都是拿钱和复杂度换一致性与容量。
上手任务
- 用
ping对同城、跨省、境外三个目标各打 200 包,记录均值、标准差、丢包率,并对照本章第一节的量级表。验收:能说清自己宽带下"抖动是平均延迟的百分之几"。 - 在 UE 5.8 的 PIE 里开 1 服 2 客户端,控制台用
Net PktLag=100 Net PktLagVariance=25 Net PktLoss=1注入人工劣网,移动并施放技能。验收:观察到自己角色校正回弹、他人角色插值平滑两种行为,并能解释差异来源。 - 把本章的插值缓冲伪代码翻成真代码(语言不限),喂一组带 20% 乱序、2% 丢包的快照流。验收:输出位置曲线无跳变,外推时长不超过 50ms。
- 按 100 人、30Hz、32 字节输入重算帧同步带宽账,再按 50 个相关实体、20Hz、80 字节增量算状态同步账。验收:两个数字差三个数量级左右,并能指出参数里哪个最敏感。
下一章
预算表有了,该看 UE 拿什么兑现它:第 21 章拆复制系统的三件套——属性复制、RPC、相关性,看服务器真相是怎么一字节一字节流到客户端影子世界的。
延伸阅读
- 官方文档词条:Unreal Engine → Networking and Multiplayer → Networking Overview,以及 Actor Replication 词条(API 细节以 5.8 文档为准)。
- Valve 开发者社区经典:Source Multiplayer Networking(插值、延迟补偿概念的原典,概念跨引擎通用)。
- Gabriel Gambetta 的 Client-Server Game Networking 系列(fast-paced multiplayer 那篇,把本章三件套讲成了图文教程)。
- GDC 2015《Overwatch Gameplay Architecture and Netcode》相关公开分享:高 tick 率下预测与补偿的工业级实践。
- 源码路径:Engine/Source/Runtime/Engine/Private/CharacterMovementComponent.cpp(UE 自己的预测-校正实现,读完本章再读它,坡度刚好)。