二七王:文档同步图集叠绘数字(清单 §1.3/§1.5/§3.6/§5.4/§6.8 + 前端 02 新增一节)

- docs_dev 清单:§3.6 数字资源改为「等宽连续排列的一张图」并给出字符顺序/单字宽高/整图尺寸,
  §1.3 说明叫分档位分数为何必须改叠绘(14 档里 13 档两位数、每档一个精灵),
  §1.5/§5.4 选主张数由 8 个精灵改回 4 个(1678–1681 记跳号不复用),
  §6.8 布局节点合并 + 新增 NUM_STYLE 数字样式表;各处均带 2026-08-26 修订说明与缘由。
- 前端 02:新增「三种显示机制:文字精灵 / 多帧图片精灵 / 图集叠绘」一节,
  给出三问选用判据、多位数误用 setFrame 的坑与真实事故、叠绘的三条前提与出图规格;
  §5 补一条「转发必须接上且参数对齐,否则事件静默失效」的警示;DO/DON'T 各补一行。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-26 23:56:32 +08:00
co-authored by Claude Opus 5
parent 2fe0f661b4
commit dbdd6607d7
2 changed files with 95 additions and 21 deletions
@@ -27,6 +27,8 @@ UI 组件 → SpriteManager(业务级 API:ID 范围校验 + 单位换算)
| `setScale(id, scale)` | 缩放 | 业务用倍数(`1.2`),框架自动转引擎百分比 |
| `setOpacity(id, o)` | 透明度 | 业务用 `0.0–1.0`,框架自动转 `0–255` |
| `setText(id, text)` / `setTextWithWidth(...)` | 文字 | 文字精灵 |
| `drawImage(id, imgId, dx,dy,dw,dh)` | 在精灵上叠绘整张图 | **只能在绘制回调里调**(见 §5) |
| `drawImageRegion(id, imgId, dx,dy,dw,dh, sx,sy,sw,sh)` | 在精灵上叠绘图片的**指定源矩形** | 从图集里裁一格再画,同上只在绘制回调里调 |
| `showGroup/hideGroup(gid)` | 群组批量 | 整块 UI 显隐 |
| `showLayer/hideLayer(lid)` | 图层批量 | 整个界面显隐 |
| `exists(id)` | 存在性检查 | |
@@ -55,6 +57,49 @@ SpriteManager.show(sid); SpriteManager.setFrame(sid, card.code - 1);
ID 必须与编辑器中实际存在的精灵**完全一致**。超范围 → 校验失败返回 `false`;编辑器里不存在的 ID → 操作静默无效。**绝不随意编造 ID。**
### 三种显示机制:文字精灵 / 多帧图片精灵 / 图集叠绘
同样是"在精灵上显示内容",引擎给了三条路,**能力边界完全不同,选错了会在后期变成返工**:
| 机制 | API | 一次能显示 | 字形 | 适用 |
|------|-----|-----------|------|------|
| **文字精灵** | `setText(id, text)` | **任意长度**的字符串 | **系统字体**(只能调字号/颜色,做不出描边、渐变、投影) | 纯数据型文本:昵称、局数、`N对`/`主N` 角标、`2子`、提示文案 |
| **多帧图片精灵** | `setFrame(id, frame)` | **一帧**(整张精灵就是那一帧) | 美术出图 | **状态切换**:按钮可用/置灰、单选框选中/未选、花色图标、牌面(一张牌 = 一帧) |
| **图集叠绘** | `NumberRenderer`(底层 `drawImageRegion`) | **一行任意个字符**,都画在**同一个**精灵内 | 美术出图 | **多位美术字**:倒计时、结算得分、叫分档位分数、张数、比分 |
**选用判据(按顺序问自己三个问题)**:
1. **要不要美术字形**(描边/渐变/投影)?不要 → **文字精灵**,到此为止,最省。
2. 要美术字形,那**一次只显示一个符号**吗(一张牌、一个图标、一个按钮态)?是 → **多帧图片精灵** + `setFrame`。
3. 要美术字形、且**可能不止一个字符**(两位数、带正负号、可变长度)?→ **图集叠绘**。
> ⚠️ **最容易踩的坑:用多帧图片精灵去显示多位数。**
> `setFrame` 是"整个精灵显示图集的第 N 帧",**一个精灵一次只能显示一位数字**。于是两位数被迫拆成"十位精灵 + 个位精灵",三位数拆三个……精灵数量随位数线性膨胀,布局、显隐、清理全都要按位处理(十位为 0 还要单独隐藏),而**位数一变就得重排**。
> 真实事故:某处 14 个档位按钮各配了**一个**分数精灵,而 14 档里 13 档是两位数——按 `setFrame` 方案**根本渲染不出来**,直到改成叠绘才修好。
> **判据一句话:值可能超过一个字符,就不要用 `setFrame`。**
**图集叠绘怎么工作**:`GameABCUtils.Draw.drawImage` 支持**源矩形**,`SpriteManager.drawImageRegion` 把这能力暴露到业务层——可以从一张图里裁出任意区域,画到精灵内的任意位置。数字图出成**等宽连续排列的一行**,第 `idx` 格的源矩形就是 `(idx × 单字宽, 0, 单字宽, 单字高)`;把每一位分别裁出来、画到精灵内递增的 x 上,一个精灵就显示了一个多位数。
```js
// 框架的 NumberRenderer 封装了这套逻辑,业务侧只有三步(样式全部来自常量,禁裸值)
NumberRenderer.bind(SPRITE_ID, {
imageId: ImageResources.NUM_SCORE, // 等宽连续排列的数字图
charWidth: 30, charHeight: 40, // 图集单字宽高
spacing: 0, // 字间距(可为负 = 重叠)
align: 'center', width: 74 // 在绘制区内怎么对齐、绘制区多宽
});
NumberRenderer.setValue(SPRITE_ID, 70); // 只写值;引擎每帧回调时才真正画
NumberRenderer.unbind(SPRITE_ID); // 组件 onDestroy 里注销回调,防残留
```
**三条前提,缺一不可**:
- **叠绘只能在精灵的绘制回调里进行**(`SpriteEventController.registerDraw` 注册的 handler 内),在回调之外调 `drawImageRegion` 不会留下任何画面——因为引擎只在绘制那一帧接受叠绘。
- **绘制回调依赖平台事件链路接通**:`Game_Modify.gamemydraw` →(转发壳)→ 子游戏 hook → `SpriteEventController.handleDraw`。这条链断了,叠绘、按钮点击一起静默失效(见 §5)。
- **只对编辑器预置的数字 ID 精灵有效**:`SpriteCopyUtils` 复制出的字符串 ID 精灵不接收独立绘制事件,不能用 `NumberRenderer`;那种场合改用文字精灵,或由父容器统一叠绘。
**图集出图规格**(写进图片资源常量的注释里,供美术照做):字符**排列顺序**(如 `0…9` 后接 `+`、`-`)、**单字符宽高**、**整图尺寸**;字符必须严格等宽对齐格子、格间不留缝,否则裁出来的字会串位。
---
## 2. 资源与布局常量三件套
@@ -173,6 +218,8 @@ SpriteEventController.registerMouseMove(sprites.HAND_AREA, function (event) { se
支持的事件类型:`mouseDown` / `mouseDownNoMove`(长按) / `mouseUp` / `mouseMove`(拖拽) / `drawBegin` / `draw`。
> ⚠️ **转发这一段必须真的接上,且参数顺序要逐个对齐 `handleXxx` 的签名**。转发缺失或参数错位不会报错,只会让**所有**精灵交互与叠绘**静默失效**——按钮点了没反应、叠绘一片空白,却查不到任何异常日志。接线时对着 `SpriteEventController` 里各 `handleXxx` 的形参表逐个核对,转发层**只转发、不写业务**。
对**每个**绘制精灵统一处理(不针对某个固定 ID)用 `registerGlobalDraw(fn)`——框架对每个 draw 事件都回调它、但**不认识**其业务含义,保持中立(“精牌标记”这类玩法叠绘即以此挂载,框架零感知,见 05「框架中立」)。
### 更高层:手势识别 `SpriteGestureRecognizer`
@@ -217,7 +264,8 @@ list.onClick = function (type, rowIndex, rowData) { /* ... */ };
| DO ✅ | DON'T ❌ |
|------|---------|
| UI 只调 `SpriteManager` | 直接调 `GameABCUtils`/引擎原生 API |
| 一精灵多帧、`setFrame` 切换 | 为每种牌面建一个精灵 |
| 一精灵多帧、`setFrame` 切换**状态** | 为每种牌面建一个精灵 |
| 多位美术字用 `NumberRenderer` 叠绘,**一个精灵画完整个数** | 用 `setFrame` 显示多位数(一帧只有一位)/ 拆十位个位各建一个精灵 |
| 精灵 ID 1001–3000、群组 ≥201、从编辑器查证 | 随意编造 ID / 超范围 ID |
| 检查 `SpriteManager` 返回值 | 忽略 `false` 返回 |
| 精灵/资源/坐标全进常量文件 | 在业务代码内联裸数字 |