feat: integrate room framework and isolated subgame bundles
Complete room UI and protocol integration, move game definitions and resources behind bundle entries, publish authoritative version XML, and document single-game builds. Include all current resource changes and experiment artifacts.
This commit is contained in:
+11
@@ -0,0 +1,11 @@
|
||||
# 子游戏静态依赖解除实施计划
|
||||
|
||||
目标:公共框架与应用启动层不导入具体子游戏。沿用已经通过独立构建与导出网页验证的 Bundle 动态加载能力,不修改引擎或服务器。
|
||||
|
||||
1. 为入口解析补充失败测试,覆盖未知入口、键不匹配、资源越界;新增公共代码不得静态引用 games 的边界检查。
|
||||
2. 将游戏定义、聊天和菜单策略放入各自目录。每个游戏通过 `GameEntry_<gameKey>` 导出定义;公共入口只依赖 SDK 契约。
|
||||
3. SubgameAssets 按 gameKey 加载 `game-<gameKey>`,完成入口解析、私有 prefab 加载后才允许启动平台。版本仍使用各自 `version-<gameKey>` Bundle,XML 保持唯一身份来源。
|
||||
4. 使用编辑器 API 将游戏根目录设为 Bundle,将模板创建房间 prefab 移到模板游戏目录;清除启动场景的私有 prefab 引用,保留公共房间场景。
|
||||
5. 更新静态资源检查、测试夹具与接入说明。测试所有既有行为及失败边界;编辑器导入后验证资源引用和真实 Web 构建运行。
|
||||
|
||||
边界:本次解除运行时依赖;不更改房间协议、玩法与原生接口。构建选择器的自动化单独围绕现有 gameKey 实现,不引入第二份身份配置。
|
||||
@@ -0,0 +1,141 @@
|
||||
# 子游戏接入与目录约定
|
||||
|
||||
返回[文档首页](../../README.md)。后续阅读:[创建房间](create-room.md)、[版本与构建](../build/version-and-build.md)。
|
||||
|
||||
## 1. gameKey、目录、gameid、route 分别是什么
|
||||
|
||||
`gameKey` 是子游戏入口和 Bundle 的选择键,负责选择游戏实现和版本资源。目录只是文件位置。`gameid` 是服务器游戏身份,由 XML 提供。`route` 是游戏入口的路由标识,必须遵循实际消息契约,不能因重命名目录而修改。
|
||||
|
||||
当前二七王:
|
||||
|
||||
- 注册键和 definition.key:`erqiwang`。
|
||||
- 源码目录:`assets/games/erqiwang`。
|
||||
- gameid:读取该游戏 XML 的 `game/game@id`,不是字符串 `erqiwang`。
|
||||
- 当前 definition.route:`erqiwang`;不要因此把平台创建房间请求的 route 改为 erqiwang,该请求仍是 `agent/create_room`。
|
||||
|
||||
当前模板:
|
||||
|
||||
- 注册键和 definition.key:`Game_Surface_3`。
|
||||
- 源码目录:`assets/games/template`。
|
||||
- 版本路径仍使用 `Game_Surface_3`,不是 template。
|
||||
|
||||
两者通过子游戏入口定义和 Bundle 名称建立关联,所以目录和 gameKey 目前允许不同。不能只把模板 gameKey 改成 template;现有注册项、资源路径与 bundle 名称不会自动跟随。
|
||||
|
||||
新游戏建议统一 `目录名 = gameKey = definition.key`。这是前端命名约定,不影响服务器 gameid 或协议路由。当前构建扩展允许 gameKey 使用英文字母、数字、下划线、连字符。
|
||||
|
||||
## 2. 建议目录
|
||||
|
||||
以下是新游戏 mygame 的组织示例,不表示该游戏已经存在:
|
||||
|
||||
```text
|
||||
YouleNexus/assets/
|
||||
app/composition/
|
||||
game-definitions.ts # 通用入口解析,不引用具体游戏
|
||||
SubgameAssets.ts # 动态加载目标游戏及版本
|
||||
games/mygame/
|
||||
game-config.ts # 定位版本 XML 和私有资源根
|
||||
game-definition.ts # 本游戏定义
|
||||
GameEntry.ts # 注册 GameEntry_mygame
|
||||
mygame-game.ts # GameBinding / GameEntry / GameModule
|
||||
mygame-room-rules.ts # 自己解释和生成 roomtype
|
||||
room-chat-config.ts # 自己的聊天配置
|
||||
MyGameCreateRoomView.ts
|
||||
MyGameCreateRoom.prefab
|
||||
MyGameRoomView.ts
|
||||
MyGameRoom.prefab
|
||||
resources/ # 编辑器设置为 version-mygame bundle
|
||||
mygame/version.xml # 身份和版本唯一来源
|
||||
```
|
||||
|
||||
框架中不添加 mygame 的游戏 ID、玩法开关、节点查找路径或特殊 RPC 分支。需要扩展公共契约时,应作为独立框架能力设计,而不是每接入一个游戏就改框架。
|
||||
|
||||
## 3. 配置与应用注册
|
||||
|
||||
游戏配置可参照 [erqiwang/game-config.ts](../sdk/contracts.md):
|
||||
|
||||
```ts
|
||||
export const gameConfig = Object.freeze({
|
||||
versionResource: 'mygame/version',
|
||||
assetRoot: 'games/mygame',
|
||||
});
|
||||
```
|
||||
|
||||
当前运行时要求 versionResource 精确等于 `<gameKey>/version`。XML 和资源包准备见[构建文档](../build/version-and-build.md)。
|
||||
|
||||
在自己的 `assets/games/mygame/game-definition.ts` 导入本游戏工厂、配置和聊天配置,导出只包含本游戏的 `GAME_DEFINITIONS`。公共应用层不添加注册项。需要提供的完整字段以 [GameDefinition](../sdk/contracts.md) 为准:
|
||||
|
||||
- `key`:与注册键及场景 gameKey 一致。
|
||||
- `route`:核对既有游戏路由,不自动从目录推导。
|
||||
- `config`:自己的版本资源定位配置。
|
||||
- `createGame(gameId)`:接收已解析的游戏身份,返回 GameBinding,不另写一份 gameid。
|
||||
- `roomMenu`:显式提供 mainSceneButton、vipInfinite 两个布尔策略。
|
||||
- `chat`:按 [RoomChatConfig](../sdk/contracts.md) 提供该游戏聊天配置。
|
||||
- `createPageClass`、`roomViewClass`:相应 Cocos 组件类名,注意 `@ccclass` 名称。
|
||||
- `resources.createRoom`、`resources.room`、`resources.roomScene`:资源路径描述,参照已有注册项。
|
||||
|
||||
**createRoom 和 room 按定义从目标游戏 Bundle 自动加载,不再绑定到启动场景。** 创建页面运行时是通过 `CreateRoomPage` 基类取组件;仅填写 createPageClass 不能替代继承该基类及在游戏 prefab 挂载组件。房间视图按 roomViewClass 取组件。
|
||||
|
||||
## 4. 实现房间入口和生命周期
|
||||
|
||||
参考 [erqiwang-game.ts](../sdk/contracts.md),但不要把其中尚未实现的玩法处理当成完整游戏模板。
|
||||
|
||||
`GameBinding` 提供 entry、mount(view)、restoredSnapshot、roomProjection。`GameEntry` 提供 key、gameId、route,以及:
|
||||
|
||||
- `resolveSeatCount(roomtype)`:解析真实服务器返回的本游戏 roomtype,返回座位数;输入非法时显式报错。平台不负责解释玩法字段。
|
||||
- `createModule()`:为房间创建独立的 GameModule,不在模块间共享上一房间的可变状态。
|
||||
|
||||
[GameModule](../sdk/contracts.md) 生命周期:
|
||||
|
||||
- `attach(host)`:接收平台能力并订阅状态。
|
||||
- `handlePlatformEvent(event)`:处理平台转交的房间事件。
|
||||
- `handleGameMessage(message)`:按本游戏协议处理玩法消息。
|
||||
- `restore(deskinfo)`:按本游戏快照契约恢复状态和视图。只保存原始数据不等于完成重连恢复。
|
||||
- `dispose()`:解除订阅、定时器与界面引用,结束后不继续发送或响应旧会话操作。
|
||||
|
||||
通过 `requireRoomCapabilities(host)` 使用 room/messages/ui/scope 能力,不从游戏中访问 PlatformRuntime、平台 Store、WebSocket 或另一款游戏的模块。
|
||||
|
||||
- room:快照、订阅、座位映射、准备、退出、解散。
|
||||
- messages:发送符合本游戏协议的消息。
|
||||
- ui:业务提示、具有所有权的 loading、数字输入及倍数选择;输入取消是正常结果,应处理取消分支。
|
||||
- scope:注册房间结束时的清理逻辑。
|
||||
|
||||
完整接口见 [game-capabilities.ts](../sdk/contracts.md)。当前未提供可用的房间音频所有权能力,不假设它存在。
|
||||
|
||||
## 5. 编辑器绑定与选中游戏
|
||||
|
||||
打开 `assets/scenes/PlatformStartup.scene`,在挂有 `SubgameAssets` 的节点配置:
|
||||
|
||||
1. `gameKey`:目标游戏键。
|
||||
2. 创建页面 prefab 在本游戏定义中声明,根节点挂自己的 CreateRoomPage 子类。
|
||||
3. 房间 prefab 在本游戏定义中声明,根节点挂匹配 roomViewClass 的组件。
|
||||
4. `roomScene`:用于承载房间的 SceneAsset。
|
||||
5. 保存场景;构建扩展读取磁盘中已保存的场景,而不是未保存的 Inspector 状态。
|
||||
|
||||
两款现有游戏当前都使用 `TemplateRoom.scene` 作为承载场景,场景名称不意味着二七王使用模板的玩法逻辑。现有二七王房间视图继承 [CommonRoomViewBase](../sdk/contracts.md),公共控制通过已有属性绑定。
|
||||
|
||||
## 6. 接入验收
|
||||
|
||||
- 注册键、definition.key、gameKey、版本资源路径与 bundle 名称一致。
|
||||
- 创建页面重复打开/关闭不重复注册监听;忙碌时不能重复提交。
|
||||
- 抓取实际 create_room 出包确认 roomtype,而不仅检查 UI 选项。
|
||||
- 验证成功、服务器拒绝、重连已有房间、退出后再进房间。
|
||||
- 游戏解析自己真实的 deskinfo;公共平台不转换子游戏快照格式。
|
||||
- 切换至另一款游戏并验证其身份、创建页面与版本 XML,避免留下另一款游戏的绑定。
|
||||
- 控制台无异常,业务提示不承载调试输出,房间释放后无旧订阅和迟到回调。
|
||||
|
||||
当前二七王 handleGameMessage 与解散结算仍有显式“尚未接入”分支,房间视图标识“玩法尚未接入”并隐藏准备按钮。这些是后续子游戏工作,不应宣称创建房间成功就已完成牌局迁移。
|
||||
|
||||
## 7. Bundle 入口
|
||||
|
||||
游戏根目录设置为 `game-<gameKey>` Bundle,优先级 1;版本子目录保持 `version-<gameKey>` Bundle,优先级 2。版本包必须比游戏包优先,保证 XML 属于版本包,不被父目录包提前收走。
|
||||
|
||||
```ts
|
||||
import {_decorator} from 'cc';
|
||||
import {GAME_DEFINITIONS} from './game-definition.ts';
|
||||
@_decorator.ccclass('GameEntry_mygame')
|
||||
export class MyGameEntry {
|
||||
static readonly definition = GAME_DEFINITIONS.mygame;
|
||||
}
|
||||
```
|
||||
|
||||
启动等待目标 Bundle 加载完成,通过公开类注册 API 读取入口定义,然后加载私有 prefab 和版本 XML。入口缺失、键不匹配、资源越界或加载失败均显式报错,不回退到其他游戏。新增游戏不修改 app/framework/scripts 或公共资源。
|
||||
@@ -0,0 +1,130 @@
|
||||
# 子游戏创建房间接入
|
||||
|
||||
返回[接入指南](README.md)。本章对应原工程的“创建页面选择玩法 → create_room → 进入房间”流程。
|
||||
|
||||
## 1. 平台与子游戏的职责
|
||||
|
||||
子游戏负责创建页面布局、选项状态、房卡展示、已有配置回显、roomtype 编码和校验。平台负责弹窗生命周期、忙碌状态、统一发包、身份与设备环境注入、响应分发和房间进入。
|
||||
|
||||
创建页面只调用 `context.submit(roomtype)`,不自行拼接 agentid/playerid/gameid/ip/location,不直接创建 WebSocket,不自行调用 create_room RPC。
|
||||
|
||||
平台将 roomtype 作为不透明配置保存和转发。它会验证可用的数据形态,但不会把所有游戏限制成字符串、数组或同一长度。当前模板使用数组,二七王新请求使用 11 位字符串。禁止对通用 roomtype 使用 String()/JSON.stringify() 进行协议类型转换。
|
||||
|
||||
## 2. 页面契约
|
||||
|
||||
接口定义:[create-room-page.ts](../sdk/contracts.md)。引擎组件基类:[CreateRoomPage.ts](../sdk/contracts.md)。
|
||||
|
||||
```ts
|
||||
interface CreateRoomPageContext {
|
||||
readonly previousRoomtype: unknown;
|
||||
submit(roomtype: Roomtype): void;
|
||||
cancel(): void;
|
||||
reportError(message: string): void;
|
||||
}
|
||||
|
||||
interface CreateRoomPagePort {
|
||||
open(context: CreateRoomPageContext): void;
|
||||
setBusy(busy: boolean): void;
|
||||
close(): void;
|
||||
}
|
||||
```
|
||||
|
||||
该代码用于说明现有契约;实际开发应直接 import SDK 类型,不复制一份接口到子游戏。
|
||||
|
||||
自己的组件应继承 CreateRoomPage,并挂在创建 prefab 的根节点。现有参考:
|
||||
|
||||
- [ErqiwangCreateRoomView.ts](../sdk/contracts.md)
|
||||
- [TemplateCreateRoomView.ts](../sdk/contracts.md)
|
||||
|
||||
### open(context)
|
||||
|
||||
1. 先清理前一次打开遗留的监听和 context。
|
||||
2. 验证必要的编辑器绑定完整,缺少控件时显式报错。
|
||||
3. 由本游戏规则对象解释 previousRoomtype,恢复 UI 选中状态。
|
||||
4. 绑定选项、提交和取消操作,保存解除监听的函数。
|
||||
5. 更新消费展示和按钮状态。
|
||||
|
||||
`previousRoomtype === null` 表示没有历史配置,子游戏规则来源可以在此定义自己的首次创建默认值。已有但非法的历史配置不应悄悄替换为默认值;应明确暴露或实现有协议依据的版本迁移。
|
||||
|
||||
当前平台从 localStorage 的 `tsgame_roomtype_<gameid><agentid>` 读取历史 JSON 的 data,交给页面。页面不必重新读取本地存储,也不要按另一套 key 再维护一份历史。
|
||||
|
||||
### setBusy(busy)
|
||||
|
||||
平台请求期间禁用提交和会改变 roomtype 的选项,避免双击重复创建。取消/关闭策略按业务处理;二七王当前保留关闭入口。忙碌状态由平台驱动,不能以固定延时假装请求结束。
|
||||
|
||||
### close() / onDestroy()
|
||||
|
||||
解除所有事件、清空 context,重复调用安全。组件 onDestroy 调用 close。异步回调应检查 context 或会话是否仍有效,关闭后不能继续 submit。
|
||||
|
||||
`context.reportError` 当前进入控制台诊断,不是给玩家看的提示接口。编码错误、缺少组件、堆栈等不得通过 Layer612_Tips 展示。
|
||||
|
||||
## 3. roomtype 放在独立规则对象中
|
||||
|
||||
建议将规则逻辑放在本游戏的 room-rules.ts,页面只负责从控件调用 select/build。规则对象应能在不启动 Cocos 的情况下测试:
|
||||
|
||||
- 无历史时的明确默认值。
|
||||
- 合法历史的解析和回显。
|
||||
- 非法值拒绝。
|
||||
- UI 选项到线协议字段的映射。
|
||||
- 房卡文案所需计算;最终扣卡仍以服务器为准。
|
||||
- 新请求编码,以及服务器回包/重连输入的解析。
|
||||
|
||||
**请求编码与历史解析不一定采用相同的严格程度。** 必须以真实协议和服务端行为为依据,不能为了让旧数据通过而在平台增加通用兜底。
|
||||
|
||||
## 4. 二七王的具体参考
|
||||
|
||||
[ErqiwangRoomRules](../sdk/contracts.md) 将 UI 顺序映射到服务器位序。UI 顺序与线协议顺序不同,不可直接拼接各 Toggle 的排列顺序。
|
||||
|
||||
当前创建页面需要在编辑器序列化:
|
||||
|
||||
- 3 个 ToggleContainer。
|
||||
- 8 个 Toggle,以及对应 8 个选项 Label。
|
||||
- 1 个提交 Button、2 个关闭/取消 Button。
|
||||
- 1 个费用 Label。
|
||||
|
||||
前三组选项是局数、扣卡方式、查牌设置;后两个独立选项是傍王和爬坡。准确的位定义见[二七王玩法协议](../../subgames/erqiwang/protocol/packet_protocol.md),本指南不另维护一份位索引常量。
|
||||
|
||||
当前 build() 新请求输出:**11 位字符串,前 5 位为 0/1,后 6 位为 0**。例如所有开关取 0 时是字符串 `"00000000000"`。此例不是平台默认配置,也不能转换成数字 0。
|
||||
|
||||
已有/回包配置解析当前允许至少 5 位、前 5 位为 0/1的字符串;新请求仍固定输出 11 位。二七王座位数由自己的 resolveSeatCount 返回 3,平台不内置该人数。
|
||||
|
||||
## 5. 完整提交与响应链
|
||||
|
||||
```text
|
||||
大厅打开创建弹窗
|
||||
→ 从 SubgameAssets.createRoom 实例化 prefab
|
||||
→ 根节点取得 CreateRoomPage 组件
|
||||
→ page.open(context),回显 previousRoomtype
|
||||
→ 点击创建:本游戏 rules.build()
|
||||
→ context.submit(roomtype)
|
||||
→ 平台请求管理与 PlatformRuntime.createRoom
|
||||
→ 既有 agent/create_room RPC
|
||||
→ 服务器响应
|
||||
→ RuntimeSession 解析公共响应
|
||||
→ 本游戏 resolveSeatCount(真实 roomtype)
|
||||
→ 平台提交房间状态,进入房间会话并挂载视图
|
||||
```
|
||||
|
||||
发包信封保持 `app: youle`、`route: agent`、`rpc: create_room`。data 中的身份字段、ip、location 由平台按既有契约提供,roomtype 原样采用子游戏生成值。通用协议入口见[agent 协议 create_room](../protocol/02-协议-agent路由.md)。该文档早期数组描述针对模板,不代表二七王也应发送数组。
|
||||
|
||||
发送成功不代表创建成功。必须等待服务器返回:拒绝响应走平台业务拒绝流程;成功响应才进入房间。页面不要在点击提交时直接切换到房间场景。
|
||||
|
||||
## 6. 房间视图与重连接续
|
||||
|
||||
创建 prefab、房间 prefab 是不同资源。除了创建页面,还要完成应用注册中的工厂和 roomViewClass,在本游戏定义中声明 room 路径,在 SubgameAssets 保留公共 roomScene 绑定。
|
||||
|
||||
房间状态/准备/退出通过公共能力接口;玩法消息通过本游戏 GameModule;登录发现已有房间时,deskinfo 必须由本游戏 restore 解释。只验证 create_room 正常,不足以证明重连已接通。
|
||||
|
||||
不要在创建页面 open 中建立房间模块、订阅房间消息或提前发送准备指令;该页面处于入房之前。
|
||||
|
||||
## 7. 推荐验证顺序
|
||||
|
||||
1. 纯规则测试:选项组合、历史恢复、边界值、非法输入。
|
||||
2. 实际 prefab:字段绑定完整,重复打开关闭无重复监听,费用显示正确。
|
||||
3. 实际按钮提交:截获 context.submit 参数,确认类型与完整 roomtype 内容。二七王可覆盖全部 32 种开关组合。
|
||||
4. 平台出包:核对最终 agent/create_room 的类型和字段,不能只看规则单测。
|
||||
5. 成功与拒绝:忙碌状态正确释放,拒绝不进入房间,连续点击不重复发包。
|
||||
6. 房间接续:创建、加入、退出重进、登录重连、已有牌局 deskinfo 恢复。
|
||||
7. 资源释放:弹窗关闭及场景销毁后无事件、计时器和迟到提交。
|
||||
|
||||
实际服务器操作会创建房间或改变玩家状态,应使用明确的测试账号;纯规则和 prefab 验证可以通过注入 submit 回调完成,不需要真实发包。
|
||||
Reference in New Issue
Block a user