Files
youle_cocos/docs/protocol/06-子游戏开发模式与Cocos方案.md
T
joywayerandClaude Opus 4.8 08640ca89a docs(protocol): 交叉源码审计后重修全 8 篇协议文档
基于 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>
2026-06-28 11:35:47 +08:00

18 KiB
Raw Blame History

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 的能力重做:

  1. 协议层 TypeScript 化:把 RpcList / 字段定义成 TS interface 与枚举,收发用泛型包裹,编译期校验字段。
    interface Envelope<T> { app: string; route: string; rpc: string; data: T }
    
  2. 平台 SDK 做成独立模块/npm 包:NetClient(连接/心跳/解包) + PlatformApi(login/room/...) + RoomStore(响应式房间态)。多款游戏复用,等价于原 00_Surface。
  3. 对局用 Cocos 组件化:场景=Scene,玩家位/手牌/牌桌=Prefab+组件;动画用 Cocos Animation/tween 取代引擎定时器与 play_ani;骨骼继续用 Spine(原框架也用 Spine)。
  4. 对局接入用接口而非全局函数:定义 IGameModule { onReceive(rpc,data); onReconnect(deskinfo); render(state) },平台 SDK 通过它回调对局;对局通过注入的 sdk 调 sendAction()/getMySeat()/seatToView()。这与原框架 Game_Modify.* 钩子 + 子游戏调 Net/Desk/Utl 的边界一一对应。
  5. 状态驱动渲染:对局态集中在一个 store(对应原框架的子游戏命名空间),render(state) 纯函数刷新。重连:响应含 deskinfo 时 onReconnect(deskinfo) 把快照灌入 store——与原框架"响应含 deskinfo 即触发 Reconnect"的判定一致(§2.4)。
  6. 座位视角换算保留:实现 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 章。