Files
2026-08-19 08:14:48 +08:00

17 KiB
Raw Permalink Blame History

05 · 开发规范与红线

本篇汇总前端必须遵守的工程纪律,是代码审查清单的来源。改任何代码前按相关条目自检。前面 01–04 是“怎么做”,本篇是“不许怎么做 / 必须怎么做”。


1. 可编辑范围

区域 可改性
js/vendor/、js/00_Surface/ 禁改(第三方/平台代码)
01_SubGame/00_SubGame_Config.js 仅可改值:只给已有 Game_Config.* 配置项赋值;禁止新增/删除/改名/改结构定义(三文件中唯一子游戏可碰者,且仅限改值)。详见 06
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. 数据驱动架构:服务端状态的投影

前端是服务端对局状态的一个投影(view),不是状态的第二个来源。 阶段、轮次、控制权、可用操作、倒计时、分数、按钮可用性……一切对局态都由服务端唯一维护并随包下发,前端只做「读包字段 → 写 this.data → 据 this.data 画界面」这一条链路。这既是正确性要求(两端各推一套必然分叉),也是反作弊要求:前端能自己推出来的东西,就是玩家能改的东西。

  • 服务端权威:所有核心运算与胜负裁定以服务端为准、数据以服务端为权威;前端只做界面展示与玩家交互,本地数据仅为「渲染副本」,不做权威计算。

6.1 前端无对局状态机

  • 状态机唯一在服务端:阶段(phase)、当前控制权(轮到谁)、该座位可用操作(availableActions)、倒计时剩余、比分/结算——一律读服务端下发的权威字段渲染。
  • 禁止本地推导:不得由「上一个包 + 本地规则」推出「现在该轮到谁 / 现在进入哪个阶段 / 现在该显示哪几个按钮」。缺字段是服务端漏发,修在服务端发包处(见服务端 03 §1.2),前端不推、不补、不猜。
  • 禁止前端推进流程:不得用本地定时器自动推进阶段、自行结算、超时后自判胜负或自动补发包。超时/托管一律由服务端驱动,前端只显示服务端下发的结果。
  • 倒计时的正确形态:服务端下发锚点/剩余时长,前端可本地 tick 做插值显示;但归零不代表状态改变——超时的裁定与后续推进仍等服务端推送。
  • shared/ 预判不是状态:前端跑 shared/ 算出的胡牌/听牌/合法性只用于提示与预校验(如置灰不可点的牌),不得据其改写对局态、也不得用来替代服务端下发的 availableActions(见 §8)。

6.2 视图 = f(服务端快照)

  • this.data 是服务端状态的镜像,不是第二份真相;不得存在「只活在前端、服务端不知道」的对局态。
  • 判据(可直接用于自检与代码审查):任意时刻丢弃全部 this.data,仅用最近一次服务端快照(重连 get_deskinfo 或最近一次推送)重画,界面与交互状态必须完全一致。做不到 → 要么前端私存了对局态、要么服务端漏发了字段,二者必居其一,都要修。
  • 这与「重连即重画」是同一条路径(见 04 §3.3):重连之所以能只靠服务端快照还原,正因为前端从来没有过独占状态。

6.3 允许的本地 UI 态(白名单)

只有不影响对局裁定、服务端根本不关心的纯表现态,才可以只存在于前端:

允许只在前端 不允许(属对局态,必须来自服务端)
选中/待出牌的高亮、拖拽位置 这张牌能不能出、出了之后轮到谁
按钮按下高亮/缩放、点击音效 按钮该不该出现、能不能点
列表滚动位置、面板展开、设置开关 阶段、控制权、倒计时基准、分数、结算
动画进度、特效播放中标记 手牌/牌河/副露内容、亮牌信息、托管状态

判别:这个值若被玩家改成任意值,会不会影响对局结果、或让他看到/做到本不该的事? 会 → 它是对局态,必须服务端权威。

与 04 §5.6 受控例外的关系:本玩家点击响应/掷骰交互按钮后的「乐观隐藏」仍然允许——它是抢先一步做了服务端稍后会确认的事,不是前端私有状态:按钮该不该出现依旧由服务端下发的 availableActions 决定,收包侧必须能独立得出同一结果(AI 托管无点击时也正确)。判据仍成立:丢弃 this.data 后按最近快照重画,该按钮的显隐与服务端一致。

6.4 组件数据与表现延后

  • 组件数据自持 + set-refresh:组件(及其下每个「零件 UI」)的自有数据集中在 this.data,不散落各处;每个 UI 都有 setXxx(只写数据)/refreshXxx(只据数据画界面)成对方法,组件另有总 refresh() 据 this.data 重建整块界面(见 02)。
  • 更新时机——发包只请求、收包才表现(悲观 UI / 输入-渲染解耦):用户点击只发请求包,绝不在点击时改动任何对局状态界面(提示显隐、按钮增删、当前控制权、倒计时启停、落牌、阶段/托管图标);这些表现一律在收到服务端结果/推送包后更新。点击回调只做「发包 +(可选)纯本地物理反馈(按下高亮/音效、防连点 disable)」。收包处理器触发源无关——数据从包字段读、不依赖「点击时暂存的本地变量」,故真人操作与 AI 托管/他人广播共用同一更新路径、对前端透明(无点击时也正确更新);把更新挂在「点击」而非「收包」会导致 AI 托管时界面卡死。详见 04 §5。受控例外:本玩家点击的响应/掷骰交互按钮(碰/杠/过/胡按钮、手动掷骰按钮)的隐藏允许乐观清除(兼作防连点+即时反馈),前提是服务端合法性验证 + 收包侧兜底(AI 托管一致),见 04 §5.6 与服务端 04 §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 文件同步流程。

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 只是服务端快照的镜像,丢弃后仅凭最近快照重画须完全一致;只有白名单纯表现态可只存在于前端(§6.1–6.3)
发包内容 请求包只带「意图」(操作类型+目标标识),不带结论(分数/判定结果/阶段指令);服务端按自己的权威数据重算(04 §1「请求包只带意图」)
数据 服务端权威(核心运算/裁定在服务端);组件自有数据集中 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 查看导航与分层模型。