Files
youle_framework/docs/client/development-guide/05-开发规范与红线.md
T
joywayerandClaude Opus 4.8 96971f3072 文档红线细化:新增服务端请求合法性验证(§8)+悲观UI/输入-渲染解耦,收紧 shared 与常量准入门槛
- 服务端 04:新增「操作请求合法性验证(不可信客户端)」章节——五层验证栈
  (座位鉴权/阶段门/操作可用性/幂等去重/参数校验)、统一裁决网关默认拒绝、
  availableActions 兼任决策依据与准入白名单、出牌回合门防死代码;后续章节顺延重编号
- 前端 04/05:悲观 UI——点击只发请求包、对局状态表现收包后更新,收包处理器触发源无关,
  AI 托管/他人广播共用同一更新路径;响应/掷骰交互按钮隐藏为受控乐观清除例外
- shared 准入门槛(前后端 04/05 §9/§8):进 shared 的门槛是「前后端都真正用到且必须
  逐字一致」而非「它是玩法逻辑」;前端只展示+悲观UI ⇒ 计算类逻辑不进 shared,默认留服务端
- 硬编码常量准则(服务端 04 §10):魔法字符串默认常量化(单一来源);常量放 shared 亦按
  同一准入门槛判定,仅前后端都用到才进 shared/constants

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 04:12:27 +08:00

13 KiB
Raw 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. 数据权威、组件数据与表现延后

  • 服务端权威:所有核心运算与胜负裁定以服务端为准、数据以服务端为权威;前端只做界面展示与玩家交互,本地数据仅为「渲染副本」,不做权威计算。
  • 组件数据自持 + 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/ 只放"前后端都真正用到"的那部分逻辑,务必慎重:进 shared/ 的门槛不是"它是玩法逻辑",而是"前端确实会用到、且两端必须算出完全一致的结果"。因为前端只做展示、不做核心运算(服务端权威,见 §5)叠加悲观 UI(可操作项/提示/结果由服务端推送、前端不预测,见 §5),前端实际会重算的逻辑很少——发牌/胡牌裁定/计分/AI 等计算类逻辑前端根本不算(服务端算好推来展示即可),它们不进 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;每个 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 查看导航与分层模型。