Files
youle_cocos/docs/superpowers/specs/2026-06-28-cocos-framework-design.md
T
joywayerandClaude Opus 4.8 17a82eb5bf docs(spec): 补充协议 SSOT 引用原则与原项目 bug 规避
- 0.1 协议单一信息源:设计/计划只引用 docs/protocol 章节、不内联复制,
  协议修订无需回改设计与实施计划;framework/protocol TS 类型为其机器镜像
- 0.2 不继承原项目源码 bug:按正确协议语义实现,主动规避同类缺陷

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 11:37:32 +08:00

244 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Cocos Creator 平台框架与子游戏架构设计
- **日期**:2026-06-28
- **状态**:设计已确认,待转实施计划(writing-plans)
- **范围**:友乐棋牌平台在 Cocos Creator 下的新一代「平台框架 + 子游戏」架构
- **宿主工程**:`cocoscreator_projects/YouleNexus`(Cocos Creator 3.8.8)
---
## 0. 第一准则:服务器零改动
任何前端开发都必须完全遵循前后端数据包的协议与数据结构,做到**服务器零改动**。这是不可逾越的最高准则,优先于一切其它考量:协议信封、`route`/`rpc` 命名、字段名与类型、`roomtype` 配置数组、`deskinfo` 快照结构等,必须与现有协议逐字节对齐(依据 `docs/protocol/`)。本设计的所有取舍都不得违反此准则。
### 0.1 协议单一信息源(SSOT)
`docs/protocol/`(已经源码交叉审计校准的 8 篇)是**协议与数据结构的唯一权威来源**。本设计文档与后续实施计划:
- **只引用、不复制**:涉及具体 rpc、字段名、信封、`roomtype`/`deskinfo` 结构时,一律以「见 `docs/protocol/<章节>`」方式**引用**,不在设计/计划文档里内联复制协议细节。这样后续修订 `docs/protocol` 时,**无需回改本设计与实施计划**。
- **代码层落地**:框架 `protocol/` 层的 TS 类型是 `docs/protocol` 的**机器可读镜像**,二者须保持一致;协议变更先改 `docs/protocol`,再同步 `protocol/` 类型。
### 0.2 不得继承原项目的源码 bug
原 H5 模板存在已知源码 bug(见 `docs/protocol` 中 🐛 标注,如 `can_award` 接收函数缺失、小程序 deeplink `checkType` 误接白名单等)。这些功能在当前业务中已废弃,**协议层无需复刻其错误行为**;但新前端实现时须**主动规避**——按正确的协议语义实现,不得因照搬原项目逻辑而把同类缺陷带入新前端。
## 1. 目标与非目标
### 目标
1. **框架现代化**:TypeScript、响应式状态、编译期协议校验、高性能、高可扩展。
2. **框架与子游戏零耦合**:依赖单向,框架不感知任何子游戏;子游戏只通过受限边界与框架交互。
3. **框架更新方便传播**:框架改代码或界面后,所有子游戏无需「拷贝/同步」即可获得更新。
4. **消灭旧架构痛点**:旧架构模板与子游戏一对一、开发新子游戏需拷贝整份模板、更新极繁琐。
### 非目标
- 不做运行时大厅聚合 / 子游戏热插拔(部署形态为「各子游戏独立构建发布独立包」)。
- 本期不重写服务器、不改协议。
### 关键决策记录
| 维度 | 决策 |
|---|---|
| 部署形态 | 各子游戏独立构建发布独立包,但共享同一份框架源(非运行时大厅) |
| 框架边界 | 逻辑 + 平台界面(Prefab/场景/图集)+ 可换肤主题 |
| 仓库组织 | Monorepo + 符号链接(junction)共享框架 |
| YouleNexus 角色 | 框架本体的开发/演示/回归宿主工程 |
| 换肤机制 | 配置 + 资源覆盖为主,插槽为辅 |
| 新建子游戏 | 脚本从「种子工程模板」全自动克隆 |
| Cocos 版本 | 全 monorepo 统一,升级即协调发布 |
## 2. Monorepo 布局与「框架更新即时传播」
### 物理布局
```
cocoscreator_projects/
├─ YouleNexus/ # 框架宿主工程(开发 + 演示 + 回归测试框架本体)
│ └─ assets/
│ ├─ framework/ # ★框架本体(唯一真源,被各子游戏 junction 引用)
│ │ ├─ core/ net/ protocol/ platform/ ui/ sdk/
│ └─ _dev/ # 框架自测:mock server、mock 子游戏、调试场景(不发布)
│
├─ games/ # 各子游戏工程(每个都是独立可构建发布的 Cocos 工程)
│ ├─ <game>/
│ │ └─ assets/
│ │ ├─ framework ──►(junction) ../../../YouleNexus/assets/framework
│ │ └─ game/ # 子游戏私有:IGameModule 实现 + theme + override + 对局资源
│ └─ …
│
└─ templates/
└─ game-seed/ # 最小但完整的空 Cocos 工程种子(new-game 克隆源)
```
### 更新传播机制
- 框架只有**一份真源**(`YouleNexus/assets/framework`)。各子游戏工程的 `assets/framework` 是指向它的 **Windows junction**(`mklink /J`,免管理员)。
- 改框架代码或界面 → 同一份物理文件 → 所有子游戏工程**即时可见**,无需拷贝/同步/bump 版本。
- 资源 `.meta` 随真源共享,**UUID 全局稳定唯一**,跨工程引用不错乱;各工程仅 `library/` 编译缓存独立。
- **取舍(已确认接受)**:框架默认即时同步到所有子游戏,改框架要对所有子游戏负责(不做「子游戏锁定框架版本」)。
### 配套工程化措施
1. **`scripts/setup-links`**(Node):junction 内容不被 git 跟踪,团队成员 clone 后跑一次,按 `games/*` 自动重建所有 junction;新建子游戏也用它接入。
2. **`.gitignore`**:忽略各子游戏的 `assets/framework`(junction 本体)、所有 `library/` `temp/` `build/`。
## 3. 框架内部分层与零耦合规则
框架 `assets/framework/` 内分 6 层,**依赖严格单向**(上层依赖下层,下层永不反向,框架永不 import 子游戏):
```
sdk/ 子游戏对接边界(子游戏只认这一层 + core 类型)
IGameModule 接口 · 平台→子游戏回调钩子 · 暴露给子游戏的 API facade
ui/ 平台界面:登录/大厅/房间/通用弹窗 Prefab+场景 · 主题/换肤系统
platform/ 平台业务:响应式 Store(RoomStore=Desk / PlayerStore=C_Player / AppStore=GameData)
+ 登录/房间/重连流程编排
protocol/ 协议契约:route/rpc 常量 · 收发包 TS 类型 · 平台 RPC 封装(send/handle)
net/ 传输:NetClient(WebSocket) · Envelope 双层拆包 · 心跳/30s超时/重连轮询
core/ 基座:EventBus · 协议类型定义 · 座位↔视图转换 · 常量/工具
```
### 对应旧架构映射(保证协议红线不破)
- `net` + `protocol` ← 旧 `09_Net` / `00_minhttp` / `02_Const`
- `platform` ← 旧 `04_Data` / `06_Player` / `07_Desk` / `12_Logic`
- `ui` ← 旧 `11_GameUI`
- `sdk` ← 旧 `01_SubGame` 钩子契约(`Game_Modify` / `02_SubGame_Input`),但反转为接口
### 零耦合三条硬规则
1. **依赖单向 + 框架不知道任何子游戏存在**:框架代码不得出现任何子游戏名/类型;子游戏通过 `sdk` 注册自己。
2. **通信只走两条道**:① 子游戏实现 `IGameModule` 被框架调用;② 子游戏通过 `sdk` facade 调用框架能力。不允许子游戏直接 import `platform`/`net` 内部实现。
3. **状态归属清晰**:框架持有 `Desk`/`C_Player`/`GameData`(响应式 Store);子游戏持有自己的对局态(手牌/出牌/轮次)于自身命名空间,并实现 `serialize()/restore(deskinfo)` 供断线重连。
### 高扩展点
- `protocol` 用 TS 类型结构化收发包,编译期校验,对齐协议零改。
- `platform` Store 响应式驱动 UI(状态变 → UI 自动刷新),告别旧的手动刷 UI。
- 新增平台 RPC = 加一个 typed handler,不动其它层;新增子游戏 = 注册一个 `IGameModule`,框架零改动。
## 4. 数据流与 sdk 接口契约
### 一条收发包的完整流向(单条 WebSocket,平台与对局复用)
```
收包 WebSocket frame
→ net 外层拆包 + 过滤 @toconcon/@serverheartbeat + JSON.parse → Envelope{route,rpc,data}
→ Router route ∈ {agent,room,platform} ─► protocol handler ─► 更新 platform Store ─► UI 响应式自动刷新
route = <game route> ─► sdk ─► activeGame.onReceive(rpc,data) ─► 子游戏对局态 ─► 对局UI刷新
发包 平台操作: platformApi.xxx() ─► protocol 编码 ─► net.send
对局操作: ctx.net.send(route,rpc,data) ─► net.send (同一条连接)
```
### 接口契约(方向已定,精确签名待后续细化)
> **说明**:以下接口的**方向与边界已确认**(子游戏被 `IGameModule` 调用、通过 `GameContext` 调用框架);**精确的方法签名、字段、能力面将在后续专门的接口设计中细化**,本 spec 不敲死。
子游戏实现(框架调用子游戏)—— `IGameModule`(示意):
```ts
interface IGameModule {
readonly route: string; // 本游戏 game route,框架据此路由对局包
onEnter(ctx: GameContext): void; // 进入牌桌场景,拿到框架能力句柄
onExit(): void;
onReceive(rpc: string, data: any): void; // 框架派发非平台包,子游戏 switch(rpc)
onReconnect(deskinfo: any): void; // login 响应 isbattle==1 时恢复对局
serialize?(): any; // 对局快照(可选)
// 平台事件钩子(默认空实现,按需覆写):onPlayerJoin/onPlayerLeave/onReady/onDissolve/onOffline/...
}
```
框架暴露给子游戏(子游戏调用框架)—— `GameContext` facade(示意):
```ts
interface GameContext {
net: { send(route: string, rpc: string, data: object): void };
room: ReadonlyRoomStore; // 只读 Desk
player: ReadonlyPlayerStore; // 只读 C_Player / 座位玩家
ui: PlatformUI; // toast / dialog / 通用弹窗
events: EventBus;
seat: { toView(mySeat: number, target: number): number }; // 替代旧 ChangeToStatus
}
```
关键点:子游戏拿到的是**只读 Store + 受限 facade**,不能触碰 `net`/`platform` 内部,强制零耦合。
## 5. 换肤/覆盖机制(配置 + 资源覆盖为主,插槽为辅)
子游戏所有定制都落在自己的 `game/` 私有目录,**框架真源一字节不改**——这是「换肤」与「框架可更新」共存的前提。
1. **主题配置**:每个子游戏 `game/theme.ts` 声明主色/字体/图集替换映射/关键布局参数。框架 UI 组件全部「皮肤无关」,从 `ThemeProvider` 读值渲染,不写死样式。可改范围 = 框架预留的「皮肤变量」集合。
2. **资源覆盖层**:框架加载资源统一走 `AssetResolver.load(logicalPath)`,**先查 `game/override/<logicalPath>`,命中即用,否则回退 `framework/ui/default/<logicalPath>`**。子游戏放同逻辑路径资源即可替换,无需改框架。
3. **插槽**:框架关键 Prefab 预留具名挂载点 `<Slot name="xxx">`,子游戏在注册阶段声明往该槽注入自己的 Prefab。仅用于结构级深度定制,日常换肤不碰。
**冲突与更新策略**:
- 子游戏覆盖物全在 `game/`,与框架真源物理隔离 → `git pull` 框架更新不产生资源冲突。
- 框架维护一份「**皮肤变量 + 可覆盖逻辑路径 + 插槽**」契约清单(约定即 API);改这份契约属 breaking change,需走版本说明通知子游戏。
- **取舍(已确认接受)**:框架须主动把 UI 抽象成「皮肤无关 + 变量/覆盖点」,增加框架自身开发成本,换取零冲突换肤。
## 6. 协议红线、错误处理、测试、技术栈
### 协议红线保障(第一准则的技术落地)
- `protocol/` 用 TS 类型把每个收发包结构化(字段名/类型/`roomtype`/`deskinfo`),编译期挡住字段写错。
- `net` 支持收发包**录制/回放**:存盘真实包流,框架改版后回放比对,确保协议行为不漂移。
- 一份「协议一致性测试」对照 `docs/protocol/` 各章,作为框架 CI 红线门禁。
### 错误处理(高可用)
- **网络**:30s 收包超时看门狗 + 断线候选服务器轮询重连(对齐旧 `Logic`);重连后重发 `player_login`、按 `isbattle` 走 `onReconnect`。
- **故障隔离**:子游戏 `onReceive`/钩子用 try-catch 包裹,子游戏抛错不拖垮框架(平台连接与大厅存活),错误进统一日志。
- **资源**:`AssetResolver` 覆盖缺失 → 回退默认 → 仍缺 → 占位资源 + 告警,不黑屏。
### 测试(依托 YouleNexus 宿主工程)
- `YouleNexus/assets/_dev/` 挂 **mock server + mock 子游戏**,独立跑通登录/大厅/房间/对局/重连全流程,作为框架回归基准。
- `core/net/protocol` 纯逻辑做单元测试(拆包、心跳、编解码、座位换算)。
- 子游戏可对自己的 `IGameModule` 单测(喂 rpc 包断言对局态),不依赖真服务器。
### 技术栈
- **TypeScript**(Cocos 3.8 标配);轻量**响应式 Store**(自研薄封装或极小库,避免重依赖);不引大型框架,保证高性能/低耦合。
## 7. 新建子游戏流程(脚本从种子工程全自动克隆)
> 事实:Cocos Creator 无官方 CLI 凭空生成全新工程,工程须从一份已有工程复制而来。故采用「种子工程模板」克隆。
仓库维护 `templates/game-seed/`——最小但完整的 Cocos 工程,预配统一引擎版本、构建平台设置、屏幕适配、引擎模块裁剪。
```
scripts/new-game <name>
1. 复制 templates/game-seed/ → games/<name>/
2. 改 package.json 的 name;重新生成 project uuid
3. 删 library/ temp/(首次打开由编辑器重建)
4. 建 assets/framework junction → 指向 YouleNexus 真源
5. 铺 assets/game/ 骨架:IGameModule 空实现 + theme.ts + override/ 目录(新资源分配全新 meta uuid)
6. 打印「用 Cocos Creator 打开 games/<name>」
```
- 复制的只是**极薄的空工程外壳**(不含框架,框架靠 junction),与旧架构「拷贝整份模板+逻辑」有本质区别。
- 种子工程需随 Cocos 版本升级维护一次;克隆时正确重置 project/资源 uuid(实现时按 3.8 实际字段验证)。
## 8. Cocos 版本升级
### 根本约束(已确认接受)
framework 真源含**带版本的序列化资源**(`.prefab`/`.scene`/`.meta`)与按某版引擎 API 写的脚本,且被多工程 junction 共享同一份物理文件。Cocos 跨版本资源格式/meta/API 可能不兼容。故:
> **所有共享 framework 的工程,Cocos 版本必须统一,不能分叉。** 这是 symlink 共享资源换「即时同步」的代价。引擎版本声明于各工程 `package.json` 的 `creator.version`。
### 升级标准流程(一次「协调发布」,在 git 升级分支上做)
```
1. 框架宿主先行:用新版 Cocos 打开 YouleNexus → 执行工程迁移 → 跑 _dev mock 回归,确认框架在新版正常
2. 升种子工程:templates/game-seed 的 creator.version 同步升新版(新子游戏天然新版,零额外操作)
3. 逐个升子游戏:每个 games/<name>
① 改 package.json 的 creator.version → 新版
② 用新版 Cocos Creator 打开,让编辑器执行迁移(library 重建、按新版重新导入 framework 资源)
③ 跑该子游戏回归
4. 全部子游戏验证通过 → 合并升级分支
```
第 3 步「迁移」是编辑器行为,无法纯脚本完成——每个工程都需用新版编辑器打开一次。
### 工具化与防错
- `scripts/check-cocos-version`:扫描 `games/*` 与 YouleNexus 的 `creator.version` 是否一致,不一致告警;接进 `setup-links` 与 CI,防止误用旧版打开改写资源。
- `scripts/bump-cocos <version>`:批量改各工程 `package.json` 版本字段(不替代编辑器迁移)。
- 版本粒度:补丁级(3.8.8→3.8.9)通常资源兼容、风险低;跨次版本/大版本(3.8→3.9)走完整迁移回归。
## 9. 待后续细化项(不阻塞本设计)
1. **`IGameModule` / `GameContext` 精确签名**:方法集、字段、能力面(是否含录像/语音/AI 托管等通用能力)——专门的接口设计轮次敲定。
2. **皮肤变量 / 可覆盖逻辑路径 / 插槽 契约清单**:框架 UI 抽象时逐项确定并文档化。
3. **响应式 Store 选型**:自研薄封装 vs 极小第三方库。
4. **种子工程的 uuid 重置细节**:按 Cocos 3.8.8 实际字段验证 project 与资源 uuid 的生成。
5. **协议层 TS 类型**:依 `docs/protocol/` 逐章把收发包结构落为 interface。
## 10. 风险与缓解
| 风险 | 缓解 |
|---|---|
| junction 不被 git 跟踪,团队成员环境缺链接 | `setup-links` 一键重建;文档与 CI 校验 |
| Cocos 版本误分叉导致共享资源损坏 | `check-cocos-version` 告警 + 升级走协调发布流程 |
| 框架改动波及全部子游戏 | 框架 CI 回归 + 协议一致性门禁;breaking change 走版本说明 |
| 皮肤变量预留不足,子游戏被迫改框架 | 维护契约清单,深度定制走插槽;按需扩充变量 |