公共房间大厅与玩家身份插件模块 V2

07/0411 浏览
本模块总结当前《猜卦》多人外壳层的设计结果,目标是把“进入同一局的组织方式”插件化:公共大厅展示房间、玩家创建/加入房间、房主控制开局、AI 补位、玩家昵称读取与持久化。
本模块不处理卡牌规则、问题卡结算、Buff、资金、宝石或胜负算法。它只负责决定:哪些玩家进入同一个房间,以及这些玩家在大厅与房间等待页中如何显示。
模块定位 text
复制 RoomLobbyPlugin ├─ PublicRoomList 公共房间列表 ├─ RoomJoinFlow 创建/加入/离开房间 ├─ RoomOwnerPermission 房主权限 ├─ RoomAIFill 房间级 AI 补位 ├─ PlayerIdentity TapTap 昵称、自定义昵称、云端/本地持久化 ├─ MobileShell 移动端字体、竖屏、扫码测试 └─ RoomLobbyUI 固定槽位大厅 UI 已验证目标 当前原型已验证或已接入:
多人常驻服配置开启,多个手机可进入同一个公共大厅实例。 主界面改为公共房间大厅。 第一版不做复杂滚动,只显示固定 8 个房间槽位。 玩家创建房间后,服务端生成 6 位房号。 房间会出现在大厅固定槽位中。 其他玩家可点击房间卡片加入,也可输入房号加入。 房间等待页显示房间号、玩家一、玩家二。 房主按钮显示“开始游戏 / 邀请阿墨 / 离开”。 非房主不显示开局按钮,只显示等待房主和离开。 对局广播限定在当前房间内,不广播给其他房间。 移动端字体改为显式 MiSans,解决手机端 UI 无字问题。 测试配置改为竖屏。 玩家昵称默认读取 TapTap 昵称,允许手动修改。 自定义昵称支持本地 + 云端持久化,避免刷新重进后被 TapTap 昵称覆盖。 公共大厅设计 大厅是房间列表,不是对局本身。
text
复制 公共房间大厅 ├─ 房间槽位 1 ├─ 房间槽位 2 ├─ 房间槽位 3 ├─ 房间槽位 4 ├─ 房间槽位 5 ├─ 房间槽位 6 ├─ 房间槽位 7 └─ 房间槽位 8 固定槽位原则 第一版只做固定 8 个槽位:
有等待房间时显示房间卡片。 没有房间时显示“空房间位”。 不做滚动。 不做分页。 超过 8 个房间时后续再扩展分页或滚动列表。 房间卡片显示 建议字段:
text
复制 房间 582913 房主:昵称 1/2 等待中 点击加入 卡片点击行为:
text
复制 点击卡片 ↓ 客户端 JoinRoom(room.roomId, playerName) ↓ 服务端校验房间存在、等待中、未满 ↓ 加入成功后广播 ROOM_UPDATED 和 ROOM_LIST_UPDATED 大厅按钮 当前保留:
text
复制 和AI对战 创建房间 加入房间 刷新 退出游戏 房间边界 房间是对局边界。所有对局消息必须限定在当前房间内。
禁止:
text
复制 A 房间开局 → B 房间收到选角/发牌/结算 A 房间 AI 补位 → 把大厅里的其他真人拉入对局 A 房间破译结果 → 广播给全服 服务端应维护:
lua
复制 rooms_ = { [roomId] = { roomId = "582913", ownerKey = "uid:xxx", status = "waiting", -- waiting / playing / closed mode = "pvp", -- pvp / ai players = { { key = "uid:1", connection = connA, name = "玩家A", seat = 1, isAI = false }, { key = "uid:2", connection = connB, name = "玩家B", seat = 2, isAI = false }, }, gameState = nil, } } 大厅列表快照只暴露公开字段:
lua
复制 { roomId = "582913", ownerName = "玩家A", playerCount = 1, maxPlayers = 2, status = "waiting", } 不暴露房间内部 gameState、手牌、问题堆。
房主权限插件 房主是创建房间的玩家。
房主可见按钮 text
复制 开始游戏 邀请阿墨 离开 非房主可见按钮 text
复制 等待房主 离开 服务端必须二次校验 客户端隐藏按钮只是体验优化,服务端必须校验:
lua
复制 if room.ownerKey ~= connectionKey then SendError(connection, "只有房主可以开始游戏。") return end 同理,INVITE_AI 也必须只允许房主调用。
AI 补位设计 AI 补位是房间级行为。
当前二人模式:
text
复制 1 真人 + 邀请阿墨 → 1 真人 + 1 AI 开局 2 真人 → 开始游戏走 PVP 禁止按全服在线人数补位。
玩家身份与昵称持久化插件 设计目标 解决多人大厅里所有玩家都显示“小白”的问题,并支持:
默认读取 TapTap 昵称。 玩家可手动修改昵称。 修改后的昵称刷新/重进后仍保留。 手动昵称不能被 TapTap 昵称的异步回包覆盖。 换设备时可通过云端昵称恢复。 昵称优先级 最终优先级:
text
复制 云端 user_nickname
本地 bagua_player_pref.txt TapTap 昵称 默认“玩家” 本地持久化 客户端本地保存:
bagua_player_pref.txt 用途:
进入游戏时立即显示上次自定义昵称。 避免等待云端读档导致输入框闪烁。 在当前设备上离线/弱网时仍可恢复昵称。 云端持久化 客户端云变量 key:
user_nickname 写入时机:
text
复制 玩家点击“修改”昵称 ↓ 更新内存 ↓ 写本地文件 ↓ clientCloud:Set("user_nickname", name) ↓ 发送 CustomName=true 给服务端 读取时机:
text
复制 Start ↓ LoadLocalNickname() ↓ UI 初始化 ↓ LoadCloudNickname() ↓ 如果云端有 user_nickname 且期间玩家没再次修改 → 应用云端昵称 时序保护 使用版本号防旧回包覆盖新输入:
lua
复制 local requestVersion = playerNameVersion_ clientCloud:Get("user_nickname", { ok = function(values) if playerNameVersion_ ~= requestVersion then return end -- 只有版本未变化,才应用云端昵称 end }) 避免:
text
复制 进入游戏发起云端读取 ↓ 玩家立即把昵称改成 A ↓ 旧云端昵称 B 晚到 ↓ 错误覆盖 A TapTap 昵称回包保护 客户端向服务端发送:
CustomName = true 服务端记录:
customNameKeys_[connectionKey] = true 当 GetUserNickname 查到 TapTap 昵称时:
lua
复制 if not customNameKeys_[key] then UpdateConnectionName(connection, tapTapNick) end 这样自定义昵称不会被 TapTap 昵称覆盖。
移动端外壳插件 字体 移动端必须显式指定字体,不依赖默认 fallback。
当前配置:
lua
复制 UI.Init({ theme = CreateTheme(), fonts = { { family = "sans", weights = { normal = "Fonts/MiSans-Regular.ttf", bold = "Fonts/MiSans-Bold.ttf", } }, }, scale = UI.Scale.DEFAULT, }) 同时所有通过辅助函数创建的 UI.Label 明确设置:
props.fontFamily = "sans" 竖屏 手机测试默认竖屏:
"screen_orientation": "portrait" 常驻大厅配置 公共房间大厅必须保证所有测试账号进入同一个游戏内大厅服务端,否则不同手机可能看不到彼此房间。
当前项目的已验证入口配置:
json
复制 { "multiplayer": { "enabled": true, "max_players": 20, "background_match": true, "match_info": { "desc_name": "free_match", "immediately_start": false, "match_timeout": 0, "player_number": 1 }, "persistent_world": { "enabled": true } } } 说明:
max_players = 20 表示大厅容量,不是单房间人数。 单房间当前仍是 2 人。 immediately_start = false 防止玩家刚进服就被拆成独立局。 不使用 free_match_with_ai 做公共大厅测试。 协议事件建议 客户端到服务端:
text
复制 CLIENT_READY REQUEST_ROOM_LIST CREATE_ROOM JOIN_ROOM LEAVE_ROOM INVITE_AI START_PVP ASK_QUESTION SUBMIT_GUESS 服务端到客户端:
text
复制 PLAYER_NAME ROOM_LIST_UPDATED ROOM_UPDATED GAME_SNAPSHOT GUESS_RESULT ERROR 插件拆分建议 当前实现仍集中在少量文件中。后续可拆为:
text
复制 scripts/network/RoomLobbyServer.lua scripts/network/RoomLobbyClient.lua scripts/ui/RoomLobbyScreens.lua scripts/profile/PlayerIdentity.lua scripts/profile/NicknamePersistence.lua scripts/platform/MobileShell.lua 拆分后主入口只负责装配:
lua
复制 RoomLobbyClient.Start({ identity = PlayerIdentity, ui = RoomLobbyScreens, onGameStart = EnterTable, }) 测试清单 大厅测试 [ ] 手机 A、B、C 都能进入同一个公共大厅。 [ ] A 创建房间后,B/C 能看到房间卡片。 [ ] B 点击房间卡片加入 A 的房间。 [ ] A 房间页能看到 B 昵称。 [ ] 非房主看不到“开始游戏”。 [ ] 房主能开始游戏或邀请阿墨。 2026-07 联机回归经验:外层匹配配置是大厅插件边界 本项目的“公共房间大厅”是游戏内插件,不是 TapTap 外层匹配大厅。修复房间加入、断线重连、掉线判负等问题时,禁止随意切换外层多人配置。
已验证的正确入口形态 玩家启动后应该直接进入游戏内界面:
text
复制 猜卦 公共房间大厅 ├─ 和AI对战 ├─ 创建房间 ├─ 加入房间 ├─ 刷新 └─ 排行榜 不应该进入平台外层界面:
text
复制 TapTap 制造 ├─ 快速匹配 ├─ 浏览房间 └─ 创建房间 如果出现平台外层匹配大厅,说明 .project/settings.json 的多人运行模式被改成了不适合本项目的房间制匹配入口。
当前大厅插件依赖的外层配置 以下是 2026-07 三账号实测通过的入口配置;调整前必须先确认不会进入 TapTap 外层匹配大厅:
json
复制 { "multiplayer": { "enabled": true, "max_players": 20, "background_match": true, "match_info": { "desc_name": "free_match", "immediately_start": false, "match_timeout": 0, "player_number": 1 }, "persistent_world": { "enabled": true } } } 含义:
max_players = 20:单个公共大厅服务端可承载的连接上限。 player_number = 1:让玩家进入后立刻加载游戏脚本,不等待外层凑人。 background_match = true:让客户端先进游戏脚本,由脚本内大厅组织房间。 persistent_world.enabled = true:公共大厅服务端持续存在,房间表 rooms_ 由游戏脚本维护。 单个房间 2/3/4 人由 RoomLobbyPlugin 的 seatCount 控制,不由外层 player_number 控制。 回归原因记录 一次错误排查中,曾将配置改为:
json
复制 { "background_match": false, "match_info": { "player_number": 20, "immediately_start": false }, "persistent_world": { "enabled": false } } 结果玩家进入 TapTap 外层匹配大厅,绕过了游戏内公共房间大厅,属于错误方向。
结论:
游戏内大厅问题 ≠ 外层匹配配置问题 除非目标明确是使用平台外层匹配,否则不要把本项目切到 persistent_world=false 的平台房间制。
排查联机问题的正确顺序 当出现“玩家 A 创建房间,玩家 B 输入房号进不去”时,按以下顺序排查:
确认所有设备进入的是游戏内公共房间大厅,而不是 TapTap 外层匹配大厅。 查看服务端日志是否出现: [LobbyServer] Started persistent room shell [LobbyServer] Client ready: ... [LobbyServer] Room created: ... [LobbyServer] ... joined room: ... 如果有 Discarding not allowed remote event,优先检查服务端入口是否调用了 Server.Start()。 如果排行榜昵称显示“玩家”,先区分是 TapTap 昵称查询失败,还是玩家未写入 user_nickname;不要直接判定为房间联机失败根因。 不要通过改 player_number、persistent_world、background_match 来试探修复,除非明确要改变外层入口形态。 修复后必须三账号实测:耳内有灰、西米、coco 互相创建/加入房间。 插件化边界 RoomLobbyPlugin 负责:
游戏内房间列表。 6 位房号创建和加入。 房主/非房主 UI。 房间内 AI 补位。 玩家昵称显示。 外层多人配置只负责:
让玩家进入同一个游戏脚本服务端。 保持公共大厅服务端生命周期。 不要让外层 TapTap 匹配大厅替代 RoomLobbyPlugin。后续好友邀请、在线好友、最近对局等功能,也应基于游戏内大厅插件扩展,而不是切换到平台外层房间入口。
昵称测试 [ ] 新用户首次进入默认显示 TapTap 昵称。 [ ] 修改昵称后,房间卡片和等待页立即显示新昵称。 [ ] 刷新/重进后仍显示自定义昵称。 [ ] 云端昵称晚到不会覆盖刚修改的新昵称。 [ ] TapTap 昵称查询晚到不会覆盖自定义昵称。 [ ] 换设备登录同一账号时能恢复云端 user_nickname。 移动端测试 [ ] 手机端大厅文字正常显示。 [ ] 手机端按钮文字正常显示。 [ ] 默认竖屏显示。 [ ] 扫码白名单用户可进入测试。 当前代码落点 text
复制 scripts/network/Shared.lua 协议消息枚举 scripts/network/Server.lua 房间列表、房间状态、房主权限、TapTap 昵称兜底 scripts/network/Client.lua 客户端连接、房间协议、CustomName 标记 scripts/ui/LobbyScreens.lua 公共大厅 UI、固定 8 槽房间卡片、昵称输入 scripts/main.lua 昵称本地/云端持久化、UI 装配、牌桌入口 .project/project.json 竖屏测试配置 .project/settings.json 常驻多人大厅配置
3