140 lines
12 KiB
Markdown
140 lines
12 KiB
Markdown
# 05 · 开发规范与红线
|
||
|
||
本篇汇总前端**必须遵守**的工程纪律,是代码审查清单的来源。改任何代码前按相关条目自检。前面 01–04 是“怎么做”,本篇是“不许怎么做 / 必须怎么做”。
|
||
|
||
---
|
||
|
||
## 1. 可编辑范围
|
||
|
||
| 区域 | 可改性 |
|
||
|------|--------|
|
||
| `js/vendor/`、`js/00_Surface/` | **禁改**(第三方/平台代码) |
|
||
| `01_SubGame/00_SubGame_Config.js` | **仅可改值**:只给**已有** `Game_Config.*` 配置项赋值;**禁止**新增/删除/改名/改结构定义(三文件中唯一子游戏可碰者,且仅限改值)。详见 [06](./06-子游戏接入模式与Hooks外置.md) |
|
||
| `01_SubGame/01_SubGame_modify.js` | **受限**:仅顶部「配置区」填值(`Type_*/CreateRoomData/combat/game_config/roomDes`);转发壳/平台接口**不碰、不新增**,业务走子游戏实现层。详见 06 |
|
||
| `01_SubGame/02_SubGame_Input.js` | **不碰**:框架维护的平台接口骨架/转发壳,不改、不新增接口、不写业务。详见 06 |
|
||
| `js/gameabc-framework/` | 可改,但须保持**游戏中立**(见 §3) |
|
||
| `01_SubGame/codes/`(除 `shared/`) | 子游戏自由开发区 |
|
||
| `01_SubGame/codes/shared/` | **只读**:服务端 `shared/` 的同步副本,改服务端权威源再同步(见 §7) |
|
||
|
||
新增前后端交互一律走服务端 `mod.js` 的 `rpc` 机制 + 前端统一发包/收包封装,**不在受限文件新增接口**。
|
||
|
||
---
|
||
|
||
## 2. 语言标准:严格 ES5
|
||
|
||
- 用 `var`/`function`;对象继承用 `Object.create(Base)` + 在 `init` 内重声明实例属性。
|
||
- **禁** `let`/`const`、箭头函数、模板字符串、解构、默认参数、展开、`class`、`for...of`。
|
||
- 跨文件按**全局名**引用(各文件 `var X = ...` 暴露全局,`index.html` 顺序加载);新增文件必须在 `index.html` 插到依赖之后、使用者之前。
|
||
|
||
---
|
||
|
||
## 3. 框架游戏中立
|
||
|
||
`gameabc-framework/` 是给**任意子游戏**复用的通用框架,**不得**渗入任何具体玩法:
|
||
|
||
- **禁止**在框架内出现玩法逻辑、玩法专属常量(牌型、动作、玩法事件名等)。
|
||
- 玩法专属内容一律定义在**子游戏侧**:
|
||
- 玩法事件 → 子游戏事件常量文件,追加到 `EventBus.Events`;
|
||
- 音效映射 → 子游戏音效资源常量 + 游戏音频管理;
|
||
- Spine 动作 → 子游戏 Spine 动作配置;
|
||
- 资源/布局 → 子游戏精灵结构/图片资源/布局常量。
|
||
- **违例信号**:框架文件里出现 `mahjong:`、牌型、吃碰杠胡等具体玩法字样(注释举例除外,且举例应尽量中立)。
|
||
|
||
> 范例:框架 `EventBus.js` 预定义事件常量全部清空(只留 `EventBus.Events={}`),事件改由子游戏事件常量文件定义;通用精灵事件控制器抽到框架 `system/SpriteEventController.js` 并把玩法标记耦合改为「全局 draw 钩子」;`UIManager` 的全局 UI(Loading/Message/Confirm)由子游戏 `init(config)` 注入而非主动读子游戏常量。新玩法照此扩展,框架零改动。
|
||
|
||
---
|
||
|
||
## 4. 渲染与常量
|
||
|
||
- **精灵只走 `SpriteManager`**:UI 代码禁止直接调 `GameABCUtils`/引擎原生 API。
|
||
- **ID 守范围**:精灵 1001–3000、群组 ≥201、普通图层 101–200、弹窗图层 301–400(另有 501–600、701+ 备用段);框架保留 1–1000 及 3001+(精灵)等交替段,不可占用;ID 必须与编辑器一致,不编造。
|
||
- **检查返回值**:`SpriteManager.*` 返回 `false` 即 ID/范围有误,及时暴露。
|
||
- **常量集中、禁硬编码**:UI 代码**禁止出现任何硬编码的 id 或裸值**——精灵 ID / 群组 ID / 图层 ID / 图片资源 ID / 声音 ID / Spine 资源 / 坐标尺寸 / 动画时长帧数 / 事件名,一律定义在对应常量文件,业务代码**只引用常量**:
|
||
- 精灵结构(含群组 / 图层 ID)→ 精灵结构常量(主界面/弹窗分文件)
|
||
- 图片资源 → 图片资源常量
|
||
- 布局坐标 → 布局常量
|
||
- 动画参数 → 动画配置
|
||
- 音效 / 语音 ID → 音效资源常量
|
||
- Spine 资源 → Spine 动作配置
|
||
- 事件名 → `EventBus.Events`(专属在子游戏事件常量文件)
|
||
- 整合入口 → 精灵常量整合入口(**最后加载**)
|
||
|
||
---
|
||
|
||
## 5. UI 组件生命周期与内存安全
|
||
|
||
- **组件继承 `BaseComponent`**,在 `init` 内重声明实例属性、建精灵、注册事件。
|
||
- **事件用 `this.addEventListener`** 订阅(`destroy()` 自动 `off`),**禁**裸 `EventBus.on`(会泄漏)。
|
||
- **销毁用 `destroy()`** 而非只 `hide()`;在 `onDestroy` 清理**定时器、动态复制精灵、Spine 回调登记**。
|
||
- 动态列表用 `DynamicSpriteList` 并在销毁时 `list.destroy()`,避免复制精灵残留。
|
||
|
||
---
|
||
|
||
## 6. 数据权威、组件数据与表现延后
|
||
|
||
- **服务端权威**:所有核心运算与胜负裁定以服务端为准、数据以服务端为权威;前端只做界面展示与玩家交互,本地数据仅为「渲染副本」,不做权威计算。
|
||
- **组件数据自持 + set-refresh**:组件(及其下每个「零件 UI」)的自有数据集中在 `this.data`,**不散落**各处;每个 UI 都有 `setXxx`(只写数据)/`refreshXxx`(只据数据画界面)成对方法,组件另有总 `refresh()` 据 `this.data` 重建整块界面(见 02)。
|
||
- **更新时机——发包只请求、收包才表现(悲观 UI / 输入-渲染解耦)**:用户点击**只发请求包**,**绝不**在点击时改动任何对局状态界面(提示显隐、按钮增删、当前控制权、倒计时启停、落牌、阶段/托管图标);这些表现一律在**收到服务端结果/推送包后**更新。点击回调只做「发包 +(可选)纯本地物理反馈(按下高亮/音效、防连点 `disable`)」。收包处理器**触发源无关**——数据从包字段读、不依赖「点击时暂存的本地变量」,故真人操作与 **AI 托管/他人广播共用同一更新路径、对前端透明**(无点击时也正确更新);把更新挂在「点击」而非「收包」会导致 AI 托管时界面卡死。详见 [04 §5](./04-网络对接与启动编排.md#5-输入渲染解耦发包只请求收包才表现)。**受控例外**:本玩家点击的**响应/掷骰交互按钮**(碰/杠/过/胡按钮、手动掷骰按钮)的**隐藏**允许乐观清除(兼作防连点+即时反馈),前提是**服务端合法性验证 + 收包侧兜底(AI 托管一致)**,见 [04 §5.6](./04-网络对接与启动编排.md#56-受控例外响应交互按钮乐观清除--服务端合法性验证) 与服务端 [04 §8](../../server/development-guide/04-开发规范与红线.md#8-操作请求合法性验证不可信客户端);其余提示/控制权/倒计时/落牌仍严格收包驱动。
|
||
- **收包节奏**:**先 `setXxx` 写数据 →(必要时 `refresh` 刷静态界面)→ 再播动画 → 动画回调里只刷新界面**;动画的开始/结束/出错等生命周期回调里**绝不设置核心数据**,动画期间**不改数据**。
|
||
- **重画随时可用(断线重连 + 硬刷新都要处理)**:重连/页面重载的本质是**恢复数据 → 调各 UI 组件 `setXxx`/`refreshXxx` 恢复数据与界面状态**;任何时候调 `refresh` 都能据 `this.data` 重建正确界面(断线重连、硬刷新/网页重载、切 app 复用**同一条**重画路径,不为重连单写一套渲染,详见 04 §3.3)。
|
||
- **动画是体验层**:开发阶段可**先不做动画**只保证静态界面正确;即使动画缺失/卡住/播错,数据、逻辑与界面仍正确、互不影响。
|
||
|
||
---
|
||
|
||
## 7. 成败标志与收发包
|
||
|
||
- **成败只认 `data.success`**:`if (!data.success)` 判失败;**禁**用 `status`/`code` 判成败、**禁** `status` 兼容兜底。个别嵌套场景按该推送契约取 `success` 所在字段,语义不变。
|
||
- **发包走语义化发包封装/`RpcHelper`**(自动注入平台字段),**收包统一分发**(一 rpc 一处理器)。
|
||
- 业务逻辑放 controllers/managers,**不**写进受限 `Game_Modify.*`。
|
||
|
||
---
|
||
|
||
## 8. shared 同步
|
||
|
||
- **`shared/` 是子游戏自己的游戏逻辑,与平台无关**:存放本玩法**前后端必须算出完全一致**的纯逻辑(胡牌/听牌/比精/牌型/计分/规则常量等),前端用于即时表现与预校验,服务端做权威裁定。它不是平台代码,平台既不提供也不感知。
|
||
- `01_SubGame/codes/shared/` 是服务端 `server/<游戏容器目录>/<游戏>/shared/` 的**同步副本**,前端**只读**(脚本生成)。
|
||
- 改共享算法只改服务端权威源,再运行同步脚本覆盖前端副本;**禁止直接编辑前端 `codes/shared/`**。
|
||
- 逻辑同源不改变数据权威:前端 `shared/` 算的是表现/预判,最终以服务端为准(数据优先、表现延后,见 §6)。详见服务端 [04 §9 shared 文件同步流程](../../server/development-guide/04-开发规范与红线.md#9-shared-文件同步流程)。
|
||
|
||
---
|
||
|
||
## 9. 模块职责边界
|
||
|
||
- 一个职能只在一个模块实现,其他模块**调用而非重造**:渲染找 `SpriteManager`、动画找 `AnimationManager`(及游戏动画封装)、音频找游戏音频管理、Spine 找 `SpineMgr`(及 Spine 回调分发)、发包找语义化发包封装、收包分发找收包分发器。
|
||
- **UI 组件专职自己的界面**:每个界面的数据与渲染**只由其对应 UI 组件实现**,组件对外提供 `setXxx`/`refreshXxx` 与语义化公开方法。别的模块(controllers/managers/其他组件)要改动或刷新某界面,一律**调用该组件的公开接口**,**禁止**在别处重复实现重叠或类似的界面数据/渲染逻辑(否则同一界面出现多份状态源,重连/刷新时必然不一致)。
|
||
- **显隐走 `showXxx`/`hideXxx` 接口**:UI 组件及其「零件 UI」的精灵/群组显隐,必须由组件自身暴露的 `showXxx()`/`hideXxx()` 语义接口控制;**禁止**在别处直接用该组件/零件的精灵 ID、群组 ID 去 `SpriteManager.show/hide`(或 `showGroup/hideGroup`)控制其显隐——绕过组件即状态分散,重连/刷新时必然不一致。
|
||
- 写代码前先问“这段属于谁的职责”,属于别人就调用它,不在本模块复制一份近似实现。
|
||
|
||
---
|
||
|
||
## 10. 测试纪律
|
||
|
||
- 测试用于验证业务正确性;失败先**裁定根因归属**(业务缺陷 vs 测试脚本缺陷),禁止 skip/软化断言/吞异常掩盖。
|
||
- 业务缺陷修业务并单独提交;脚本缺陷修置场并保持硬断言。
|
||
- `shared/` 算法变更应在 Node 跑相应单测验证(前后端同源)。
|
||
|
||
---
|
||
|
||
## 11. 审查速查表
|
||
|
||
| 维度 | 红线 |
|
||
|------|------|
|
||
| 范围 | `vendor`/`00_Surface` 禁改;受限文件不新增接口;`shared` 只读 |
|
||
| 语言 | 严格 ES5;新文件插对加载顺序 |
|
||
| 框架中立 | 框架无玩法逻辑/专属常量;专属内容归子游戏 |
|
||
| 渲染 | 只走 `SpriteManager`;ID 守范围、不编造;查返回值 |
|
||
| 常量 | 精灵/群组/图层/图片/声音/Spine/坐标/动画/事件全进常量;UI 代码禁硬编码 id 与裸值 |
|
||
| 组件 | 继承 `BaseComponent`;事件 `addEventListener`;`destroy` 清动态精灵/定时器 |
|
||
| 数据 | 服务端权威(核心运算/裁定在服务端);组件自有数据集中 `this.data`;每个 UI/零件有 set/refresh 对、组件有总 `refresh` |
|
||
| 更新时机 | 点击只发请求包,对局状态表现等**收包后**更新(悲观 UI);收包处理器触发源无关、数据从包字段读;AI 托管/他人广播共用同一更新路径。**受控例外**:响应/掷骰交互按钮隐藏可乐观清除(防连点+即时反馈),前提是服务端合法性验证+收包兜底(04 §5.6、服务端 04 §8) |
|
||
| 节奏 | 先写数据后表现;动画回调只刷界面、**不写核心数据**;重画可随时据数据还原界面 |
|
||
| 成败 | 只认 `data.success`,禁 `status` 兜底 |
|
||
| 收发包 | 发走语义化发包封装、收走收包分发器;业务不进 `Game_Modify` |
|
||
| 职责 | 一职能一模块,调用不重造 |
|
||
| UI 职责 | 每个界面的数据/渲染/显隐只在其 UI 组件实现;别处调组件接口(含 `showXxx`/`hideXxx`),不重叠重造、不直接用其精灵/群组 id 控显隐 |
|
||
| 重连 | 断线重连 + 硬刷新都要处理;本质=恢复数据→各组件 set-refresh;复用同一重画路径 |
|
||
|
||
---
|
||
|
||
至此 01–05 构成一套完整的前端子游戏开发指导。回到 [README](./README.md) 查看导航与分层模型。
|