Pong 对战(Realtime 60 FPS 原型)
- 类别:1v1 实时对战动作原型
- 目标:验证 60Hz Tick 同步、客户端预测、服务器纠正、插值/外推、丢包重传与观战延迟管线。
- 原型代号:
proto-006-pong - 依赖模块:
proto-001-echo-chatroom,proto-005-pictionary - 推荐语言栈:Go / Rust / C++ / TypeScript
- 网络协议:UDP (QUIC) / WebSocket (Binary Frame)
一、概述(Overview)
“Pong” 是游戏史上的起点,也是实时对战架构的 Hello World。
两名玩家各自控制一个挡板,
一颗小球在屏幕中弹来弹去,
任何一方未能接到球则对方得分。
这个极简机制背后,隐藏了所有实时同步游戏的关键挑战:
- 输入延迟与预测;
- Tick 驱动的状态更新;
- 客户端插值与服务器权威状态;
- 丢包重传、抖动缓冲;
- 观战延迟同步;
- 状态回放与重演一致性。
一旦你能在 60 FPS 下稳定运行一场跨国 Pong 对战,
你就掌握了所有实时网络游戏的核心原理。
二、核心玩法与系统目标(Gameplay & Objectives)
2.1 基础玩法
- 两名玩家(Player A 与 Player B);
- 各自控制左右两侧挡板(上下移动);
- 小球在场中弹跳;
- 若一方未接到球,对方得分;
- 比赛分若干回合(例如 11 分制)。
2.2 系统目标
| 模块 | 功能目标 | 验证重点 |
|---|---|---|
| Tick 驱动引擎 | 60Hz 固定帧率更新 | 一致性时间步 |
| 客户端预测 | 输入延迟隐藏 | Input Prediction |
| 服务器权威 | 状态校准与纠正 | Server Reconciliation |
| 插值与外推 | 平滑显示 | Network Interpolation |
| 丢包恢复 | 重传或跳过 | Reliable vs Unreliable Channel |
| 延迟观战 | 延迟数 Tick 播放 | Replay Buffer Pipeline |
| 状态快照 | 可重放 | Deterministic Simulation |
三、系统架构设计(Architecture)
graph TD
A1[Client A] -->|Input Frame| S[Server]
A2[Client B] -->|Input Frame| S
S -->|State Snapshot| A1
S -->|State Snapshot| A2
S --> L[Replay Logger]
S --> W[Watcher (Spectator)]
模块说明
| 模块 | 职责 |
|---|---|
| Server | 状态权威、Tick 调度、碰撞检测、回放记录 |
| Client | 输入预测、插值渲染、输入上报 |
| Replay Logger | 记录 Tick 状态 |
| Spectator | 延迟播放(延迟 N Tick) |
| Network Layer | 序列化 / 压缩 / 丢包恢复 |
四、Tick 同步与帧循环(Tick System)
4.1 Tick 模型
服务器以固定频率(60Hz)运行逻辑循环:
[
Tick = \frac{1}{FPS} = \frac{1}{60} \approx 16.67ms
]
每 Tick 执行:
- 收集所有玩家输入;
- 更新游戏状态;
- 发送状态快照(Snapshot)给客户端;
- 记录日志。
func GameLoop() {
for {
tickStart := time.Now()
ProcessInputs()
UpdatePhysics()
BroadcastSnapshot()
SleepUntilNextTick(tickStart, 16*time.Millisecond)
}
}
4.2 客户端预测(Client Prediction)
客户端不等待服务器确认,而是立即执行输入预测:
用户按下“上” → 客户端立即移动挡板 → 同时上报输入。
当服务器状态返回后,客户端进行“回滚与重演”:
predicted_state = apply(local_inputs)
authoritative_state = server_state
if predicted_state ≠ authoritative_state:
rollback to server_state
reapply unconfirmed inputs
4.3 插值与外推(Interpolation & Extrapolation)
插值(Interpolation):平滑过渡历史状态
- 当前渲染延迟 100ms,渲染的是 Tick-6 状态;
- 连续两帧插值让运动平滑。
外推(Extrapolation):短期预测未来状态
- 当丢包时,根据速度和方向预测物体位置。
五、服务器权威逻辑(Server-Authoritative Core)
5.1 状态结构
type GameState struct {
Tick int
Ball Vector2
PaddleA Vector2
PaddleB Vector2
Velocity Vector2
ScoreA int
ScoreB int
}
5.2 权威更新逻辑
func UpdatePhysics() {
Ball.X += Velocity.X * DeltaTime
Ball.Y += Velocity.Y * DeltaTime
if Collide(PaddleA) || Collide(PaddleB) {
Velocity.X *= -1
}
}
5.3 快照广播
{
"tick": 183,
"ball": [250.3, 141.2],
"paddleA": [10, 120],
"paddleB": [490, 128],
"vel": [3.2, -2.8]
}
六、网络同步机制(Networking Model)
| 通信方向 | 内容 | 协议 | 特点 |
|---|---|---|---|
| Client → Server | 输入帧(InputFrame) | UDP / QUIC | 不可靠、带序号 |
| Server → Client | 状态快照(Snapshot) | UDP / WS Binary | 小包高频 |
| Client ↔ Server | 心跳包(Ping/Pong) | WS Text | 维持连接 |
丢包检测
- 包序号差距 > 1 → 丢包;
- 客户端可请求补发关键帧(Key Snapshot)。
七、观战延迟管线(Spectator Delay Pipeline)
原理
观战者不能与实时玩家看到相同 Tick,
否则可能暴露对战信息(作弊)。
解决方案:
- 延迟 N Tick(例如 300ms = 18 Tick);
- 使用 Replay Buffer 实现延迟播放。
graph LR
S[Server] -->|Tick Data| B[Replay Buffer]
B -->|N Tick Delay| W[Spectator Clients]
八、回放系统(Replay System)
数据格式
{
"tick": 233,
"inputs": {
"A": "Up",
"B": "None"
},
"state": {
"ball": [230, 110],
"vel": [3, -2]
}
}
功能
- 按 Tick 顺序记录;
- 支持 deterministic 重演;
- 用于 debug、反作弊与回放回顾。
九、延迟、抖动与丢包补偿(Latency & Jitter Compensation)
| 问题 | 策略 | 说明 |
|---|---|---|
| 延迟(Latency) | 客户端预测 | 隐藏控制滞后 |
| 抖动(Jitter) | 抖动缓冲区(Jitter Buffer) | 缓冲 2–3 帧 |
| 丢包(Loss) | KeyFrame 重传 | 每 10 Tick 发关键帧 |
| 不一致(Desync) | 状态回滚 | 重演逻辑修正 |
十、性能与验证指标(Metrics & KPI)
| 指标 | 目标 | 说明 |
|---|---|---|
| Tick 同步误差 | ≤1ms | 服务器对齐 |
| 平均延迟 | ≤80ms | 可接受游戏体验 |
| 丢包率耐受 | ≤5% | 有效纠错 |
| 插值平滑度 | ≥95% | 无跳帧 |
| FPS 稳定性 | 60 ± 1 | 稳定逻辑帧率 |
| 观战延迟 | 300ms 固定 | 公平性保证 |
十一、技术选型(Tech Stack)
| 层 | 技术 | 理由 |
|---|---|---|
| 网络层 | QUIC / WebSocket Binary | 支持可靠与不可靠混合 |
| 引擎层 | Go + ECS 架构 | 高性能逻辑更新 |
| 时间同步 | NTP + Ping Offset | Tick 对齐 |
| 存储层 | Redis Stream + PostgreSQL | 回放存档 |
| 客户端 | Pixi.js / Phaser / Unity WebGL | 60 FPS 渲染 |
| 压测工具 | Locust + k6 | 模拟输入流量 |
十二、扩展功能(Extensions)
Rank Elo 匹配系统
- 参考第 2 章 RPS;
- 基于胜负更新积分。
延迟自适应 Tick
- 低延迟玩家 Tick=60Hz;
- 高延迟客户端动态插值 Tick。
AI 代打调试模式
- AI 模拟输入行为;
- 用于测试同步延迟效果。
帧压缩与差分广播
- 每 10 帧仅广播差异字段;
- 节省 70% 带宽。
自动录像导出
- 以帧为单位导出视频;
- 用于比赛分析与作弊检测。
十三、压测与运维(Load & Ops)
| 测试场景 | 模拟行为 | 目标 |
|---|---|---|
| 10 万 Tick 持续运行 | 负载稳定 | CPU < 80% |
| 高丢包网络 | 10% 丢包率 | 游戏仍可恢复 |
| 跨区域延迟 | 200ms RTT | 延迟补偿成功 |
| 高频广播 | 每秒 120 帧 | 无丢包 |
| 观战同步 | 100 观众延迟播放 | 延迟差 < 50ms |
监控指标:
tick_rate_actuallatency_avg_mspacket_loss_raterollback_totalsnapshot_broadcast_total
十四、设计反思与演化(Reflection & Evolution)
14.1 关键洞察
“Pong 对战”是实时游戏系统的真正考验。
它揭示了**“一致性 vs 延迟”**的永恒矛盾。
—— 越一致,越慢;越实时,越不同步。
因此所有实时游戏都在做一件事:
“制造延迟的幻觉,让玩家相信这就是实时。”
14.2 架构哲学
- 客户端预测是玩家体验的基石;
- 服务器权威是公平性的核心;
- 插值算法是视觉真实性的关键;
- Tick 驱动是物理世界的节奏。
14.3 下一阶段演化
| 能力 | 下一原型 |
|---|---|
| AOI + 区域广播 | → #7 贪吃蛇大作战 |
| 多人同步(>10 人) | → #8 战舰/UNO |
| 状态分片(Shard) | → #10 城格建造 mini-SLG |
| 回滚重演 | → #16 实时战斗核心框架 |
十五、总结
Pong 是网络同步的真理之镜。
在短短 16.67ms 的时间里,你必须完成:
- 输入收集;
- 状态更新;
- 校准;
- 广播;
- 渲染;
- 审计;
- 存档。
任何一环延迟或丢失,整个世界就“不同步”。
因此,Pong 对战 不是一个游戏,而是实时系统架构的微缩宇宙。
你可以把它当作「物理 + 网络 + 时间」的交响乐。
“当两块挡板、一个球和 60 Tick 同步地在全球弹跳时——
你就理解了分布式的灵魂。”
相关技术栈与进一步学习
本原型验证的核心技术:帧同步 60 FPS、确定性碰撞检测、客户端预测与服务端权威
| 相关教程 | 关联内容 |
|---|---|
| 《Rust 游戏服务端性能优化》 | — |
| 《游戏服务端进阶知识储备》 | — |
💡 如果你想系统性地理解游戏服务端开发的全栈知识体系,可以参考
《产品原型开发指南》目录页,
了解从「联机 Hello World」到「永续自治世界」的完整学习路线。
核心概念定义
帧同步(Frame Synchronization):一种实时游戏网络同步方案:所有客户端和服务器以固定帧率(如 60 FPS = 每 16.67ms 一帧)推进游戏逻辑,每帧收集所有玩家的输入指令,统一计算后分发状态,确保严格一致。
客户端预测(Client-Side Prediction):玩家在本地立即响应输入并渲染结果,同时向服务端发送操作指令;服务端权威结果返回后,客户端进行差异校正(Rewind & Replay),在性能和一致性之间取得折中。
确定性物理(Deterministic Physics):一种通过固定时间步长、固定初始状态、以及避免浮点误差累积来保证所有客户端在相同输入序列下产生完全一致的物理模拟结果的算法设计原则。
常见问题解答(FAQ)
以下问答帮助快速回顾本原型的核心要点,也有助于搜索引擎与大模型更好地理解内容主题。
Q1: Pong 的球速越快,不同步问题越严重吗?
是的。高速运动物体在帧间移动距离更大,客户端预测与服务端权威的差异会更明显(如出现「穿墙」或「拍空」视觉 bug)。好的做法是服务端进行连续碰撞检测(CCD)。
Q2: 帧同步必须用固定帧率吗?
是的,帧同步的核心假设就是所有参与者以相同的固定步长推进模拟。如果不固定,不同设备的浮点误差和更新节奏会导致状态逐渐分叉。
Q3: 客户端预测失败后的回滚体验差怎么办?
采用插值平滑修正(如 PlayCanvas 的 smoothCorrection)而非瞬间跳帧;对于一些轻微偏移(< 10px)可以忽略差异,只在大偏差时执行校正。
Q4: 不同机型帧率不一致怎么解?
引擎层固定逻辑更新为固定时间步(Fixed Timestep),渲染层做独立插值。例如逻辑固定 60 tick/s,渲染可按显示器刷新率自由插值,逻辑帧与渲染帧分离。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。