基于 00_Surface 平台层源码逐条交叉核对,修订 docs/protocol: - 补全前后端双向数据包(请求+响应/推送字段),覆盖框架模板全部平台层 rpc - 修正错误:重连触发条件(deskinfo 而非 isbattle)、小程序桥接(openminigamedata 而非 miniProData)、 get_player_invitecode 字段、setGameServer/GameData.Server 来源、route 分发守卫等 - 补遗漏 rpc:send_phone_code_wechat、submit_error/submit_log、refresh_task_state、 update_bean/update_charm/broadcast、connect_agentserver 字段、5 个成对推送常量 - 补 Desk 约 11 个字段、登录响应账号字段、10 个子游戏钩子 - 标注源码 bug(🐛 can_award 接收函数未定义、小程序 checkType=8 误接白名单) - 校准全篇 file:line 引用;待后端确认项标 ⚠️ - 明确范围:仅框架模板协议,不含子游戏对局包 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
18 KiB
06 · 子游戏开发模式(基于框架)与 CocosCreator 方案
本章讲框架模板提供给子游戏的钩子契约 + 状态归属 + 子游戏开发模式,并给出 CocosCreator 重写的两种设计方案。 不涉及任何一款具体子游戏的对局算法/字段/专属模块——凡涉及处统一标注"由各子游戏自定义"。 模板工程以
Game_Surface_3为基准,源码事实均以该工程为准。
1. 一个子游戏工程 = 模板壳 + 三处定制
对比模板 Game_Surface_3,真实子游戏的改动集中在固定几处,平台层(00_Surface/*)几乎原样保留:
| 部分 | 模板 | 子游戏 | 是否改动 |
|---|---|---|---|
js/00_Surface/*(平台层) |
完整 | 几乎不动(与服务器对接的协议层,保持兼容) | ❌ 基本不改 |
js/01_SubGame/00_SubGame_Config.js |
默认配置 | 改:人数/聊天气泡位/分享/声音/系统开关等 | ✅ 配置 |
js/01_SubGame/01_SubGame_modify.js |
含创建房间界面 + 战绩页,对局留空 | 填:创建房间界面、战绩定制、对局界面相关 | ✅ 重写 |
js/01_SubGame/02_SubGame_Input.js |
全是空桩 | 填:所有 Game_Modify.* 钩子的真实实现(含 _ReceiveData) |
✅ 重写 |
js/gamemain.js |
纯转发引擎回调,tcpmessage 等为空体 |
扩展:对局精灵初始化、引擎定时器/动画回调驱动对局 | ✅ 定制 |
js/*.js(子游戏专属模块) |
无 | 新增:玩法相关模块(牌型/出牌/理牌/回放等,由各子游戏自定义) | ✅ 新增 |
js/class/*(OOP 类库,可选) |
无 | 可选新增:牌型/牌局/算法类库(由各子游戏自定义) | ✅ 新增 |
output/*.min.js |
模板界面 | 替换:本游戏的精灵布局数据(编辑器导出) | ✅ 美术 |
一句话:改服务器对接以上的那一层(
01_SubGame三件套 +gamemain+ 专属模块 + 美术数据)就得到一款新游戏,平台层和协议不动。这正是"子游戏框架"的价值,也是"服务器零改动"的根因。
加载顺序
引擎(gameabc) + Spine
→ 00_Surface/00..12 平台层(与模板一致,含 Desk/Net/GameUI/Logic 等)
→ 01_SubGame/00,01,02 子游戏三件套
→ gamemain.js 子游戏定制的引擎回调
→ 专属模块 玩法相关模块(由各子游戏自定义)
→ class/* 类库(可选,由各子游戏自定义)
→ output/*.min.js 美术布局数据
后加载的 01_SubGame/02_SubGame_Input.js 用真实实现覆盖了平台默认的 Game_Modify 空桩。
2. 子游戏 ↔ 框架的契约(最重要)
2.1 框架 → 子游戏:回调钩子(平台在关键时机调用)
子游戏在 02_SubGame_Input.js 实现这些 Game_Modify.* 钩子,模板里它们全是空桩(或仅 console.log);平台层在固定时机调用它们,子游戏据此驱动对局界面与状态。下表的"平台调用点"以模板工程 Game_Surface_3 的源码为准(文件:行号),子游戏内部的具体实现"由各子游戏自定义"。
方向均为 平台 → 子游戏调用(框架在内部回调子游戏实现的钩子)。
| 钩子(签名) | 触发时机 / 平台调用点(文件:行) | 说明 |
|---|---|---|
_ReceiveData(_msg) |
收到对局自定义包时(12_Logic.js:263、09_Net.js:36) |
对局包入口。子游戏内 switch(_msg.rpc) 自分发并刷新对局,rpc 集合由各子游戏自定义 |
StartWar(_msg) |
开战(07_Desk.js:642 / 1018 / 1057 / 1141) |
开局:初始化牌桌、起手发牌等 |
Reconnect(_deskinfo) |
进房响应含 deskinfo 时(07_Desk.js:423) |
断线重连还原对局快照。触发条件是响应含 deskinfo(见 §2.4);模板为空实现,还原逻辑由各子游戏自定义 |
DeskInfo(_msg) |
未开战状态下自己加入、携带牌桌数据时(07_Desk.js:653) |
同步未开战阶段的牌桌信息 |
createRoom(_roomtype,_infinite) |
收到创建房间回包(09_Net.js:115) |
重置对局变量、铺座位等 |
onCreateRoom(_data) |
创建房间成功后(09_Net.js:117,可选钩子) |
创建房间成功后的子游戏处理 |
myJoinRoom(_msg) |
自己进入房间(07_Desk.js:632) |
自己入座后的初始化 |
playerJoinRoom(seat) |
其他玩家加入(07_Desk.js:736) |
同步该座位玩家显示 |
playerLeaveRoom(seat) |
其他玩家离开(07_Desk.js:801) |
清理该座位显示 |
myExitRoom(seat) |
自己退出房间(07_Desk.js:766,可选钩子) |
自己离场处理 |
breakRoom() |
已开局情况下自己退出(07_Desk.js:756) |
开局中退出的清理 |
playerOffline(seat) |
玩家离线(07_Desk.js:891) |
标记离线态 |
playerOnline(seat) |
玩家上线(07_Desk.js:898) |
标记在线态 |
playerphonestate(seat,type) |
玩家电话状态(07_Desk.js:974/984,type 1=挂断、0=通话/来电) |
电话状态显示 |
onReady(seat) |
玩家准备(07_Desk.js:1136) |
同步准备态 |
changeSeat(seat1,seat2) |
收到换座包(07_Desk.js:191,可选钩子) |
同步换座后界面 |
onSurrender(_msg) |
收到投降回包(09_Net.js:621) |
投降结果处理 |
Free(_msg) |
投票解散同意后确认时(07_Desk.js:873、11_GameUI.js:5026) |
解散结算分支 |
updateScene() |
按本地状态重绘界面(05_Func.js:1642 / 2922,可选钩子) |
断线恢复 / 切前台时重绘整个对局界面 |
closeGameScene() |
关闭游戏界面(07_Desk.js:427) |
退出对局界面 |
onEnterMainScene(roomtype) |
进入游戏主场景(11_GameUI.js:4795) |
进入主场景 |
onExitMainScene() |
退出游戏主场景(11_GameUI.js:3901) |
退出主场景 |
onMainMenuScene() |
显示大厅界面(11_GameUI.js:3904,可选钩子) |
回到大厅 |
onCreateDesk(roomtype) |
进入游戏界面创建牌桌之前(12_Logic.js:2125) |
创建牌桌前的子游戏准备 |
onGameConfig(_gameConfig) |
获取到游戏配置时(12_Logic.js:1825,仅 game_config 有数据时调用) |
接收服务器下发的游戏配置 |
onEnterVideo() |
进入牌局回放时(08_Utl_Output.js:593) |
回放入口 |
calResult(inputArr) |
倍率结算面板确认(11_GameUI.js:6556,参数为倍率数组) |
结算计算回调 |
onOpenHelp(spid) |
打开帮助页面(11_GameUI.js:4720) |
帮助页处理 |
onCheckInput(_result) |
数字输入框确认(11_GameUI.js:7986/7988) |
数字输入回调 |
onLocationInfo(_locationInfo) |
成功获取定位信息(05_Func.js:2063 / 3087,可选钩子) |
定位信息回调 |
onCloseVip() |
关闭 vip 选项时(11_GameUI.js:7436,可选钩子) |
关闭 vip 处理 |
getShareRoom(_msg) |
收到星星场(分享房)信息时(07_Desk.js:1160,可选钩子) |
星星场房间处理 |
stopAllSounds() |
需要静音对局声音时(05_Func.js:1658 / 2934、07_Desk.js:718 / 725 / 772) |
关闭子游戏声音 |
shakeEvent() |
摇一摇事件(05_Func.js:1958 / 3049) |
模板示例里据 GameData.shakeID 走 Net.Send_self_makewar() 开战 |
返回类钩子(平台据返回值渲染大厅 / 房间列表,返回内容由各子游戏自定义):
| 钩子(签名) | 平台调用点(文件:行) | 返回值含义 |
|---|---|---|
getRoomInfo(roomtype,type,tea) |
11_GameUI.js:6942 |
房间描述文本(type 1=系统房间、2=非系统房间) |
getFullRoomInfo(roomtype) |
11_GameUI.js:7479 / 9284 / 9337 |
房间全部信息描述文本 |
getRoomTopDescAry(roomtype) |
11_GameUI.js:8951 |
房间顶部一组描述(字符数组) |
getStarLimit(roomtype) |
11_GameUI.js:6874 / 7167 … |
星星场准入下限 |
getMult(roomtype,type) |
11_GameUI.js:6823 / 6921 / 7168 |
星星场倍数 |
getLeaveLimit(roomtype) |
11_GameUI.js:6948 |
离场限制 |
getVideoByRoomType(roomtype) |
据 roomtype 决定是否开视频(模板内当前为注释状态) | 0=不开、1=开 |
getRoomMode(roomtype) |
11_GameUI.js:1886 / 6721 / 7183 … |
是否金币场(1/0) |
roomtype的具体取值、房间描述文案、各返回值的格式均"由各子游戏自定义",本框架文档不展开。
2.2 子游戏 → 框架:可调用的 API(契约清单)
这是 CocosCreator 重写时必须在"平台 SDK"侧提供的接口。子游戏通过这些接口读取框架态、发包、复用平台 UI:
| 类别 | API | 作用 |
|---|---|---|
| 座位/身份 | Utl.getMySeat() |
我的绝对座位 |
Logic.ChangeToStatus(mySeat, targetSeat) |
绝对座位 → 以我为视角的相对位(UI 摆位关键) | |
C_Player.playerid / GameData.AgentId / GameData.GameId |
身份 | |
| 房间状态 | Desk.roomcode / Desk.roomtype / Desk.GetPlayerBySeat(seat) / Desk.PlayerList |
房间/座位玩家数据 |
Utl.getIsInfinite() / Utl.getIsDebugger() |
房间属性/调试 | |
Utl.setDeskStage(...) |
设置牌桌阶段 | |
| 座位展示 | Utl.setGrade(seat, value) |
设置某座积分显示 |
Utl.setPlayerPrepare(seat, ...) / Utl.getPlayerReadyState(seat) |
准备态读写 | |
| 发包(平台 RPC) | Net.Send_*(data) |
平台 RPC(开局/聊天/解散/创建房间…见 02/03 章) |
Net.Send_self_makewar(data) |
主动开战(摇一摇等触发) | |
| 发包(对局自定义) | Net._SendData(_app, _route, _rpc, _data) |
对局自定义包(出牌等);_app/_route/_rpc 取值由各子游戏自定义 |
| 平台 UI | GameUI.*(弹窗/按钮/提示等) |
复用平台通用界面 |
| 渲染引擎 | set_self/get_self/set_group/play_ani/set_clip/ifast_* |
直接操作精灵(见 00 章) |
| 配置/全局 | Game_Config.* / GameData.* |
配置与全局态 |
平台 RPC 的发包入口与字段见 02/03 章;对局自定义包统一走
Net._SendData(_app,_route,_rpc,_data),其_app/_route/_rpc与_data结构"由各子游戏自定义"。
2.3 状态归属:框架态 vs 子游戏态
| 框架维护 | 子游戏维护 | |
|---|---|---|
| 对象 | Desk、C_Player、GameData |
自建命名空间(对局状态对象,由各子游戏自定义) |
| 内容 | 房间/座位/玩家公共信息、连接、资产 | 纯对局数据(牌、轮次、结算明细等) |
| 来源 | 平台协议(01–04 章) | 对局协议(_ReceiveData 收到的 _msg.data) + deskinfo(重连快照) |
- 框架态:由
00_Surface/*平台层统一维护,跟随平台协议(登录/大厅/房间/解散/资产)更新,子游戏只读不写。 - 子游戏态:子游戏自建命名空间存放纯对局数据,来源是对局协议包与重连快照
deskinfo,结构由各子游戏自定义。
2.4 deskinfo 与断线重连机制(与 05 章一致)
- 触发条件:进房响应里携带
deskinfo字段时,平台在07_Desk.js:423调用Game_Modify.Reconnect(_msg.data.deskinfo)。判定依据是"响应含deskinfo"(07_Desk.js:419的if(_msg.data.deskinfo)),不是isbattle==1之类的标志位。 - deskinfo 含义:
deskinfo是子游戏对局态的完整快照,由服务器在开战时记录、在玩家重连时回发;其内部结构属对局协议,"由各子游戏自定义",本框架文档不展开。 - 模板实现:模板里
Game_Modify.Reconnect是空桩(02_SubGame_Input.js)。"用deskinfo还原对局状态"的具体逻辑由各子游戏实现,模板为空实现。 - 新前端约束:CocosCreator 重写时,对局态必须能从同一份
deskinfo完整还原,才能与旧服务器的重连流程兼容。
3. 对局驱动机制(子游戏的"心跳")
子游戏不轮询,而是靠引擎回调推进,全部经 gamemain.js(gameabc_face.*)转发到子游戏模块:
ontimer(...):子游戏用精灵定时器(set_self(spid, 57, 间隔ms))开启,在此驱动动画 / 回合节奏。具体定时器 spid 与节奏"由各子游戏自定义"。ani_doend(id,sx,count,allend):动画结束回调,用于串联动画链。gamemydraw / gamemydrawbegin:精灵自绘与裁剪(手牌滚动、勾选标记等)。mousedown / mouseup / mousemove:对局交互(选牌/滑动/出牌);模板gamemain.js已把这些引擎回调转发给GameUI、Game_Modify、gameCombat三方。gamestart:Logic.AppStart()后,子游戏在此把对局精灵组初始化(通常先隐藏)。
网络回调(
tcpmessage / tcpconnected / tcpdisconnected等)在gamemain.js里保持为空体——对局包统一从平台的_ReceiveData进入,不走引擎 TCP。模板gamemain.js的桥接职责就是"把引擎回调转发给平台/子游戏模块",本身不含业务逻辑。
4. 子游戏开发的通用模式(小结)
| 维度 | 框架提供 | 子游戏负责 |
|---|---|---|
平台协议层 00_Surface/* |
完整、稳定,不动 | 只读消费 |
三件套 01_SubGame/* |
空桩契约(钩子签名固定) | 填实现(接入点统一) |
gamemain.js |
引擎回调桥接(转发,tcp* 空体) |
扩展对局精灵初始化与驱动 |
对局 rpc(_ReceiveData) |
提供入口 | switch(_msg.rpc) 自分发,rpc 集合自定义 |
| 重连 | 提供 deskinfo 快照与 Reconnect 钩子 |
实现"快照 → 对局态"的还原 |
| 结算 | 提供 Free / 倍率面板等通用 UI |
据玩法走结算分支 |
| 专属模块 / 类库 / 美术 | 无 | 全部自定义 |
结论:换一款游戏 = 换 01_SubGame 三件套 + gamemain 定制 + 专属模块 + 美术数据;框架与协议是稳定底座。具体玩法(牌型、出牌规则、roomtype 取值、deskinfo 结构、专属模块拆分)均"由各子游戏自定义",不在本框架文档范围内。
5. CocosCreator 重写方案(设计建议)
目标不变:服务器零改动。因此无论哪种方案,"平台 SDK + 对局协议"的字节级一致是硬约束(01–05 章),可自由替换的是渲染与工程组织。本节为设计建议,须与前述源码事实(钩子契约、状态归属、deskinfo 重连机制)保持一致。
方案 A:忠实复刻(迁移成本低、风险小)
把原框架的分层直接映射到 Cocos:
NetworkLayer WebSocket 封装:信封{app,route,rpc,data}+双层解包+心跳(01章)
PlatformSDK 复刻 00_Surface:登录/大厅/房间/解散/聊天/资产(02–04章协议 1:1)
├─ Net Send_*/收包分发,含 _SendData(app,route,rpc,data) 对局自定义包通道
├─ Desk 房间/座位状态(04章)
├─ Player C_Player(04章)
└─ GameUI 大厅/房间通用界面(Cocos 场景/Prefab 重画,不复刻 spid)
SubGameModule 对局模块,暴露与 Game_Modify 等价的钩子接口:
_ReceiveData(msg) / StartWar / Reconnect(deskinfo) / DeskInfo /
myJoinRoom / playerJoinRoom / onReady / Free / getRoomInfo... (§2.1 全套)
EventBus 取代 gamemain.js 的统一分发(输入/帧/定时 → UI 与对局)
- 优点:与原游戏行为高度一致,便于逐个对照验证、对接旧
deskinfo。 - 适合:先快速上线、再逐步现代化。
方案 B:现代化重构(推荐,更高效)
保留协议层不变,对局层用 Cocos 的能力重做:
- 协议层 TypeScript 化:把 RpcList / 字段定义成 TS
interface与枚举,收发用泛型包裹,编译期校验字段。interface Envelope<T> { app: string; route: string; rpc: string; data: T } - 平台 SDK 做成独立模块/npm 包:
NetClient(连接/心跳/解包) +PlatformApi(login/room/...) +RoomStore(响应式房间态)。多款游戏复用,等价于原00_Surface。 - 对局用 Cocos 组件化:场景=Scene,玩家位/手牌/牌桌=Prefab+组件;动画用 Cocos Animation/tween 取代引擎定时器与
play_ani;骨骼继续用 Spine(原框架也用 Spine)。 - 对局接入用接口而非全局函数:定义
IGameModule { onReceive(rpc,data); onReconnect(deskinfo); render(state) },平台 SDK 通过它回调对局;对局通过注入的sdk调sendAction()/getMySeat()/seatToView()。这与原框架Game_Modify.*钩子 + 子游戏调Net/Desk/Utl的边界一一对应。 - 状态驱动渲染:对局态集中在一个 store(对应原框架的子游戏命名空间),
render(state)纯函数刷新。重连:响应含deskinfo时onReconnect(deskinfo)把快照灌入 store——与原框架"响应含 deskinfo 即触发 Reconnect"的判定一致(§2.4)。 - 座位视角换算保留:实现
seatToView(mySeat,target)等价Logic.ChangeToStatus,对局只关心"相对位"。
建议工程结构:
assets/
scripts/
net/ NetClient, Envelope, heartbeat, reconnect
platform/ PlatformApi(02/03章), RoomStore/PlayerStore(04章), 通用UI
game/<玩法>/ GameModule(实现 IGameModule), state, view 组件, prefab
core/ EventBus, seatUtil, types(协议 TS 定义)
两方案对比
| 方案 A 复刻 | 方案 B 现代化 | |
|---|---|---|
| 协议兼容 | ✅ | ✅(不动协议) |
| 上手速度 | 快 | 中 |
| 多游戏复用 | 一般 | 强(SDK 独立) |
| 可维护性/类型安全 | 一般 | 强 |
| 适合 | 首款快速验证 | 长期多游戏平台 |
共同底线:把"平台 SDK"和"对局模块"边界划清(即 §2.1 钩子契约 + §2.2 API 清单 + §2.4 重连机制),协议字段严格对齐 01–05 章。这样新前端对服务器而言与旧前端不可区分,即达成"完美适配、服务器零改动"。
6. 待补:具体对局字段
本章聚焦框架使用模式与钩子契约,未列任何对局包字段。某款子游戏的对局 rpc 详细字段(StartWar/Free/deskinfo 等结构)、roomtype 取值、专属模块划分等,均属该子游戏范畴;可在确定首款游戏后,对其 _ReceiveData 各 case 与发包点做一次专项提取,补入 05 章。