降耗②·铺开:按试点尺度精简其余 12 篇编号正文
沿用 client 02 的保守尺度(规范条款/表格/关键代码/标题/链接全保留, 只压缩冗长代码示例、重复解说、演进历史、✅/❌ 成对代码块)逐篇精简 client 01/03/04/05/06、server 01/02/03/04、engineering 01/02/03。 - 12 篇合计 78,171 → 74,726 字符(省 3,445,~4.4%);红线密集篇(client 05、server 04)极保守、几乎不动,符合"不丢规范优先于省字数"。 - 已核验:51 个跨文档链接目标全部存在、3 个锚点全部命中真实标题; server 03 §6、server 04 §8/§10/§11 等被引用小节标题逐字未改;README 未动。 - 每篇均随附规范保留清单逐条自查(并行子代理完成、逐份复核)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 01 · 前端架构与运行环境
|
||||
|
||||
本篇建立**全局认知**:前端跑在什么环境、由哪几层构成、新旧两套架构如何并存、文件按什么顺序加载。读懂这一篇,后面的渲染、系统、网络才有坐标。
|
||||
本篇建立**全局认知**:前端跑在什么环境、由哪几层构成、新旧两套架构如何并存、文件按什么顺序加载。
|
||||
|
||||
> 举例以麻将为主,但 `gameabc-framework` 是游戏中立的通用框架,本篇机制对任意子游戏一致。
|
||||
|
||||
@@ -40,7 +40,7 @@ js/01_SubGame/ 子游戏前端
|
||||
|
||||
> `codes/` 内部如何分目录、如何命名文件,均由子游戏自行决定,本套文档不作规定;唯一例外是 `shared/`——它是服务端共享算法的同步副本,前端只读(见 05)。
|
||||
|
||||
依赖方向是**单向**的:`01_SubGame/codes` → `gameabc-framework` → `gameabc.min.js`。框架**不反向依赖**任何子游戏(这正是上一步把 `EventBus` 里的麻将事件剥离出去的原因,见 03/05 的「框架中立」)。
|
||||
依赖方向是**单向**的:`01_SubGame/codes` → `gameabc-framework` → `gameabc.min.js`。框架**不反向依赖**任何子游戏(见 03/05 的「框架中立」)。
|
||||
|
||||
---
|
||||
|
||||
@@ -88,8 +88,6 @@ js/01_SubGame/ 子游戏前端
|
||||
1. **被依赖者先加载**。例如框架 `EventBus.js` 必须在子游戏事件常量之前、事件常量又必须在任何注册这些事件的视图之前;整合所有精灵常量的入口文件必须**最后**加载。
|
||||
2. **新增文件要插对位置**。新增一个常量/组件文件,必须在 `index.html` 里插到其依赖之后、使用者之前,否则运行时拿到 `undefined`。
|
||||
|
||||
> 示例:把玩法专属事件从框架剥到子游戏事件常量文件时,正是把它插在 `EventBus.js`(框架)之后、其使用者(视图组件)之前,引用零改动。
|
||||
|
||||
---
|
||||
|
||||
## 5. codes/ 内部组织
|
||||
@@ -111,4 +109,3 @@ js/01_SubGame/ 子游戏前端
|
||||
- 加载顺序即依赖,新增文件务必插对位置。
|
||||
|
||||
下一篇 [02-渲染与UI组件体系](./02-渲染与UI组件体系.md) 讲:精灵怎么画、资源常量怎么组织、UI 组件怎么写。
|
||||
</content>
|
||||
|
||||
@@ -21,18 +21,13 @@
|
||||
throw new Error('[GameEvents] EventBus 未加载;请检查 index.html 加载顺序');
|
||||
}
|
||||
var E = EventBus.Events;
|
||||
// 通用语义事件
|
||||
E.GAME_STARTED = 'game:started';
|
||||
E.PLAYER_DISCARDED = 'player:discarded';
|
||||
E.SCENE_CHANGED = 'ui:sceneChanged';
|
||||
// 玩法专属事件
|
||||
E.TILES_DEALT = 'mahjong:tilesDealt';
|
||||
E.MELD_FORMED = 'mahjong:meldFormed';
|
||||
E.GAME_STARTED = 'game:started'; // 通用语义事件
|
||||
E.TILES_DEALT = 'mahjong:tilesDealt'; // 玩法专属事件
|
||||
// ...本子游戏用到的全部事件
|
||||
})();
|
||||
```
|
||||
|
||||
> 这正是本仓库的落点:框架 `EventBus` 不预置任何事件常量、保持纯粹中立,所有事件(含通用语义)都由子游戏在其事件常量文件定义——既保证框架可被任意玩法复用,也避免「引用了未定义常量」导致的 `emit(undefined)`(见 05「框架中立」)。
|
||||
> 框架 `EventBus` 不预置任何事件常量、保持纯粹中立,避免「引用了未定义常量」导致的 `emit(undefined)`(见 05「框架中立」)。
|
||||
|
||||
### 用法
|
||||
|
||||
@@ -60,7 +55,7 @@ EventBus.once(EventBus.Events.GAME_FINISHED, fn); // 一次性
|
||||
3) 动画完成回调里只刷新静态界面(refresh)
|
||||
```
|
||||
|
||||
好处:动画是“锦上添花”,**即使不播动画,静态界面也始终正确**(据组件 `this.data` 调 `refresh` 即可还原,见 02 组件范式 / 04 重连)。**动画的开始/结束/出错等生命周期回调里只刷新界面、绝不设置核心数据**;动画期间**绝不修改游戏数据**。这样动画卡住/播错、或开发前期没有动画时,数据、逻辑与界面依然正确、互不影响。
|
||||
即使不播动画,静态界面也始终正确(据组件 `this.data` 调 `refresh` 即可还原,见 02 组件范式 / 04 重连)。**动画的开始/结束/出错等生命周期回调里只刷新界面、绝不设置核心数据**;动画期间**绝不修改游戏数据**。
|
||||
|
||||
### AnimationManager API(框架,通用)
|
||||
|
||||
@@ -117,12 +112,7 @@ AnimationManager.frame(spriteId, opt.startFrame, opt.endFrame, opt.duration, opt
|
||||
|
||||
- **`AudioManager`(框架)**:`playSound(file)`、`playVoice(baseId, sex)`、`playVoiceBySeat(seat, baseId)`、`playMusic/stopMusic`。约定女音 ID = 男音 ID + 偏移;**不含任何牌值/动作映射**。
|
||||
- **音效资源常量(子游戏)**:集中定义音效/语音文件 ID(音效、男/女语音等分类)。新增音效**只改这里**。
|
||||
- **游戏音频管理(子游戏)**:把“出牌/碰/某张牌”等概念映射到音效资源键,再调 `AudioManager`。**不写文件名/数字 ID**。
|
||||
|
||||
```js
|
||||
// 子游戏音频管理:按概念播放,内部映射到资源键并按座位性别选男/女语音
|
||||
// 通用音效直接走框架:AudioManager.playSound(音效资源常量.某音效)
|
||||
```
|
||||
- **游戏音频管理(子游戏)**:把“出牌/碰/某张牌”等概念映射到音效资源键,再调 `AudioManager`。**不写文件名/数字 ID**。通用音效直接走框架 `AudioManager.playSound(音效资源常量.某音效)`。
|
||||
|
||||
**声音文件手动准备**:音频文件由开发者**手动**按平台命名规范放入资源目录(文件名/存放目录/ID→文件名转换规则以平台的资源管理接口规范为准),代码只按 ID 引用、不生成文件。因此**音效资源常量每个 ID 必须注明【类型(背景音乐 / 音效 / 语音)+ 用途 + 时长 + 播放方式(循环 / 单次)】**,让人据注释就能准确准备对应音频。
|
||||
|
||||
@@ -181,4 +171,3 @@ Spine 骨骼资源(`json` / `atlas` / 贴图)由开发者**手动**制作并
|
||||
| Spine | 概念走 Spine 动作配置,回调走 Spine 回调分发器,改资源跑脚本 | 硬编码 spineId/animName;散接回调 |
|
||||
|
||||
下一篇 [04-网络对接与启动编排](./04-网络对接与启动编排.md) 讲:怎么收发包、新旧架构如何对接、一局怎么启动。
|
||||
</content>
|
||||
|
||||
@@ -19,10 +19,9 @@
|
||||
|
||||
### RpcHelper(自动注入平台字段)
|
||||
|
||||
`RpcHelper` 把每个请求自动补齐平台必需字段,业务只传业务数据:
|
||||
`RpcHelper` 把每个请求自动补齐平台必需字段(`agentid / gameid / playerid / roomcode / seat ...`),业务只传业务数据:
|
||||
|
||||
```js
|
||||
// 自动注入:agentid / gameid / playerid / roomcode / seat ...
|
||||
RpcHelper.sendGameRpc(rpc, gameData, options); // route = "room"/子游戏路由
|
||||
```
|
||||
|
||||
@@ -38,13 +37,11 @@ RpcHelper.sendGameRpc(rpc, gameData, options); // route = "room"/子游戏路
|
||||
|
||||
## 2. 收包:统一分发
|
||||
|
||||
服务端的主动推送统一格式 `{ rpc, data }`,前端有**唯一收包入口**,再按 `rpc` 分发到对应处理器。收包分发器是**纯路由表 + 分发器**:
|
||||
服务端的主动推送统一格式 `{ rpc, data }`,前端有**唯一收包入口**,再按 `rpc` 分发到对应处理器。收包分发器是**纯路由表 + 分发器**(一 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 一条
|
||||
};
|
||||
@@ -103,7 +100,6 @@ Game_Modify.StartWar = function (_msg) {
|
||||
前端 RPC 没有“同步返回”,操作结果由服务端**后续主动推送**告知。判成败的唯一权威是推送 `data` 里的 **`success`**:
|
||||
|
||||
```js
|
||||
// 处理器统一写法
|
||||
function handleXxx(data) {
|
||||
if (!data || !data.success) { /* 失败处理 */ return; }
|
||||
// 成功逻辑
|
||||
@@ -168,4 +164,3 @@ function handleXxx(data) {
|
||||
| 业务在 controllers/managers,各司其职 | 处理器交叉重造别人的能力 |
|
||||
|
||||
下一篇 [05-开发规范与红线](./05-开发规范与红线.md) 汇总前端所有工程纪律。
|
||||
</content>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
- 资源/布局 → 子游戏精灵结构/图片资源/布局常量。
|
||||
- **违例信号**:框架文件里出现 `mahjong:`、牌型、吃碰杠胡等具体玩法字样(注释举例除外,且举例应尽量中立)。
|
||||
|
||||
> 范例:本仓库把框架 `EventBus.js` 内**所有**预定义事件常量清空(只留空容器 `EventBus.Events={}`),全部事件(通用语义 + 玩法专属)改由子游戏的事件常量文件定义;并把通用的精灵事件控制器从子游戏抽到框架 `system/SpriteEventController.js`、剥离其中的玩法标记耦合为「全局 draw 钩子」;把 `UIManager` 的全局 UI(Loading/Message/Confirm)从「主动读子游戏精灵常量」改为「由子游戏 `init(config)` 注入」。新玩法照此扩展,框架零改动。
|
||||
> 范例:框架 `EventBus.js` 预定义事件常量全部清空(只留 `EventBus.Events={}`),事件改由子游戏事件常量文件定义;通用精灵事件控制器抽到框架 `system/SpriteEventController.js` 并把玩法标记耦合改为「全局 draw 钩子」;`UIManager` 的全局 UI(Loading/Message/Confirm)由子游戏 `init(config)` 注入而非主动读子游戏常量。新玩法照此扩展,框架零改动。
|
||||
|
||||
---
|
||||
|
||||
@@ -129,5 +129,4 @@
|
||||
|
||||
---
|
||||
|
||||
至此,从架构与环境(01)、渲染与组件(02)、表现系统(03)、网络与启动(04)到工程红线(05),构成一套完整的前端子游戏开发指导。回到 [README](./README.md) 查看导航与分层模型。
|
||||
</content>
|
||||
至此 01–05 构成一套完整的前端子游戏开发指导。回到 [README](./README.md) 查看导航与分层模型。
|
||||
|
||||
@@ -44,21 +44,8 @@
|
||||
|
||||
`00` 是平台配置项的**结构定义**文件,配置项的**集合与结构由平台/框架固定**。子游戏接入只做一件事:**把已有配置项的值改成本游戏需要的**。
|
||||
|
||||
✅ **允许(改值)**:
|
||||
```js
|
||||
Game_Config.Max.PlayerCnt = 4; // 改人数
|
||||
Game_Config.Share.title = "进贤麻将"; // 改分享标题
|
||||
Game_Config.Info.TextContent = ["你好","谢谢","快点","不要","加油","收到","稍等"]; // 改常用语的【值】
|
||||
Game_Config.Chat.ChatLoc = [[35,517],[1100,315]/* ... */]; // 改聊天气泡坐标【值】
|
||||
```
|
||||
|
||||
❌ **禁止(改定义 / 结构)**:
|
||||
```js
|
||||
Game_Config.Info.MyNewField = 1; // ✗ 新增配置项(增定义)
|
||||
delete Game_Config.Voice; // ✗ 删除配置项(删定义)
|
||||
Game_Config.Max = [4]; // ✗ 改变项的类型/结构
|
||||
// ✗ 重命名平台已定义的字段(如把 PlayerCnt 改成 playerCount)
|
||||
```
|
||||
- ✅ **允许(改值)**:给已有配置项赋值,如 `Game_Config.Max.PlayerCnt = 4;`、`Game_Config.Share.title = "进贤麻将";`、改 `Game_Config.Info.TextContent`(常用语)/`Game_Config.Chat.ChatLoc`(气泡坐标)等数组元素的【值】。
|
||||
- ❌ **禁止(改定义 / 结构)**:新增配置项(`Game_Config.Info.MyNewField = 1;`);删除配置项(`delete Game_Config.Voice;`);改变项的类型/结构(`Game_Config.Max = [4];`);重命名平台已定义字段(如 `PlayerCnt` → `playerCount`)。
|
||||
|
||||
> **为什么**:平台代码(`00_Surface/*`)按**固定字段名**读取 `Game_Config.*`。增 / 删 / 改名定义会让平台读到 `undefined` 或破坏约定,引发线上故障。**配置项的集合与结构是平台契约,只有「值」属于子游戏**。
|
||||
>
|
||||
@@ -75,12 +62,10 @@ Hooks 外置模式下,职责分为三层,单向向下:
|
||||
00_SubGame_Config.js 平台配置项结构 + 子游戏填【配置值】
|
||||
01_SubGame_modify.js 平台接口骨架,每个接口是固定【转发壳】
|
||||
02_SubGame_Input.js 平台接口骨架,每个接口是固定【转发壳】
|
||||
|
|
||||
| 转发壳查 SubGameHooks.X,存在则委托,不存在则平台默认(B类)/空操作(A类)
|
||||
v
|
||||
子游戏入口层(codes/,Hooks 外置新方案核心)
|
||||
SubGameHooks{} 全局对象,接口名与平台接口一一对应;子游戏只填需要的
|
||||
|
|
||||
| SubGameHooks.StartWar = function(_msg){ /* 委托子游戏的开局处理 */ };
|
||||
v
|
||||
子游戏实现层(codes/,已有)
|
||||
@@ -136,12 +121,6 @@ Game_Modify.StartWar = function (_msg) {
|
||||
return SubGameHooks.StartWar(_msg);
|
||||
}
|
||||
};
|
||||
|
||||
Game_Modify.Reconnect = function (_msg) {
|
||||
if (window.SubGameHooks && SubGameHooks.Reconnect) {
|
||||
return SubGameHooks.Reconnect(_msg);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### B 类 — 有平台默认返回值(无 hook 即返回默认)
|
||||
@@ -156,37 +135,15 @@ Game_Modify.getMaxPlayerCount = function (roomtype) {
|
||||
}
|
||||
return (Game_Config.Max && Game_Config.Max.PlayerCnt) || 4;
|
||||
};
|
||||
|
||||
Game_Modify.getLeaveLimit = function (roomtype) {
|
||||
if (window.SubGameHooks && SubGameHooks.getLeaveLimit) {
|
||||
return SubGameHooks.getLeaveLimit(roomtype);
|
||||
}
|
||||
return 10;
|
||||
};
|
||||
|
||||
gameHallImport.isInstalled = function () {
|
||||
if (window.SubGameHooks && SubGameHooks.isInstalled) {
|
||||
return SubGameHooks.isInstalled();
|
||||
}
|
||||
return 1;
|
||||
};
|
||||
```
|
||||
|
||||
`gameHallImport.*` 侧同理(如 `isInstalled` 无 hook 返回 `1`、`getLeaveLimit` 返回 `10`)。
|
||||
|
||||
### C 类 — 配置数据(不是 hook,留配置文件)
|
||||
|
||||
`Game_Config.*`(00 全部)与房型数据(`Game_Modify.Type_1/Type_2/CreateRoomData/game_config`、`Game_Modify.combat/roomDes`)属于**配置数据**而非行为接口,不进 `SubGameHooks`。
|
||||
|
||||
模板在 01 文件顶部设置「配置区」,由子游戏直接填值:
|
||||
|
||||
```js
|
||||
// 01_SubGame_modify.js 顶部:配置区(子游戏填值,与转发壳接口段物理分开)
|
||||
Game_Modify.combat = null; // 战绩配置,子游戏按需赋值
|
||||
Game_Modify.roomDes = ''; // 房间描述
|
||||
Game_Modify.Type_1 = []; // 房型一级选项
|
||||
Game_Modify.Type_2 = []; // 房型二级选项
|
||||
Game_Modify.CreateRoomData = []; // 创建房间数据
|
||||
Game_Modify.game_config = {}; // 游戏配置
|
||||
```
|
||||
模板在 01 文件顶部设置「配置区」,由子游戏直接填值(与转发壳接口段物理分开):`Game_Modify.combat/roomDes/Type_1/Type_2/CreateRoomData/game_config`。
|
||||
|
||||
### D 类 — 模板默认 UI 渲染(可变人数)
|
||||
|
||||
@@ -211,17 +168,6 @@ Game_Modify.game_config = {}; // 游戏配置
|
||||
|
||||
目前仅 `appStart` 存在此冲突。`gameHallImport` 侧所有同名接口均加 `hall` 前缀以区分。
|
||||
|
||||
```js
|
||||
// SubGameHooks.js 中的写法
|
||||
SubGameHooks.appStart = function () {
|
||||
// 对应 Game_Modify.appStart:委托子游戏的启动编排
|
||||
};
|
||||
|
||||
SubGameHooks.hallAppStart = function () {
|
||||
// 对应 gameHallImport.appStart(大厅启动):委托子游戏的大厅初始化
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 接口覆盖要求
|
||||
|
||||
Reference in New Issue
Block a user