Go 多人游戏锁步帧同步服务器 — 30Hz 权威输入中继 + 房间管理。纯匹配模式。
go build ./...
go test ./...
go run main.go
# 监听 :8080| 目录 | 用途 |
|---|---|
main.go |
HTTP + WebSocket 入口,匹配逻辑 |
dispatch/ |
玩家注册、输入缓冲、帧广播 |
protocol/ |
消息类型定义 + JSON 线路格式 |
room/ |
房间生命周期(等待→游戏中→结束)、状态哈希校验 |
session/ |
WebSocket 连接封装 |
tick/ |
30Hz 定时循环 |
matchmaking/ |
匹配队列 |
/ws JSON 线路格式(Unity 客户端)
[4 字节 LE 长度][1 字节类型标签][JSON UTF-8]
类型标签与服务端消息的对应关系见 protocol/jsonwire.go。
| 消息 | 类型标签 | 说明 |
|---|---|---|
MatchmakingRequest |
5 | 发起匹配,含 playerName 和 shapeType |
PlayerInput |
2 | 每帧输入:moveX, moveY, jump, useSkill |
StateHash |
3 | 定期状态哈希用于一致性检测 |
Heartbeat |
4 | 保活 |
| 消息 | 类型标签 | 说明 |
|---|---|---|
MatchmakingStarted |
6 | 确认已加入匹配队列 |
JoinResponse |
1 | 匹配成功后分配 playerID |
GameStart |
2 | 广播:随机种子、玩家列表 |
FrameInput |
3 | 每 tick 广播:所有玩家的输入 |
GameOver |
4 | 广播:胜者 |
PlayerEliminated |
5 | 广播:玩家淘汰 |
客户端 → WebSocket → MatchmakingRequest → Queue.Enqueue
matchmakingTick (1s)
→ Queue.DequeueN(2)
→ 创建 Room + 分配 ID + 注册 dispatch
→ 发送 MatchmakingStarted + JoinResponse × 2
→ Room.StartGame()(直接开局,无倒计时)
游戏内:
客户端 → PlayerInput → dispatch.CollectInput
客户端 → StateHash → validator.CollectHash
tick 循环(30Hz)→ dispatch.BroadcastFrame(tick)
→ 组装 FrameInput → 广播至所有 session
断开:
→ 在队列中? → Queue.Remove(sess) : 进入 30 秒断线超时
waiting → playing → over
- waiting:等待匹配凑满 2 人
- playing:30Hz tick 循环广播帧输入,每 300 tick(10 秒)进行状态哈希校验
- over:游戏结束,tick 停止
断线后 30 秒超时移除槽位并广播淘汰。无重连机制——玩家需重新匹配。
每 300 tick(10 秒),客户端上报其确定性模拟的状态哈希。服务器收集至少 2 个玩家的哈希进行对比——若不一致表明存在确定性模拟分歧,记录 DIVERGENCE 日志。
- 服务器不运行游戏模拟,仅做输入中继 + 房间管理。客户端本地确定性运行。
- 纯匹配模式:客户端连接后发送
MatchmakingRequest入队,凑齐 2 人直接开局。无手动创建/加入房间。 - 仅支持 JSON 线路格式,无 Protobuf。
TryParseJSONClientFormat接收完整 WebSocket 帧(含 4 字节 LE 长度前缀),UnmarshalClientMessageJSON接收已剥离前缀的数据。