多人同步与持久化模块

07/033 浏览
本模块记录多人模式下必须优先考虑的即时反馈、持久化、重连和事件时序设计。
核心原则 TAGIRON 类推理游戏有隐藏信息,必须由服务端作为权威裁判。
服务端掌握:
完整牌堆。 所有玩家手牌。 中央隐藏牌。 问题卡牌堆。 当前回合。 已公开回答。 胜负判定。 客户端只掌握:
自己手牌。 公开问题卡。 公开回答。 可见玩家状态。 自己可执行的动作。 房间状态结构建议 lua
复制 RoomState = { roomId = "room_001", mode = "2p", phase = "asking", version = 1, players = {}, hands = {}, centerTiles = {}, questionDeck = {}, openQuestions = {}, usedQuestions = {}, currentTurnUid = nil, eliminated = {}, publicLog = {}, createdAt = 0, updatedAt = 0, } 注意:hands 和 centerTiles 是服务端私有状态,不能完整广播给所有客户端。
客户端动作命令 客户端只发送“我要做什么”,不发送“结果是什么”。
text
复制 CLIENT_READY CREATE_ROOM JOIN_ROOM LEAVE_ROOM PLAYER_READY ASK_QUESTION MAKE_GUESS REQUEST_FULL_SYNC REQUEST_RECONNECT 服务端广播事件 text
复制 ROOM_SYNC_FULL ROOM_PLAYER_JOINED ROOM_PLAYER_LEFT ROOM_STARTED TURN_CHANGED QUESTION_ASKED QUESTION_ANSWERED GUESS_RESULT PLAYER_ELIMINATED GAME_OVER ERROR_MESSAGE 所有事件建议带:
lua
复制 { roomId = roomId, version = room.version, serverTime = now, payload = data } 完整快照同步 必须支持 ROOM_SYNC_FULL。
用于:
首次进入房间。 刷新重连。 事件丢失后修复。 版本号跳变时恢复。 不同玩家收到的快照不同。
二人模式快照 玩家只能看到:
自己完整手牌。 对手手牌数量。 公开问题卡。 公共日志。 当前回合。 游戏阶段。 不能看到:
对手手牌内容。 三四人模式快照 玩家只能看到:
自己完整手牌。 其他玩家手牌数量。 中央牌数量。 公开问题卡。 公共日志。 出局状态。 不能看到:
其他玩家手牌内容。 中央牌内容。 异步时序:pendingClients 模式 常见问题:
text
复制 客户端 CLIENT_READY 已到 服务端 RoomState 还没从云端加载完成 如果直接 return 客户端永远收不到 ROOM_SYNC_FULL 正确模式:
lua
复制 if not room.loaded then table.insert(room.pendingClients, connection) return end
SendFullSync(connection) 加载完成后:
lua
复制 for _, conn in ipairs(room.pendingClients) do SendFullSync(conn) end room.pendingClients = {} 初始化保护期 危险流程:
text
复制 Start initDefaultRoom saveRoom loadCloudRoom 这会用默认状态覆盖真实云端状态。
正确流程:
lua
复制 initializing = true LoadRoomFromCloud() ApplyCloudRoom() initializing = false SaveRoomIfNeeded() 保存函数必须检查:
lua
复制 if initializing then return end 版本号机制 每次服务端改变房间状态:
room.version = room.version + 1 客户端保存本地版本号:
收到旧版本:忽略。 收到连续新版本:应用。 发现跳号:请求 REQUEST_FULL_SYNC。 持久化分类 房间短期状态 用于断线重连和刷新恢复。
建议 key:
tagiron_room_{roomId}_state 保存时机:
房间创建。 发牌完成。 每次提问后。 每次推测后。 玩家出局后。 游戏结束后。 玩家断线时。 玩家长期状态 建议 key:
text
复制 tagiron_player_{uid}profile tagiron_player{uid}_stats 内容:
昵称。 胜场。 败场。 平局。 总局数。 教程进度。 偏好设置。 排行榜整数 排行榜字段必须单独保存为整数。
示例:
text
复制 tagiron_rank_score tagiron_rank_wins 写入策略 采用:
脏标记 + 定时保存 + 关键节点即时保存 + 断线 flush 建议:
普通状态 10 秒自动保存。 胜负、发牌、出局等关键节点立即保存。 断线时主动 flush 当前快照。 防止隐藏信息泄漏 任何发送给客户端的数据都要经过过滤。
禁止广播:
所有玩家完整手牌。 中央牌内容。 未公开的问题牌堆顺序。 推测判定前的真实答案。 允许广播:
自己手牌。 其他玩家手牌数量。 中央牌数量。 已公开回答。 已使用问题。 胜负结果。 日志建议 第一版必须保留详细日志:
text
复制 [Room] Create roomId=... [Room] Load start roomId=... [Room] Load done roomId=... version=... [Net] CLIENT_READY uid=... roomLoaded=... [Net] pending client added uid=... [Net] Send ROOM_SYNC_FULL uid=... version=... [Action] ASK_QUESTION uid=... qid=... [Action] MAKE_GUESS uid=... target=... [Persist] Save roomId=... version=... [Persist] Load roomId=... version=... [Error] Reject action reason=... 排查清单 遇到刷新后状态错误或便签式数据不显示的问题,按顺序检查:
事件是否注册。 RemoteEvent 是否被引擎丢弃。 云端 key 是否一致。 回调触发时消费者是否就绪。 初始化是否覆盖云端真实状态。 是否有 pending 请求被直接 return 丢弃。 是否有完整快照兜底。 是否有断线 flush。 是否存在并行回调写同一个变量。 是否所有 error 分支都避免盲写。 结论 多人模式的重点不是 UI,而是:
服务端权威。 隐藏信息安全。 刷新重连恢复。 异步请求不丢。 初始化不覆盖。 关键节点持久化。 完整快照兜底。 玩家昵称持久化与异步覆盖保护 公共房间大厅中,玩家昵称属于“玩家长期状态 + UI 身份展示”数据。它不参与对局裁判,但会影响房间列表、等待页、日志和测试可识别性。
昵称优先级 推荐优先级:
text
复制 云端自定义昵称 user_nickname
本地昵称缓存 bagua_player_pref.txt TapTap 平台昵称 默认“玩家” 为什么不能直接用 TapTap 昵称覆盖 危险时序:
text
复制 玩家手动改昵称为 A ↓ 刷新重进 ↓ 客户端先显示默认值 ↓ 服务端 GetUserNickname 异步返回 TapTap 昵称 B ↓ 如果无条件应用 B,就会覆盖 A 因此 TapTap 昵称只能作为“没有自定义昵称时的默认值”,不能作为最高优先级。
本地 + 云端双层持久化 本地文件用于快速恢复当前设备昵称:
bagua_player_pref.txt 云端变量用于账号级恢复:
user_nickname 手动修改昵称时必须同时执行:
text
复制 更新内存 写本地文件 写 clientCloud user_nickname 向服务端发送 CustomName=true 刷新 UI 云端读取的版本号保护 云端读取是异步的,必须防止旧回包覆盖玩家刚刚输入的新昵称。
建议模式:
lua
复制 local requestVersion = playerNameVersion_ clientCloud:Get("user_nickname", { ok = function(values) if playerNameVersion_ ~= requestVersion then return end -- 只有期间玩家没有再次修改昵称,才应用云端值 end }) 服务端 TapTap 昵称回包保护 客户端如果使用自定义昵称,应在房间协议中携带:
CustomName = true 服务端记录:
customNameKeys_[connectionKey] = true GetUserNickname 回调回来后:
lua
复制 if not customNameKeys_[key] then UpdateConnectionName(connection, tapTapNick) end 这样 TapTap 昵称不会覆盖用户自定义昵称。
推荐测试流程 [ ] 新用户首次进入时,默认显示 TapTap 昵称。 [ ] 修改昵称后,房间卡片和等待页立即显示新昵称。 [ ] 刷新/重进后仍显示自定义昵称。 [ ] 云端旧昵称晚到不会覆盖刚修改的新昵称。 [ ] TapTap 昵称查询晚到不会覆盖自定义昵称。 [ ] 换设备登录同一账号时,能恢复云端 user_nickname。
3