新规hook
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 01 · 服务端环境与框架基础
|
||||
|
||||
本篇建立**全局认知**:子游戏运行在什么环境里、平台框架由哪些对象构成、一个数据包如何被路由到你的代码、一个房间从生到死经历哪些阶段。读懂这一篇,后面的接入与协议才有坐标。
|
||||
本篇建立**全局认知**:子游戏运行在什么环境里、平台框架由哪些对象构成、一个数据包如何被路由到你的代码、一个房间从生到死经历哪些阶段。
|
||||
|
||||
> 举例以麻将类房卡游戏为主,但本篇所有机制对任意子游戏一致。
|
||||
|
||||
@@ -23,14 +23,13 @@
|
||||
2. **`require` 只能在文件开头的守卫块内**:
|
||||
|
||||
```js
|
||||
// ✅ 正确:集中在顶部守卫块;运行时按全局名引用
|
||||
if (typeof require !== 'undefined') {
|
||||
var GameStateManager = require('./dataStructures/GameStateManager.js');
|
||||
}
|
||||
// ... 之后直接用全局名 GameStateManager.xxx() ——浏览器由 mod.js 加载为同名全局
|
||||
// 之后按全局名引用 GameStateManager.xxx()
|
||||
```
|
||||
|
||||
浏览器运行时没有 `require`,任何**无守卫的、函数体内的中途 `require`** 都会抛 `ReferenceError: require is not defined`,导致功能崩溃。跨模块统一**按全局名引用**:Node 由守卫块 `var X = require(...)` 得到,浏览器由 `mod.js` 加载的同名全局解析,二者靠共享全局作用域下的同名 `var` 统一。
|
||||
浏览器运行时没有 `require`,任何**无守卫的、函数体内的中途 `require`** 都会抛 `ReferenceError: require is not defined`。跨模块统一**按全局名引用**:Node 由守卫块 `var X = require(...)` 得到,浏览器由 `mod.js` 加载的同名全局解析,二者靠共享全局作用域下的同名 `var` 统一。
|
||||
|
||||
### 前后端是物理分离的两端
|
||||
|
||||
@@ -202,4 +201,3 @@ var mod_<你的游戏> = mod_<你的游戏> || cls_mod.new("mod_<你的游戏>",
|
||||
- 房间生命周期由 export/import 接缝串起:`makewar` 开局、`get_deskinfo` 重连、`deduct_roomcard` 首局扣卡、`save_grade` 终局保存并自动回收。
|
||||
|
||||
下一篇 [02-子游戏接入与开发流程](./02-子游戏接入与开发流程.md) 讲:这三个文件具体怎么写、8+4 个接口各做什么、一次操作的完整数据流。
|
||||
</content>
|
||||
|
||||
@@ -32,7 +32,7 @@ server/<游戏容器目录>/<你的游戏>/ (容器目录名由接入方
|
||||
└── tests/ 单元/集成测试
|
||||
```
|
||||
|
||||
> 举例(麻将):`game/RoomAdapter.js`(房间↔对局适配)、`game/GameController.js`(对局编排)、`rpc/BroadcastManager.js`(差异化广播)、`rpc/handlers/*`(各操作处理)。换个玩法,分层思路不变,文件内容不同。
|
||||
> 举例(麻将):`game/RoomAdapter.js`(房间↔对局适配)、`game/GameController.js`(对局编排)、`rpc/BroadcastManager.js`(差异化广播)、`rpc/handlers/*`(各操作处理)。换个玩法,分层思路不变。
|
||||
|
||||
---
|
||||
|
||||
@@ -51,17 +51,12 @@ var mod_<你的游戏> = mod_<你的游戏> || cls_mod.new("mod_<你的游戏>",
|
||||
|
||||
### 2.2 按依赖顺序加载文件
|
||||
|
||||
被依赖的先加载;`export.js`/`import.js` 通常在业务类之后加载(它们会用到业务模块):
|
||||
被依赖的先加载;`export.js`/`import.js` 通常在业务类之后加载(它们会用到业务模块)。浏览器/友乐用 `min_loadJsFile` 异步链式加载:
|
||||
|
||||
```js
|
||||
// 浏览器/友乐:min_loadJsFile 异步链式加载
|
||||
min_loadJsFile("<容器目录>/<你的游戏>/常量与工具.js", function(){
|
||||
min_loadJsFile("<容器目录>/<你的游戏>/数据结构.js", function(){
|
||||
min_loadJsFile("<容器目录>/<你的游戏>/export.js", function(){
|
||||
min_loadJsFile("<容器目录>/<你的游戏>/import.js", function(){
|
||||
min_loadJsFile("<容器目录>/<你的游戏>/业务与rpc.js", function(){
|
||||
console.log("模块 [" + mod_<你的游戏>.modname + "] 加载完成");
|
||||
});});});});});
|
||||
min_loadJsFile("<容器目录>/<你的游戏>/export.js", function(){ /* ...嵌套加载 import.js、业务与rpc.js... */ });
|
||||
});
|
||||
```
|
||||
|
||||
> 推荐分层加载顺序:**常量 → 工具 → 数据结构 → export/import → 收发包层 → 业务层 → 规则引擎**。Node 测试侧用 `require`(写在守卫块内),浏览器侧用 `min_loadJsFile`,两套并存但按全局名统一引用(见 01)。
|
||||
@@ -83,7 +78,6 @@ RPC 方法是**前后端交互的服务端入口**。以下是必须遵守的规
|
||||
RPC 方法本身**不写业务逻辑**,只把 `pack` 转交给收发包层的 handler;真正的参数校验、业务编排、广播都在 handler 里:
|
||||
|
||||
```js
|
||||
// mod_<你的游戏> 上挂的 RPC 方法(示例名,可自定)
|
||||
mod_<你的游戏>.playCard = function(pack) {
|
||||
// 就绪守卫:handler 由 min_loadJsFile 异步加载,未就绪时防御性返回(见规则四)
|
||||
if (typeof RpcHandler === 'undefined' || !RpcHandler) {
|
||||
@@ -91,9 +85,6 @@ mod_<你的游戏>.playCard = function(pack) {
|
||||
}
|
||||
return RpcHandler.handlePlayCard(pack); // 委托到收发包层,真正逻辑在这里
|
||||
};
|
||||
mod_<你的游戏>.declareHu = function(pack) {
|
||||
return RpcHandler.handleDeclareHu(pack);
|
||||
};
|
||||
```
|
||||
|
||||
> `RpcHandler`、`handlePlayCard` 这些是**本项目的命名示例**;换成任何风格都行,关键是"薄入口 + 委托"的分层。
|
||||
@@ -184,14 +175,13 @@ mod_<你的游戏>.export = cls_<游戏>_export.new(); // 文件末尾自动
|
||||
|
||||
```js
|
||||
exp.makewar = function(o_room, o_game_config) {
|
||||
// 1) 创建子游戏牌桌对象,并与房间建立【双向引用】
|
||||
// 1) 创建牌桌对象,与房间建立【双向引用】
|
||||
if (!o_room.o_desk) { o_room.o_desk = {}; }
|
||||
if (!o_room.o_desk.data) { o_room.o_desk.data = {}; }
|
||||
o_room.o_desk.o_room = o_room; // 反向引用
|
||||
|
||||
// 2) 创建对局状态,挂到 o_room.o_desk.data.*(房间隔离的落点)
|
||||
var gameState = createGameState(o_room, o_game_config);
|
||||
o_room.o_desk.data.gameState = gameState; // 此后所有业务都从这里读对局态
|
||||
o_room.o_desk.data.gameState = createGameState(o_room, o_game_config);
|
||||
|
||||
// 3) 返回开战数据包(通常按座位差异化下发)
|
||||
return {
|
||||
@@ -217,26 +207,15 @@ exp.makewar = function(o_room, o_game_config) {
|
||||
|
||||
## 4. `import.js` —— 子游戏调用平台的 4 个接口
|
||||
|
||||
这 4 个接口是对平台 `youle_room.export` 的薄封装,**逻辑由平台实现,你只管在正确时机调用**:
|
||||
这 4 个接口是对平台 `youle_room.export` 的薄封装,**逻辑由平台实现,你只管在正确时机调用**。每个都形如 `imp.<接口> = function(...args){ return mod_<你的游戏>.app.youle_room.export.<接口>(...args); }`,本项目 4 个:
|
||||
|
||||
```js
|
||||
mod_<你的游戏>.import = (function() {
|
||||
var imp = {};
|
||||
imp.check_player = function(agentid, gameid, roomcode, seat, playerid, conmode, fromid) {
|
||||
return mod_<你的游戏>.app.youle_room.export.check_player(
|
||||
agentid, gameid, roomcode, seat, playerid, conmode, fromid);
|
||||
};
|
||||
imp.deduct_roomcard = function(o_room) {
|
||||
return mod_<你的游戏>.app.youle_room.export.deduct_roomcard(o_room);
|
||||
};
|
||||
imp.save_grade = function(o_room, o_gameinfo1, o_gameinfo2, freeroomflag) {
|
||||
return mod_<你的游戏>.app.youle_room.export.save_grade(
|
||||
o_room, o_gameinfo1, o_gameinfo2, freeroomflag);
|
||||
};
|
||||
imp.finish_gametask = function(agentid, o_player, taskid, finishamount) {
|
||||
return mod_<你的游戏>.app.youle_room.export.finish_gametask(
|
||||
agentid, o_player, taskid, finishamount);
|
||||
};
|
||||
imp.check_player = function(agentid, gameid, roomcode, seat, playerid, conmode, fromid) { /* → youle_room.export.check_player(...) */ };
|
||||
imp.deduct_roomcard = function(o_room) { /* → youle_room.export.deduct_roomcard(o_room) */ };
|
||||
imp.save_grade = function(o_room, o_gameinfo1, o_gameinfo2, freeroomflag) { /* → youle_room.export.save_grade(...) */ };
|
||||
imp.finish_gametask = function(agentid, o_player, taskid, finishamount) { /* → youle_room.export.finish_gametask(...) */ };
|
||||
return imp;
|
||||
})();
|
||||
```
|
||||
@@ -306,4 +285,3 @@ mod_<你的游戏>.import = (function() {
|
||||
- [ ] 改了下发结构,前端 `StartWar`/`Reconnect`/对应操作解析同步检查。
|
||||
|
||||
下一篇 [03-数据收发与通信协议](./03-数据收发与通信协议.md) 详解包结构、发包方式、主动推送与 `success` 成败协议。
|
||||
</content>
|
||||
|
||||
@@ -27,26 +27,25 @@
|
||||
|
||||
`route` 决定包被投到**哪个模块**,是前后端必须对齐的字符串。它的权威定义与匹配链如下(均可在代码验证):
|
||||
|
||||
- **服务端:`routename` 的唯一定义点**是 `mod.js` 里创建模块的 `cls_mod.new(模块名, 路由名, 所属应用)` 的**第二个参数**:
|
||||
- **服务端:`routename` 的唯一定义点**是 `mod.js` 里 `cls_mod.new(模块名, 路由名, 所属应用)` 的**第二个参数**:
|
||||
|
||||
```js
|
||||
// server/games2/<你的游戏>/mod.js
|
||||
// server/games2/<你的游戏>/mod.js —— 第二参 "<你的游戏>" 即 routename(route 要用的值)
|
||||
var mod_<你的游戏> = global.mod_<你的游戏>
|
||||
|| cls_mod.new("mod_<你的游戏>", "<你的游戏>", youle_app);
|
||||
// ▲ 第一参 modname ▲ 第二参 routename(就是 route 要用的值)
|
||||
```
|
||||
|
||||
`cls_mod.new` 把第二参存进 `mod.routename`,并把模块 `push` 进 `app.modlist`(`server/class/class.mod.js`)。
|
||||
- **它是你自定义的字符串**:由你起名,只要**在应用内唯一**即可;与目录名、与模块名 `modname`(第一参,用于全局暴露 `global[modname]`、`app[modname]`)都**无强制绑定**——本项目三者恰好都叫 `jinxianmahjong` 只是约定(见 02 §2.1、01 §5)。
|
||||
- **平台按它匹配模块**:收包时 `cls_app.ReceivePack` 用 `pack.route == modlist[i].routename` 找到模块,再 `DoPack` 进第三层按 `rpc` 调方法(`server/class/class.app.js`,见 01 §4)。
|
||||
- **前端发包的 `route` 必须与它逐字一致**:前端把该值固化为常量(本项目 `codes/game/network/RpcSender.js` 里 `var ROUTE_NAME = 'jinxianmahjong'`,经 `Utl.sendData(app, route, rpc, data)` 发出)。**两端字符串不一致 → 平台匹配不到模块,包被静默丢弃**(前端也收不到任何响应)。
|
||||
- **与平台房间模块区分**:平台自带的房间模块 `routename` 是 `"room"`,处理创建/加入/开战(`createRoom`、`self_join_room`、`self_makewar` 等);**你的子游戏自定义 RPC**(`playCard` 等)走**你自己的 `routename`**。两者不要混用:平台流程发 `route:"room"`,玩法操作发 `route:"<你的游戏>"`。
|
||||
- **与平台房间模块区分**:平台自带房间模块 `routename` 是 `"room"`,处理创建/加入/开战(`createRoom`、`self_join_room`、`self_makewar` 等);**你的子游戏自定义 RPC**(`playCard` 等)走**你自己的 `routename`**。两者不要混用:平台流程发 `route:"room"`,玩法操作发 `route:"<你的游戏>"`。
|
||||
|
||||
> 一句话:**`routename` 在服务端 `cls_mod.new` 第二参定义(自定、应用内唯一),前端发包 `route` 必须与它逐字相同,平台据此把包投到你的模块**;改名要两端同步改。
|
||||
|
||||
### 1.2 发包必须自带前端界面所需的全部核心数据
|
||||
|
||||
**前端以服务端数据为权威——只做界面展示与交互、不做权威计算**(见 [`client 05`](../../client/development-guide/05-开发规范与红线.md))。推论落到服务端:**每个下发包都必须携带前端渲染/刷新该界面所需的全部核心数据**,让前端「据包写入数据 → 直接刷新出界面」,而**不需要前端自行推算、补全或兜底**。
|
||||
**前端以服务端数据为权威——只做界面展示与交互、不做权威计算**(见 [前端 05 开发规范与红线](../../client/development-guide/05-开发规范与红线.md))。推论落到服务端:**每个下发包都必须携带前端渲染/刷新该界面所需的全部核心数据**,让前端「据包写入数据 → 直接刷新出界面」,而**不需要前端自行推算、补全或兜底**。
|
||||
|
||||
- **界面要用的字段都要发全**:凡界面要显示、或前端 set/refresh 要用到的核心字段(出了什么牌、轮到谁、各家剩余张数、手牌/副露、分数/比分、倒计时锚点、庄家/座位、各类状态标志……)都要放进 `data`,不能让前端"猜"或本地推算权威结果。
|
||||
- **漏发是服务端的缺陷,不许前端补**:前端遵循数据权威原则——权威字段缺失应显式报错/留空、不用 `|| 0`/`|| []` 兜造(见 client 05)。所以服务端漏发 = 前端界面缺数据,**修在服务端发包处,不在前端补洞**。
|
||||
@@ -65,9 +64,7 @@
|
||||
```js
|
||||
XxxHandler.handlePlayCard = function(pack) { // 由 mod_<游戏>.playCard 委托进来
|
||||
try {
|
||||
// 1) 提取并校验参数:列出必填字段 + 数值型字段的类型要求
|
||||
// (本项目用统一工具 ValidationHelper.extractAndValidateParams 做这件事)
|
||||
// 等价于:agentid/playerid/gameid/roomcode/seat 必填,playerid/roomcode/seat 需为数值
|
||||
// 1) 提取并校验参数:必填字段 + 数值型字段类型(本项目用 ValidationHelper.extractAndValidateParams)
|
||||
var params = extractAndValidateParams(
|
||||
pack,
|
||||
['agentid', 'playerid', 'gameid', 'roomcode', 'seat', 'cardUniqueId'],
|
||||
@@ -85,15 +82,10 @@ XxxHandler.handlePlayCard = function(pack) { // 由 mod_<游戏>.playCard 委
|
||||
var o_desk = o_room.o_desk;
|
||||
if (!o_desk) return { success: false, error: '游戏桌不存在' };
|
||||
|
||||
// 4) 调试记录(若框架提供)——便于复盘
|
||||
if (o_desk.debug && o_desk.debug.save_receivepack) {
|
||||
o_desk.debug.save_receivepack(pack, p.seat, p.playerid);
|
||||
}
|
||||
|
||||
// 4) 调试记录(若框架提供 o_desk.debug.save_receivepack)——便于复盘
|
||||
// 5) 业务校验 + 执行业务:委托权威模块(handler 不内联规则,见 04)
|
||||
// 如:OperationExecutor.executePlayCard(o_room, {...})
|
||||
// 6) 构建响应 + 【主动推送】:ResponseBuilder 组包 → BroadcastManager 逐座位 sendpack_toseat
|
||||
// 推送 data 自带 success(见 §5);return 不是下发通道(见 §4)
|
||||
// 6) 构建响应 + 【主动推送】:组包 → 逐座位 sendpack_toseat;推送 data 自带 success(见 §5);
|
||||
// return 不是下发通道(见 §4)
|
||||
} catch (e) { /* 记录日志;必要时给该座推送失败包 */ }
|
||||
};
|
||||
```
|
||||
@@ -126,11 +118,8 @@ for (var seat = 0; seat < o_room.seatlist.length; seat++) {
|
||||
app: "youle", route: "<游戏>", rpc: "playCard",
|
||||
data: deepCopy(baseData) // 公共信息
|
||||
};
|
||||
if (seat === actionSeat) {
|
||||
msg.data.handCards = hands[seat]; // 仅本人可见手牌
|
||||
} else {
|
||||
msg.data.handCards = []; // 他人看不到
|
||||
}
|
||||
// 敏感信息只发本人:本人给真实手牌,他人给 []
|
||||
msg.data.handCards = (seat === actionSeat) ? hands[seat] : [];
|
||||
o_room.method.sendpack_toseat(msg, seat);
|
||||
}
|
||||
```
|
||||
@@ -163,23 +152,13 @@ for (var seat = 0; seat < o_room.seatlist.length; seat++) {
|
||||
### 正反例
|
||||
|
||||
```js
|
||||
// ❌ 错误:推送只带 status,前端读 data.success 恒 undefined → 误判失败
|
||||
o_room.method.sendpack_toseat({
|
||||
app:"youle", route:"<游戏>", rpc:"setHostingState",
|
||||
data: { status: 200, hosting: true } // 少了 success
|
||||
}, seat);
|
||||
// ❌ 推送只带 status、少了 success → 前端读 data.success 恒 undefined,误判失败
|
||||
data: { status: 200, hosting: true }
|
||||
// ✅ 成败语义放 success,status 仅作细分
|
||||
data: { success: true, status: 200, hosting: true }
|
||||
|
||||
// ✅ 正确:成败语义放 success,status 仅作细分
|
||||
o_room.method.sendpack_toseat({
|
||||
app:"youle", route:"<游戏>", rpc:"setHostingState",
|
||||
data: { success: true, status: 200, hosting: true }
|
||||
}, seat);
|
||||
```
|
||||
|
||||
```js
|
||||
// 前端:只认 success
|
||||
// 前端:只认 success,status/code 仅用于展示或日志
|
||||
if (!data.success) { /* 失败处理 */ return; }
|
||||
// data.status / data.code 仅用于展示或日志细分
|
||||
```
|
||||
|
||||
> 典型事故:托管状态推送 `{ status: 200, ... }` 不带 `success`,前端读 `data.success` 恒为 `undefined`,于是"已进入托管却报失败"。根因就是违反了本协议第 3 条。
|
||||
@@ -253,7 +232,7 @@ if (!data.success) { /* 失败处理 */ return; }
|
||||
| 重连/中途加入 | `export.get_deskinfo` 的返回 | `Game_Modify.Reconnect(_deskinfo)` → 重画 |
|
||||
| 对局/推送 | RPC handler 或主动推送(按 `rpc`) | 收包分发表里同名 `rpc` 的处理器 |
|
||||
|
||||
**改服务端下发结构 = 同步核对前端对应 `rpc` 的解析**;**新增一种推送 = 服务端选定 `rpc` + 前端在分发表加同名处理器**。任一端单方面改,另一端必按旧结构解析出错。前端侧的收发细节以 [`docs/client/development-guide/04-网络对接与启动编排`](../../client/development-guide/04-网络对接与启动编排.md) 为权威。
|
||||
**改服务端下发结构 = 同步核对前端对应 `rpc` 的解析**;**新增一种推送 = 服务端选定 `rpc` + 前端在分发表加同名处理器**。任一端单方面改,另一端必按旧结构解析出错。前端侧的收发细节以 [前端 04 网络对接与启动编排](../../client/development-guide/04-网络对接与启动编排.md) 为权威。
|
||||
|
||||
---
|
||||
|
||||
@@ -274,4 +253,3 @@ if (!data.success) { /* 失败处理 */ return; }
|
||||
- 改下发结构必同步核对前端 `StartWar`/`Reconnect`/对应 `rpc` 解析。
|
||||
|
||||
下一篇 [04-开发规范与红线](./04-开发规范与红线.md) 汇总所有必须遵守的工程纪律。
|
||||
</content>
|
||||
|
||||
@@ -38,14 +38,12 @@
|
||||
- 跨模块运行时**按全局名引用**:Node 由守卫块 `var X = require(...)` 得到,浏览器由 `mod.js` 加载的同名全局解析。
|
||||
|
||||
```js
|
||||
// ✅ 正确
|
||||
// ✅ 标准形态:require 仅在顶部守卫块,函数体内直接用全局名
|
||||
if (typeof require !== 'undefined') {
|
||||
var GameStateManager = require('./dataStructures/GameStateManager.js');
|
||||
}
|
||||
function foo() { GameStateManager.doSomething(); } // 直接用全局名
|
||||
|
||||
// ❌ 错误:函数体内中途 require —— 浏览器崩溃
|
||||
function bar() { var GSM = require('./dataStructures/GameStateManager.js'); }
|
||||
function foo() { GameStateManager.doSomething(); }
|
||||
// ❌ 函数体内中途 require → 浏览器崩溃:function bar(){ var GSM = require('...'); }
|
||||
```
|
||||
|
||||
### 双运行时全局暴露陷阱
|
||||
@@ -60,6 +58,7 @@ function bar() { var GSM = require('./dataStructures/GameStateManager.js'); }
|
||||
- **禁止猜测兜底**:不用默认值掩盖缺失数据。**关键权威字段缺失要显式报错或返回 `null`**,由上层决定是否终止,**禁止** `|| 0`、`|| []`、`|| ''`、双源回退等掩盖。
|
||||
- **下游不修补**:下游不得为"让流程跑起来"而补写/重算上游本该提供的数据;发现下游在修补,应把责任前移到权威来源。
|
||||
- **边界**:只有展示层、纯 UI 兼容、非关键日志字段,才可在明确边界内用安全默认值。
|
||||
- **下发面要发全**:因前端以服务端为权威、不自算权威结果,服务端**每个下发包必须携带前端界面所需的全部核心数据**(界面要显示、或前端 set-refresh 要用的字段都发全);漏发 = 前端缺数据,**修在服务端发包处、前端不补洞**(详见 [03 §1.2](./03-数据收发与通信协议.md))。
|
||||
|
||||
> 审查信号:看到 `|| []`、`|| 0`、`|| ''`、三元默认、双源字段,先判断它是不是**权威字段**(影响发牌/庄家/手牌/规则/结算/重连/状态流转)。是 → 改成权威读取;只是展示/日志 → 才可保留默认。
|
||||
|
||||
@@ -167,7 +166,7 @@ function bar() { var GSM = require('./dataStructures/GameStateManager.js'); }
|
||||
| 范围 | 只改子游戏目录 `<容器目录>/<游戏>/` 与允许的前端范围 |
|
||||
| 语言 | 纯 ES5;`require` 仅在顶部守卫块 |
|
||||
| 成败 | 只认 `data.success`,推送必自带,禁 `status` 兜底 |
|
||||
| 数据 | 权威唯一、缺失报错/返回 null、禁兜底、下游不修补 |
|
||||
| 数据 | 权威唯一、缺失报错/返回 null、禁兜底、下游不修补;下发包发全前端界面所需核心数据 |
|
||||
| 职责 | 一职能一模块,调用不重造 |
|
||||
| 隔离 | 状态挂 `o_desk.data.*`,禁全局;`房间+seat` 作 key;定时器随房清理 |
|
||||
| 自动操作 | 复用真人链路,对前端透明 |
|
||||
@@ -178,4 +177,3 @@ function bar() { var GSM = require('./dataStructures/GameStateManager.js'); }
|
||||
---
|
||||
|
||||
至此,从框架运作(01)、子游戏接入(02)、收发协议(03)到工程红线(04),构成一套完整的服务端子游戏开发指导。回到 [README](./README.md) 查看导航与一页纸模型。
|
||||
</content>
|
||||
|
||||
@@ -52,7 +52,7 @@ o_room.method.sendpack_toseat 按座位取连接信息(conmode/fromid) → 下
|
||||
- **平台框架**负责:网络收发、应用/模块/房间/玩家对象、路由、房卡、战绩、房间生命周期。
|
||||
- **子游戏**负责:玩法规则、对局状态、每个操作的处理与广播。两者通过 **export / import** 两组接口对接。
|
||||
|
||||
> 上图是**入站半程**(前端 → 服务端 handler)。完整的往返链路(含服务端 `sendpack_toseat` 推回 → 前端 `Game_Modify._ReceiveData` 按 `rpc` 分发)见 [03 §6「端到端收发全链路」](./03-数据收发与通信协议.md#6-端到端收发全链路前后端对照),前端侧细节见 [`docs/client/development-guide/04`](../../client/development-guide/04-网络对接与启动编排.md)。
|
||||
> 上图是**入站半程**(前端 → 服务端 handler)。完整的往返链路(含服务端 `sendpack_toseat` 推回 → 前端 `Game_Modify._ReceiveData` 按 `rpc` 分发)见 [03 §6「端到端收发全链路」](./03-数据收发与通信协议.md#6-端到端收发全链路前后端对照),前端侧细节见 [前端 04 网络对接与启动编排](../../client/development-guide/04-网络对接与启动编排.md)。
|
||||
|
||||
---
|
||||
|
||||
@@ -91,8 +91,8 @@ o_room.method.sendpack_toseat 按座位取连接信息(conmode/fromid) → 下
|
||||
|
||||
## 与既有文档的关系
|
||||
|
||||
- 本套文档是**落地版**:结合本仓库真实代码,给出可直接照做的接入步骤与红线,并对其中已被本项目实践修正的部分(如成败标志由 `status` 收敛为 `success`)以本套为准。
|
||||
- 本套文档是平台级收发包/子游戏开发规范的**落地版**:结合本仓库真实代码,给出可直接照做的接入步骤与红线,并对其中已被本项目实践修正的部分(如成败标志由 `status` 收敛为 `success`)以本套为准。
|
||||
- 各子游戏内部的架构细节(模块划分、算法)仍以各自 `<游戏容器目录>/<游戏>/docs/` 为准。
|
||||
- **平台无关的通用工程与架构规范**(分层、可扩展模式、配置化、数据权威、反模式与审查清单)见 [`docs/games/engineering/`](../../games/engineering/):本套讲"平台怎么接、红线是什么",`engineering/` 讲"该怎么设计、怎么长久演进",互补阅读。
|
||||
- **平台无关的通用工程与架构规范**(分层、可扩展模式、配置化、数据权威、反模式与审查清单)见 [工程与架构通则](../../games/engineering/):本套讲"平台怎么接、红线是什么",工程通则讲"该怎么设计、怎么长久演进",互补阅读。
|
||||
</content>
|
||||
</invoke>
|
||||
|
||||
Reference in New Issue
Block a user