初始化仓库:友乐/gameabc 房卡游戏平台脚手架(client + server + docs)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-01 18:13:26 +08:00
co-authored by Claude Opus 5
commit 594820d393
655 changed files with 310861 additions and 0 deletions
@@ -0,0 +1,171 @@
# 04 · 网络对接与启动编排
本篇讲**前端怎么和服务端打通、一局怎么启动**:发包链路、收包分发、新旧架构的对接边界、成败判定、启动顺序与 controllers/managers 的职责。
> **想看前后端端到端全链路**(一个包从前端出发 → 服务端三层路由/handler → 推回前端分发的完整往返、及各步在谁的哪个文件)见服务端 [`server/docs/development-guide/03 §6`](../../../server/docs/development-guide/03-数据收发与通信协议.md#6-端到端收发全链路前后端对照)。本篇聚焦**前端这一侧**的收发细节。
---
## 1. 发包:语义化发包封装 → RpcHelper
前端发起操作时**不直接拼包**,走两层封装:
```
业务/控制器
└─ 语义化发包封装(子游戏:一个操作一个语义化方法)
└─ RpcHelper(框架基座:自动注入平台字段,调引擎发送)
└─ Utl.sendData(app, route, rpc, data)
```
### RpcHelper(自动注入平台字段)
`RpcHelper` 把每个请求自动补齐平台必需字段,业务只传业务数据:
```js
// 自动注入:agentid / gameid / playerid / roomcode / seat ...
RpcHelper.sendGameRpc(rpc, gameData, options); // route = "room"/子游戏路由
```
### 语义化发包封装(子游戏)
子游戏为每个操作暴露一个语义化发包方法,内部补 `seat/roomcode` 等业务字段后经 `RpcHelper` 发送;业务侧只调这些语义化方法,不关心平台字段。
**DO**:发包一律走语义化发包封装 / `RpcHelper`。**DON'T**:在业务里直接 `Utl.sendData` 手拼包、漏注入平台字段。
> 平台字段缺失(如 `playerid` 为 0/空)应 fail-fast 暴露,不静默发出残缺包。
---
## 2. 收包:统一分发
服务端的主动推送统一格式 `{ rpc, data }`,前端有**唯一收包入口**,再按 `rpc` 分发到对应处理器。收包分发器是**纯路由表 + 分发器**:
```js
// 纯路由表 + 分发器:一 rpc 一处理器
var _handlers = {
'someRpc': function (d) { return someHandler.handleSomeRpc(d); },
'anotherRpc': function (d) { return anotherHandler.handleAnother(d); },
'error': function (d) { console.error('[dispatch]', d.message); }
// ...每个 rpc 一条
};
function dispatch(msg) { // msg = { rpc, data }
var h = _handlers[msg.rpc];
if (h) { h(msg.data); }
else { console.warn('[dispatch] 未处理的 rpc:', msg.rpc); }
}
```
**新增一种服务端推送** = 在路由表加一条 `rpc → 处理器` + 在对应处理器里写业务。**收包分发器只分发,不写业务逻辑。**
---
## 3. 新旧架构对接边界
平台事件先进旧的受限入口 `Game_Modify.*`,旧入口**只解包、只转交**,把数据委托给新架构:
| 平台入口(受限文件,尽量不改) | 转交到(新架构) | 作用 |
|--------------------------------|------------------|------|
| `Game_Modify.appStart()` | 启动编排 | 启动初始化 |
| `Game_Modify.StartWar(_msg)` | 开局处理 | 开局 |
| `Game_Modify._ReceiveData(_msg)` | 收包分发 | 对局推送分发 |
| `Game_Modify.Reconnect(info)` | 重连处理 | 断线重连/重画 |
### 开局解包要点(StartWar)
服务端 `makewar` 常用**差异化下发**(`sendtype:1` + `seatlist[]`)。`StartWar` 先按本座位取出自己的数据,再交给新架构的开局处理:
```js
Game_Modify.StartWar = function (_msg) {
var raw = _msg.data.deskwar || _msg.data;
var gameData = raw;
if (raw && raw.sendtype === 1 && Array.isArray(raw.seatlist)) {
for (var i = 0; i < raw.seatlist.length; i++) {
if (raw.seatlist[i].seat === C_Player.seat) { gameData = raw.seatlist[i].data; break; }
}
}
// 委托新架构的开局处理,受限入口本身不写业务
};
```
### 重连(Reconnect)= 重画
重连入口拿到服务端 `get_deskinfo` 的完整快照,交由新架构的重连处理**据本地数据重建整个界面**。重连与“切 app 重画”应复用同一条重画路径——**任何时候执行重画函数都能还原正确界面**(如网页刷新)。这与「数据优先、表现延后」一脉相承:界面永远能从数据无歧义地重建。
**红线**:受限文件 `Game_Modify.*` 里**只接不写**,业务逻辑全部在新架构 handler 中。
---
## 4. 成败判定:只认 `data.success`
> 与服务端 [`server/docs/development-guide/03`](../../../server/docs/development-guide/03-数据收发与通信协议.md) 同一条协议。
前端 RPC 没有“同步返回”,操作结果由服务端**后续主动推送**告知。判成败的唯一权威是推送 `data` 里的 **`success`**:
```js
// 处理器统一写法
function handleXxx(data) {
if (!data || !data.success) { /* 失败处理 */ return; }
// 成功逻辑
}
```
- **只认 `data.success`**,不用 `status`/`code` 判成败,不写 `status` 兼容兜底。
- 个别综合推送的 success 在**嵌套字段**(如 `gameSettleComplete` 的 `data.gameSettle.success`)——按该推送的契约取对应位置,仍以 `success` 语义为准,不改用 status。
---
## 5. 启动编排
一局前端的启动由一段**启动编排**一次性完成(经 `Game_Modify.appStart` 触发):
```
启动编排
1) 检查依赖 检查 EventBus/SpriteManager/UIManager/各 View 就绪
2) 初始化系统 SpriteManager.init / AnimationManager.init / UIManager.init
3) 注册视图 创建并注册各 View 到 UIManager
4) 标记初始化完成
```
启动后即等待平台事件:`StartWar` 开局、`_ReceiveData` 推送、`Reconnect` 重连,分别转交新架构。
---
## 6. controllers 与 managers 职责
| 类别 | 角色 | 典型成员 |
|------|------|----------|
| **controllers**(处理器) | 接收网络消息 → 改数据/UI → 必要时 `emit` 事件 | 按消息类型分工的处理器(流程、操作、结算、特殊规则等) |
| **managers**(资源/状态) | 管资源与跨模块能力 | 音频、Spine 回调分发、玩法标记、特效管理等 |
> 精灵交互事件**不属于**子游戏 controllers:它由框架 `gameabc-framework/system/SpriteEventController.js` 统一分发。子游戏在组件 `init` 里用 `SpriteEventController.registerMouseDown/registerMouseUp/registerMouseMove/...` 注册按精灵 ID 的回调;需对每个绘制精灵统一处理的能力用 `registerGlobalDraw` 注册全局 draw 钩子。平台入口 `Game_Modify.utlmousedown/mouseup/...` 只把事件转调给它。
调用链示例:
```
平台 _ReceiveData(_msg)
→ 收包分发(_msg)
→ 操作处理器(data)
├ 改数据模型
├ 播动画(经游戏动画封装)
├ 动画回调里刷新静态界面
└ 播音效(经游戏音频管理)
```
**红线**:业务逻辑放 controllers/managers,**不**写进受限的 `Game_Modify.*`;处理器只处理自己那类消息,需要别的能力调对应 manager,不重造。
---
## 7. 本篇 DO / DON'T
| DO ✅ | DON'T ❌ |
|------|---------|
| 发包走语义化发包封装/`RpcHelper` | 业务里直接 `Utl.sendData` 手拼包 |
| 收包统一进收包分发器,一 rpc 一处理器 | 在 `Game_Modify.*` 或散点里直接处理推送 |
| 受限入口只解包转交,业务在新架构 handler | 在受限文件堆业务逻辑 |
| 成败只认 `data.success` | 用 `status`/`code` 判成败、写 status 兜底 |
| 重连/重画复用同一路径,据数据重建界面 | 重连单写一套与正常对局不同的渲染 |
| 业务在 controllers/managers,各司其职 | 处理器交叉重造别人的能力 |
下一篇 [05-开发规范与红线](./05-开发规范与红线.md) 汇总前端所有工程纪律。
</content>