111 lines
10 KiB
Markdown
111 lines
10 KiB
Markdown
# 公共 UI 接线设计:启动 → 登录 → 大厅 → 进房
|
||
|
||
日期:2026-09-05。基线:`master@8eae325`。
|
||
|
||
## 1. 目标与实施边界
|
||
|
||
这是总设计 `2026-09-04-framework-subgame-zero-coupling-migration-design.md` 第三阶段的第一个子项目:把已完成的 PlatformRuntime 接到可测试的公共 UI Presenter。先交付不依赖 Cocos 的接线核心,再进行编辑器接管。
|
||
|
||
本轮产物为设计与实施计划,不运行实施任务。下一实施批次称为 **3A:UI 接线核心**,包括 Runtime 只读观察、ViewModel 投影、命令入口、同步 ScenePort 适配与真实 Runtime 回放测试。
|
||
|
||
**3B:Cocos 实际接管**单独实施:需要编辑器确认节点、挂载与持久化,且须先取得修改指定序列化资源的明确授权。用户现有“不修改任何 `.scene/.prefab/.anim/.meta`”限制继续有效,不能因为有 MCP 就绕过。3A 不启动编辑器导入,不写这些文件,也不声称实际界面已经可用。
|
||
|
||
主题扩展、SemanticAssetKey/ExtensionSlot 完整实现、创房规则页、聊天/商城/设置、首个真实子游戏均不在 3A 内。现有主题模块不复制、不重构。
|
||
|
||
## 2. 已核对的现状
|
||
|
||
- `assets/framework/platform/runtime.ts` 已拥有唯一 Store、WireClient、Router、GameSessionHost,公开 `state`、`ready`、登录/进房/准备/退出命令,但没有 UI 订阅入口。
|
||
- Store 为不可变快照,订阅不立即回调;其通用监听异常处理只记录日志。UI 需要显式错误通道,不能让渲染故障只留日志。
|
||
- `ScenePort` 是同步接口。当前房间时序为 module attach、`room.entered`、`showRoom()`、可选 `restore(deskinfo)`。不得将 `showRoom()` 实现为未等待的异步场景切换。
|
||
- 当前 `assets/scenes` 只有 Login 场景;旧 `LoginFlow.ts` 跳转 MainMenu,且自行建立旧网络栈、内置调试账号。`LaunchFlow.ts`、`SceneStart.ts`、`RoomEventProbe.ts` 也包含演示流程,不是新 Runtime 入口。
|
||
- 已迁移 Login_Layer、Layer4_MainMenu、Layer15_JoinRoom、Layer50_MainScene、Layer202_PlayerHeadScore 及 loading/reconnect/kick 等 Prefab。存在资源不等于已确认按钮语义或绑定。
|
||
- 两个旧 PlayerInfoView 使用相同 ccclass 名,且含下游默认值。新核心不依赖它们;是否阻碍实际编辑器接管由 3B 预检决定,不能随手修改旧资源引用。
|
||
- 旧 `net/cocos-transport.ts` 含可选发送、字符串强转和吞异常,不能不经契约测试就作为现代生产适配器。
|
||
|
||
以上路径均相对 `cocoscreator_projects/YouleNexus`。已废止的 `2026-09-04-platform-vertical-slice.md` 不得执行。
|
||
|
||
## 3. 方案选择
|
||
|
||
选择单场景常驻根、页面预加载、同步页面激活。3A 用严格 ViewPort 验证该语义,不创建 Cocos 节点;3B 用组件引用实现同一端口。
|
||
|
||
另外两个选项不采用:
|
||
|
||
- 多场景异步导航需要修改 Runtime/Host 的进房恢复时序,扩大已验证生命周期契约的变更面。
|
||
- 直接给旧 LoginFlow/RoomSceneStart 接新网络对象会保留双 Store/双网络栈及下游默认值,无法满足唯一来源。
|
||
|
||
3B 的所有基础页面、公共座位容器和必需绑定须在 `loadResources()` 完成前准备好。加载画面本身由启动根预先具备,不依赖这次异步加载。`showRoom()` 只投影最新快照、同步渲染并激活已就绪页面;不加载资源、不发包、不初始化第二个 GameSessionHost。
|
||
|
||
## 4. 核心接口与所有权
|
||
|
||
新增 `framework/presentation/`,允许依赖 platform 的只读类型、现有 selector 与 ScenePort;不导出到 SDK,不允许子游戏反向导入。View 不得到 Store、WireClient、Router 或协议包构造器。
|
||
|
||
Runtime 增加:
|
||
|
||
```ts
|
||
subscribeState(
|
||
listener: (state: PlatformState) => void,
|
||
onError: (error: unknown) => void,
|
||
): () => void;
|
||
```
|
||
|
||
订阅不立即通知;通知交付 Store 提交的原快照,不复制、修补或另建响应式状态。调用者订阅后同步读取 `state` 获取初值。取消幂等;stop/fatal 清理观察者;终止后新订阅显式拒绝。某监听器抛错时先取消该监听器,再将原错误交给必需 onError,其他监听器仍可接收。onError 也是可抛的外部回调:复用现有清理/错误聚合路径,原错误不可丢失;不得修改通用 Store 的订阅语义来满足 UI。
|
||
|
||
Presenter 接口:
|
||
|
||
```ts
|
||
type UiRuntime = Pick<PlatformRuntime,
|
||
'state' | 'ready' | 'subscribeState' | 'login' | 'joinRoom' | 'prepare' | 'exitRoom'>;
|
||
interface FrameScheduler { request(callback: () => void): () => void; }
|
||
interface PlatformViewPort {
|
||
render(model: PageModel): void;
|
||
activate(page: PageId): void;
|
||
showOverlay(model: OverlayModel): void;
|
||
}
|
||
```
|
||
|
||
FrameScheduler 必须异步、至多调用一次;返回取消函数。测试用手动帧,3B 才使用 Creator 更新周期。Presenter 拥有订阅与帧取消;外层应用拥有 Runtime.stop。清理与错误处理不得相互递归复活。
|
||
|
||
`PlatformUiController` 实现 ScenePort,构造参数为 ViewPort、FrameScheduler、必需 `onFault(error: unknown): void`,随后 `connect(runtime: UiRuntime): void` 一次。外层顺序:构造 controller → 用 controller 作为 ScenePort 构造 Runtime → connect → Runtime.start。controller 不调用 Runtime.start/stop、不选择 GameEntry/渠道/账号来源。
|
||
|
||
## 5. ViewModel 与页面状态
|
||
|
||
`PageId = 'loading' | 'login' | 'lobby' | 'room'`。PageModel 是对应判别联合:loading 无业务字段;login 携带 canLogin;lobby 携带必需 self;room 携带 roomcode、selfSeat、动态 seats、canPrepare、canExit。玩家公共显示字段仅 playerid/nickname/avatar/bean。
|
||
|
||
SeatModel 为 `empty` 或 `occupied`,都携带服务器 seat 索引,occupied 额外携带 player、ready、offline。数组长度严格等于权威 `seatPlayerIds.length`;不固定为 4/8,不压缩空位,不解析 roomtype 推测布局。2/4/10 座位都须验证。
|
||
|
||
已登录页缺少 self 或 occupied 缺实体必须报错;空座位是来源明确的 null 语义,不是伪造匿名玩家。头像 URL 原样交给 View,资源失败的视觉策略不在核心中猜测。
|
||
|
||
canPrepare 在 room.needprepare 为 1、自身未准备、stage 为 0 且连接 phase 为 logged-in 时为真;canExit 在 stage 为 0 或 infinite 为 1,且 phase 为 logged-in 时为真。这是本批 UI 的可用性策略,不是新增服务器规则。Runtime/Commands 仍为命令合法性的最终执行入口。
|
||
|
||
页面只由 ScenePort 决定,不能同时通过 app.phase 推断第二套导航。快照通知只触发当前页面更新。页面切换取消待处理旧帧、同步生成最新模型并 render,然后 activate;render 抛错不得激活半成品页面。room 首帧必须在 showRoom 返回前完成,保证既有 restore 时序。
|
||
|
||
同一帧多次状态变化只渲染最新值。dispose、fatal、kick 后旧帧/旧订阅无效。生命周期检查必须覆盖 View 回调重入 dispose,不能仅在入口检查一次。
|
||
|
||
## 6. 操作、拒绝与错误
|
||
|
||
controller 暴露 `login(account: LoginAccountIdentity)`、`joinRoom(command: JoinRoomCommand)`、`prepare()`、`exitRoom()`、`dismissDenial()` 与 `dispose()`,均返回 void。
|
||
|
||
- 登录只在 login 页且 runtime.ready 时转发;大厅进房只在 lobby 页;prepare/exit 只在 room 且模型对应能力为真时转发。界面本地误用显式抛出,不偷偷缓存请求。
|
||
- account/JoinRoomCommand 原引用转发;不填 mock_openid、ip、location、gameid,不裁剪/重写 roomcode,不擅加正则或长度协议。账号/设备/进房环境由外层权威适配器完整供给。
|
||
- 本批不新增超时、重连策略或通用请求 pending 状态机;网络策略沿用 Runtime。按钮节流/账号异步获取另有来源契约后再实施。
|
||
- OverlayModel 为 none/reconnect/denial/kicked/fatal 判别联合。denial 保存完整 ServerDenial 引用,包括原 data;不把合法非零结果升级 fatal,不修改 Store。dismissDenial 仅清除当前 denial;登录/进房成功页面切换清除非终止 overlay。
|
||
- 重连保留当前页面。Store phase 从 reconnecting/slow 恢复 connected/logged-in 时只清 reconnect overlay,不清 denial,不自行跳大厅。
|
||
- kicked 保存原 data,fatal 保存原 Error。两者终止交互并取消观察/帧,但保留底图用于展示;fatal 可覆盖 kicked,其他回调不得覆盖终止画面。
|
||
- 同步 ScenePort/View 异常向 Runtime 原样传播;帧/订阅异步异常必须取消更新并调用 onFault。外层应展示 fatal 并停止 Runtime;两项操作均须尝试,清理异常聚合保留。3A 不把错误转换为默认模型。
|
||
|
||
## 7. 外部兼容与后续实际接管
|
||
|
||
服务器包、route/rpc、roomtype 与 deskinfo 全部沿用已验证 contracts。不得为 UI 新增字段。远程配置与原生读取/WVJB 不在本批修改;后续适配前须完整读 native-bridge-contract,并用原工程核实精确契约,不能根据摘要重新发明接口。已核定远程配置 POST 空 body、URL 拼接、gameconfig 等行为保持现状。
|
||
|
||
3A 测试夹具只留在 framework-tests,不导入 assets,不声明生产 GameEntry。3B 启动前须明确真实 GameEntry 与 config.identity.gameid 一致、账号来源及目标宿主。尚未选定真实游戏不能用夹具顶替生产入口。
|
||
|
||
3B 单独批准后需完成:MCP 枚举 Login 活跃组件及 Prefab 控件语义;形成唯一绑定清单;移除活跃路径上的旧模拟驱动;以一个应用根持有新 Runtime;严格 WebSocket adapter 契约测试;保存并重开确认挂载;浏览器回放截图与真实宿主验收分别记录。没有这些证据,3A 完成不能称为“UI 接管完成”。
|
||
|
||
## 8. 验收
|
||
|
||
3A 必须通过:观察生命周期、2/4/10 座位、同帧合并/切页首帧、命令原样转发、非零拒绝可重试、重连 overlay 恢复、kick/fatal 终止、回调重入销毁、真实 PlatformRuntime 登录/进房/deskinfo 恢复顺序回放、现有非 UI 回归、类型与架构检查。
|
||
|
||
代码边界测试禁止 presentation 导入 cc/net/protocol 实现、旧 PlatformSession/RoomRPCBus,以及 SDK 导出 presentation。允许 type-only 引用现有 LoginAccountIdentity、JoinRoomCommand、ServerDenial;协议 parser/builder 不进入 Presenter。
|
||
|
||
任何 `.scene/.prefab/.anim/.meta` 变更均使 3A 验收失败。本计划完成后交付测试证据与明确 3B 门槛,不自动启动编辑器、选择真实游戏或删除旧资源。
|