docs(spec): 旧 gameabc UI 数据迁移与分辨率适配设计(子系统 A)
把「旧平台 UI 搬到 Cocos」拆成三个子系统, 本 spec 只覆盖 A(UI 数据迁移); B(引擎机制映射)与 C(平台逻辑移植)另立。 关键决策: 设计分辨率保持 1280x720 + fitHeight(坐标 1:1 迁移)、多帧图切成散图 (1306 张)、同组挂同一父节点、锚点按中心点三区推断(仅水平)、全部 55 界面转进框架、 走「中间产物 + 编辑器批量」而非直接生成 prefab(红线)。 帧序经实证: 1 基 + 行优先(读 00014.png 网格内容与 11 个按钮的 FrameIndex 对照, 六项精确命中)。并推翻了早期「51.6% 帧未被引用」的错误统计。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,404 @@
|
||||
# 旧 gameabc UI 数据 → Cocos 平台界面:迁移与分辨率适配 设计
|
||||
|
||||
> 状态:已与用户确认(brainstorming 通过),待转 writing-plans。
|
||||
> 适用工程:`cocoscreator_projects/YouleNexus`(框架真源)。
|
||||
> 数据源:`projects/Game_Surface_3/output/gameabc_*.json`(旧 H5 工程的 UI 导出数据)。
|
||||
> 关联:`2026-08-27-ui-asset-and-skin-design.md`(资源归属 §2 / 图集划分 / override 白名单 / theme §4);
|
||||
> `2026-06-28-cocos-framework-design.md` §5(换肤)。
|
||||
> **本 spec 只覆盖子系统 A**,B(引擎机制映射)与 C(平台逻辑移植)另立,见 §0.1。
|
||||
|
||||
---
|
||||
|
||||
## 0. 目标与背景
|
||||
|
||||
### 0.1 范围:这是三个子系统中的第一个
|
||||
|
||||
把「旧平台 UI 搬到 Cocos」拆成三件互相独立的事,本 spec 只做 A:
|
||||
|
||||
| | 子系统 | 产出 | 依赖 |
|
||||
|---|---|---|---|
|
||||
| **A** | **UI 数据迁移**(本 spec) | 991 对象 / 55 界面 → 中间描述 → prefab + 1306 张散图 | 无 |
|
||||
| **B** | 引擎机制映射 | ~10 个 `ifast_*` API + `set_self`/`get_self` **属性号对照表** → Cocos 等价物 | 需逆向 `js/gameabc.min.js`(186KB) |
|
||||
| **C** | 平台逻辑移植 | `11_GameUI.js`(9862 行)等 → TypeScript | **A 与 B 都要先有** |
|
||||
|
||||
A 与 B 互不依赖、可并行;C 必须等两者。
|
||||
|
||||
> **B 的紧迫性**:平台层有 `set_self` **1773 处** + `get_self` **447 处**调用,全部使用数字属性号(37/7/18/20/43…)。若属性号解不出来,C 就只能**重写**而非**移植**,工作量差一个量级。建议 A 开工后尽快并行启动 B。
|
||||
|
||||
### 0.2 决策依据(实测数据)
|
||||
|
||||
以下全部实测自 `projects/Game_Surface_3`,2026-08-28。**记录在此,便于日后回看每个取舍的依据。**
|
||||
|
||||
**布局数据规模**
|
||||
- `gameabc_Object.json`:**991 个对象**,`ObjectType` 仅两种——`2` = 图片精灵(782)、`4` = 文字(209)
|
||||
- `gameabc_Layer.json`:**55 个 Layer = 55 个界面**(`Logo_Layer` / `Login_Layer` / `MainMenu_Layer` / `Player_Head_Score_Layer` / `inputPannel` …)
|
||||
- `gameabc_GroupList.json`:**80 个组**,覆盖 **990/991** 个对象(仅 1 个未分组)
|
||||
- **109 个对象**使用九宫格(`L9`/`T9`/`R9`/`B9` 非零)
|
||||
- **52 个对象**为满屏尺寸(≥1280×720),其中 **40 个是所在层的首个对象**
|
||||
- **222 个对象**坐标超出画布边界;`Left` 范围 **−286 ~ 1501**、`Top` 范围 **−318 ~ 1206**
|
||||
|
||||
**图片资源**
|
||||
- `gameabc_Image.json`:**439 张图**,其中 **139 张多帧(31.7%)**
|
||||
- 多帧图全部为规整网格:**`w × h = frame_all`,139 张零例外**
|
||||
- 切分后总计 **1006 帧 + 300 张单帧图 = 1306 张散图**
|
||||
- **78/139** 张多帧图被多个对象共用,最多一张被 **13 个对象**共用
|
||||
- **389 个对象**引用多帧图,其中 `FrameIndex = 0` 的仅 **2 个**
|
||||
|
||||
**坐标系与适配(现状)**
|
||||
- 旧引擎原点**左上**、Y 向下、**精灵锚点左上**,`position = (Left, Top)`,设计分辨率 **1280×720**
|
||||
- `OriginID` / `Parent` **全为 0**,`OriginPos` / `SelfPos` **全为 1** → **零相对锚定数据**
|
||||
- 引擎(`js/gameabc.min.js`)用 `innerWidth`/`innerHeight` + `scale` + `onresize` 做**整画布等比缩放**,靠 letterbox 适配
|
||||
|
||||
**文字**
|
||||
- 209 个 Type4 对象**全部没有 `ImageFileID`、`TextFrames` 为空** → 纯动态文本占位
|
||||
- 样式在代码里:`Utl.setFontColor(spid, value)`(`08_Utl_Output.js:824`),引擎用 canvas `fillText`
|
||||
- → **转换只能得到几何信息,字体/字号/颜色须由 `theme.fonts` 提供**
|
||||
|
||||
**运行时动态精灵(B 的线索,A 需为其保留信息)**
|
||||
- `ifast_addtospritefromspritecopy(父精灵ID, 模板精灵ID, x, y, tag)` —— **88 处调用**
|
||||
- `ifast_dllpritefromspritecopy` —— **74 处调用**
|
||||
- → 典型「克隆模板 → 用完删除」的列表/对象池模式;**991 个对象中有一部分是模板、一部分是容器**
|
||||
|
||||
### 0.3 帧序:1 基 + 行优先(实证结论)
|
||||
|
||||
```
|
||||
列 = (FrameIndex − 1) % w
|
||||
行 = ⌊(FrameIndex − 1) / w⌋
|
||||
源矩形 = (列 × w1, 行 × h1, w1, h1)
|
||||
```
|
||||
|
||||
**验证方法**(记录在此,因为这是最容易搞错且后果最广的一处):`00014.png` 为 450×280、网格 3×4,被 11 个按钮共用。直接读取该 PNG,其网格内容为:
|
||||
|
||||
```
|
||||
我的 福利 任务
|
||||
战绩 背包 仓库
|
||||
吐槽 财富榜 邀请码
|
||||
通知 设置 (空)
|
||||
```
|
||||
|
||||
对照对象的 `FrameIndex`:通知=10、设置=11、战绩=4、背包=5、仓库=6、任务=3。按「1 基 + 行优先」换算,六项**全部精确命中**;列优先与 0 基行优先均对不上。用户随后独立确认了该结论。
|
||||
|
||||
`FrameIndex = 0` 的 2 个对象按帧 1 处理。
|
||||
|
||||
> **一处被推翻的统计**:早期曾得出「1306 帧中 674 帧(51.6%)从未被引用」并险些据此裁剪资源。该数字**错误且不得使用**,两个原因:① 帧号 1 基,统计时把下标 0 当成了有效帧;② 平台层有 `set_self` 1773 处调用,**帧是运行时切换的**,静态数据里的 `FrameIndex` 只是初始帧。按钮的按下态/选中态就在那些「未引用」帧里。**任何帧都不得因静态未引用而删除。**
|
||||
|
||||
### 0.4 已确认决策
|
||||
|
||||
- **设计分辨率保持 1280×720 + `fitHeight`** —— 991 个对象的 X/Y **全部 1:1 迁移**,不做任何坐标缩放。现代宽屏由 `fitHeight` 天然获得(20:9 下水平可见区约等于 1600×720)。
|
||||
- **多帧图切成散图**(1306 张)—— 多帧网格是旧引擎缺图集能力的手工替代品,Cocos 的 Auto Atlas 在构建期自动打包;照搬它等于把引擎缺陷当成新框架的约定。
|
||||
- **同组对象挂同一父节点** —— `GroupList` 覆盖 990/991,是旧引擎用来做整体操作的机制,语义等价于父节点。
|
||||
- **锚点按中心点三区自动推断**,仅水平方向(§2.3)。
|
||||
- **背景四边拉伸**,美术后续手动更换正确尺寸的图片。
|
||||
- **全部 55 个界面转进框架**(含牌桌内的平台部分)。
|
||||
- **落地走「中间产物 + 编辑器批量」**(§4.1),不直接生成 `.prefab` 文件。
|
||||
|
||||
### 0.5 红线
|
||||
|
||||
- **绝不手写 `.prefab` / `.meta` / `.scene`**(CLAUDE.md)。序列化资源只能由编辑器经官方 API 产生。这一条直接否决了「Node 脚本直接吐 prefab」这一最直觉的做法(§4.1)。
|
||||
- **服务器零改动**:本 spec 不触及协议。
|
||||
- **数据源权威唯一、下游不兜底**:中间描述是转换的唯一真相;转换失败必须显式报错,不得静默产出残缺节点。
|
||||
- **不得因静态未引用而裁剪帧**(§0.3)。
|
||||
|
||||
### 0.6 方法论教训(写给后续维护者)
|
||||
|
||||
本 spec 的调研过程中**两次**因看错字段而误判「数据不存在」:
|
||||
|
||||
1. 查 `OriginID`/`Parent` 全为 0 → 结论「没有层级信息」。**错**:层级语义在 `GroupList` 里。
|
||||
2. 统计 `FrameIndex` 引用 → 结论「51.6% 帧是死重量」。**错**:帧号 1 基,且帧在运行时切换。
|
||||
|
||||
**教训**:在 gameabc 这类自研引擎的数据上,「某字段全为空」**不等于**「该语义不存在」——它可能编码在另一个字段、另一个文件,或根本在代码里。下结论前先把 `output/` 下**全部** JSON 与相关引擎 API 都看一遍。
|
||||
|
||||
---
|
||||
|
||||
## 1. 中间描述的数据模型
|
||||
|
||||
### 1.1 形态:一层一个 JSON + 一份 manifest
|
||||
|
||||
```
|
||||
tools/ui-migration/intermediate/
|
||||
├─ manifest.json # 55 界面索引 + ObjectID 全局映射 + 转换器版本
|
||||
├─ 002-Login_Layer.json
|
||||
├─ 004-MainMenu_Layer.json
|
||||
└─ …(共 55 个)
|
||||
```
|
||||
|
||||
一层一文件:**局部重跑与人工校对的单位就是界面**。改了锚点规则只想重转登录页,不该动其余 54 个。
|
||||
|
||||
### 1.2 节点模型
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"id": 254,
|
||||
"name": "微信登录按钮", // ObjectName 原样保留(中文名信息量大)
|
||||
"kind": "sprite", // ObjectType 2→sprite, 4→label
|
||||
"position": { "x": -245, "y": 125 }, // 已转为 Cocos 坐标系(§2.2)
|
||||
"size": { "width": 150, "height": 70 },
|
||||
"siblingIndex": 7, // 来自 IndexOfLayer,决定 z 序
|
||||
"widget": { "horizontal": "center", "horizontalCenter": -245 }, // §2.3
|
||||
|
||||
"sprite": {
|
||||
"frame": "atlas-login/00014_05.png", // 当前帧的逻辑路径(§3)
|
||||
"frameSet": { // 供 FrameSet 组件(§3.4)
|
||||
"prefix": "atlas-login/00014", // 帧文件名前缀
|
||||
"count": 12, // 帧总数,索引 1..count
|
||||
"pad": 2 // 补零位数,用于拼出 00014_01 … 00014_12
|
||||
},
|
||||
"type": "sliced", // L9/T9/R9/B9 任一非零 → sliced
|
||||
"slice": { "left": 12, "right": 12, "top": 8, "bottom": 8 }
|
||||
},
|
||||
|
||||
"flags": {
|
||||
"stretch": false, // 背景类,四边拉伸(§2.4)
|
||||
"offscreen": false, // 坐标超出画布(§2.5)
|
||||
"animation": false // FrameStyle=1,整图动画(§3.5)
|
||||
},
|
||||
|
||||
"legacy": { // 原始信息,不参与渲染,但**承重**(§1.3)
|
||||
"objectId": 254,
|
||||
"imageFileId": 14, "frameIndex": 5, "frameStyle": 0,
|
||||
"groupId": 3, "voiceFileId": 0, "timerInterval": 0,
|
||||
"left": 320, "top": 200, // 原始左上坐标,供回溯对照
|
||||
"events": ["mousedown", "mouseup"]
|
||||
// 校验:x = 320 + 150/2 − 640 = −245;y = 360 − 200 − 70/2 = 125
|
||||
// cx = 320 + 75 = 395 ∈ [320, 960] → 水平居中,horizontalCenter = 395 − 640 = −245
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`kind: "label"` 的节点只有 `position` / `size` / `widget` / `legacy`,**没有样式字段**——依据 §0.2,样式在旧代码里,数据中不存在。字体字号颜色由 `theme.fonts` 提供(见 `2026-08-27-ui-asset-and-skin-design.md` §4)。
|
||||
|
||||
### 1.3 `legacy.objectId` 是承重字段,不是纪念品
|
||||
|
||||
旧平台逻辑通过 **`set_self(精灵ID, 属性号, …)`** 操作 UI,全平台层 **1773 处**调用。**子系统 C 移植这些逻辑时,必须能由 ObjectID 定位到 Cocos 节点**——丢掉这个映射,那 1773 处调用就没有着落,C 会从「移植」退化成「重写」。
|
||||
|
||||
因此:
|
||||
- 每个节点保留完整 `legacy` 块
|
||||
- `manifest.json` 额外维护一份全局索引:**ObjectID → { 界面, 节点路径 }**
|
||||
|
||||
同理保留 `groupId`(B 需要它理解容器语义)与 `events`(C 需要它接事件)。
|
||||
|
||||
### 1.4 层级信息
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"layerId": 2,
|
||||
"name": "Login_Layer",
|
||||
"bucket": "login", // login | hall | room | common | unassigned(§3.3)
|
||||
"backgroundNodeId": 2, // 「层内首个 + 满屏」识别(40/52 命中)
|
||||
"groups": [ // 来自 GroupList,同组挂同一父节点(§2.1)
|
||||
{ "groupId": 2, "nodeIds": [2, 405, 285, /* … */] }
|
||||
],
|
||||
"nodes": [ /* 按 siblingIndex 排序 */ ]
|
||||
}
|
||||
```
|
||||
|
||||
### 1.5 本节有意不做的事
|
||||
|
||||
- **不推断按钮**。`Event` 含 `mousedown` 的对象包括背景图(对象 1 即是),据此判断交互性不可靠。事件原样存入 `legacy`,留给 C。
|
||||
- **不识别模板/容器**。§0.2 表明存在运行时克隆的模板与容器,但要认出「哪个 ObjectID 是 `ConstVal.myRoomList.bgSp`」需要读平台层代码——那是 B/C 的范围。A 只需**忠实保留 ObjectID**,使后续可交叉引用。
|
||||
|
||||
---
|
||||
|
||||
## 2. 坐标与锚点转换规则
|
||||
|
||||
### 2.1 group → 父节点:父节点必须是满屏容器
|
||||
|
||||
子节点坐标相对父节点,而旧数据全是绝对坐标。若 group 父节点用包围盒或零尺寸,两件事会同时出问题:子坐标要重算,且 **子节点的 Widget 会锚到组边界而非屏幕边界,适配立刻失效**。
|
||||
|
||||
解法是让父节点**不产生坐标偏移、且与屏幕同尺寸**:
|
||||
|
||||
```
|
||||
Layer 根节点 1280×720,Widget 四边拉伸
|
||||
└─ Group 节点 1280×720,Widget 四边拉伸,position (0,0)
|
||||
└─ 对象节点 §2.2 转换后的绝对坐标
|
||||
```
|
||||
|
||||
于是子对象坐标 = 转换后的绝对坐标(无需再减父偏移),其 Widget 锚定的满屏父节点等价于锚定屏幕边界。
|
||||
|
||||
### 2.2 坐标系转换
|
||||
|
||||
节点锚点采用 **Cocos 惯例 `(0.5, 0.5)`**:
|
||||
|
||||
```
|
||||
x = Left + Width / 2 − 640
|
||||
y = 360 − Top − Height / 2
|
||||
```
|
||||
|
||||
**为何不用 `(0, 1)`(左上)**:那样公式更简(`x = Left − 640`、`y = 360 − Top`)且与旧引擎语义一一对应,但用户明确会**在编辑器里人工审核并修改节点树**,而 Cocos 的编辑器、对齐工具、Widget 全部假定锚点居中。转换公式复杂一点是转换器一次性的成本,人工编辑的别扭是长期成本。
|
||||
|
||||
原始 `Left`/`Top` 存入 `legacy`,回溯对照随时可查。
|
||||
|
||||
### 2.3 锚点推断:只需要水平方向
|
||||
|
||||
**`fitHeight` 下垂直方向永远精确撑满、不会溢出,因此不需要任何垂直适配**——垂直用固定位置即可。这是保持 1280×720 设计分辨率带来的实质简化。
|
||||
|
||||
水平方向按对象中心点 `cx = Left + Width / 2` 三区推断:
|
||||
|
||||
| 条件 | 锚定 | Widget 值 |
|
||||
|---|---|---|
|
||||
| `cx < 320` | 靠左 | `left = Left` |
|
||||
| `cx > 960` | 靠右 | `right = 1280 − (Left + Width)` |
|
||||
| 其余 | 水平居中 | `horizontalCenter = cx − 640` |
|
||||
|
||||
分界取 1280 的四等分点。这是**机械规则,不追求完美**——用户已确认转换后会逐屏人工审核修正,规则只需「大部分对、且错了容易看出」。
|
||||
|
||||
### 2.4 背景:四边拉伸
|
||||
|
||||
识别规则:**层内首个 + 满屏尺寸**(实测 40/52 命中)。此类节点 Widget 四边置 0 全拉伸,中间描述标 `flags.stretch = true`。
|
||||
|
||||
另外 12 个满屏但非层内首个的(如「创建房间遮罩」)同样四边拉伸——它们本就是覆盖全屏的遮罩,行为一致。
|
||||
|
||||
转换器**不做任何图片处理**:美术后续会手动更换为正确尺寸的图片。
|
||||
|
||||
### 2.5 屏外对象
|
||||
|
||||
222 个对象坐标超出画布(`Left −286 ~ 1501`、`Top −318 ~ 1206`),多为动画入场的暂存位。
|
||||
|
||||
处理:**坐标原样转换,不裁剪、不钳制**。三区规则照常适用(`cx` 为负自然落入「靠左」)。额外标 `flags.offscreen = true`,供人工审核时快速筛出——它们最可能需要手工确认锚点是否合理。
|
||||
|
||||
---
|
||||
|
||||
## 3. 多帧图切分与命名约定
|
||||
|
||||
### 3.1 切分规则
|
||||
|
||||
见 §0.3 的公式与实证。139 张多帧图全部满足 `w × h = frame_all`,切分是纯机械操作。
|
||||
|
||||
### 3.2 命名:保持 1 基,补零对齐字典序
|
||||
|
||||
```
|
||||
00014.png (3×4) → 00014_01.png … 00014_12.png
|
||||
00019.png (13×1) → 00019_01.png … 00019_13.png
|
||||
```
|
||||
|
||||
**用 1 基而非 0 基**,使文件名与 `FrameIndex` 直接对齐:C 移植时代码里的 `set_self(spid, 帧属性号, 7)` 能一眼对上 `_07`,无需心算减一。这类 off-by-one 是最易反复踩的坑(§0.3 已经踩过一次统计口径)。
|
||||
|
||||
补零位数按 `frame_all` 决定,保证**字典序 = 帧序**(否则 `_10` 会排在 `_2` 前,图集打包与人工浏览都会乱)。
|
||||
|
||||
### 3.3 图集归属
|
||||
|
||||
每张图按**使用它的对象所在层**决定 bucket;跨多个 bucket 的图归 `atlas-common`。
|
||||
|
||||
层 → bucket 用实测出的 **LayerID 段位规律** + 可覆盖的显式表:
|
||||
|
||||
| LayerID 段 | bucket | 典型层 |
|
||||
|---|---|---|
|
||||
| 1–29 | `login` / `hall` | Logo/Login/Auth/Bind → login;MainMenu/Pay/Task/rankList/wareHouse → hall |
|
||||
| 50 / 202 / 4xx | `room` | MainScene / Player_Head_Score / Chat / Interact / btnPannel |
|
||||
| 6xx | `common` | inputPannel / Tips / Loading / Reconnect / Kick |
|
||||
|
||||
命名笼统的层(`Layer6` / `Layer13` / `Layer403` / `Layer411` / `Layer601` / `Layer603` / `Layer618`)标 `bucket: "unassigned"`,**不猜**,由人工归位。转换器遇到 `unassigned` 应告警而非静默归入 common。
|
||||
|
||||
### 3.4 运行时切帧:`FrameSet` 组件
|
||||
|
||||
每个引用多帧图的节点挂一个 `FrameSet` 组件,持有该图**全部帧**的 SpriteFrame 数组,索引与 `FrameIndex` 同为 1 基:
|
||||
|
||||
```ts
|
||||
frameSet.setFrame(7); // ← C 移植时对应 set_self(spid, 帧属性号, 7)
|
||||
```
|
||||
|
||||
副作用是必要的:全部帧都被数组引用 → **都会进图集、不会被依赖树裁掉**,符合 §0.3「任何帧都不得因静态未引用而删除」。
|
||||
|
||||
### 3.5 不切的情况
|
||||
|
||||
- **300 张单帧图**原样保留,仅按 bucket 分目录
|
||||
- **`FrameStyle = 1` 的 54 个对象**(整图动画)照常切帧,节点额外标 `flags.animation = true`,供后续做逐帧动画识别
|
||||
|
||||
### 3.6 物理组织与命名保留
|
||||
|
||||
```
|
||||
framework/ui/atlas-hall/00014_01.png … 00014_12.png
|
||||
framework/ui/atlas-login/00006_01.png … 00006_05.png
|
||||
```
|
||||
|
||||
**沿用原编号 + 帧号,不重命名为语义名**。两个理由:① 原编号是与 `gameabc_Image.json` 对照回溯的唯一线索;② 实测证明**对象名与美术内容会脱节**(对象叫「更多」,图上是「我的」;对象叫「分享」,图上是「福利」),语义命名会把错误信息固化进文件名。
|
||||
|
||||
---
|
||||
|
||||
## 4. 落地管线与验证
|
||||
|
||||
### 4.1 管线形态
|
||||
|
||||
```
|
||||
gameabc_*.json ──[Node 转换器]──► 中间描述 55 份 + 散图 1306 张
|
||||
│
|
||||
▼
|
||||
[编辑器侧批量落地]
|
||||
│
|
||||
▼
|
||||
framework/ui/*.prefab(55 个)
|
||||
```
|
||||
|
||||
**Node 侧**为纯函数:读 JSON、算坐标、推锚点、切帧、分 bucket。可 TDD,沿用既有工具链约定(`node:test`、零第三方依赖、显式路径提交)。
|
||||
|
||||
**编辑器侧**先用 MCP 跑通**一个**界面(建议 `Login_Layer`,18 个对象,规模合适且链路完整),确认节点树、Widget、SpriteFrame 引用无误,**再决定**是否值得写编辑器扩展批量跑完其余 54 个。不预先写扩展——先证明单条链路成立。
|
||||
|
||||
**为何不直接用 Node 生成 `.prefab`**:那要手写 UUID 引用的序列化格式,直接违反 §0.5 红线;991 个对象 × 55 个 prefab,格式错一点就是「资源导入失败」且只能人工恢复。
|
||||
|
||||
### 4.2 验证:分四层
|
||||
|
||||
991 个对象、1306 张图无法靠肉眼验证,故分层且尽量自动:
|
||||
|
||||
**① 几何与规则层(自动,TDD)**
|
||||
坐标换算、三区锚点推断、bucket 归属——纯函数,用已知样例断言(如「通知」按钮的 `Left`/`Top` → 期望的 Cocos 坐标与 Widget 值写死在测试中)。
|
||||
|
||||
**② 切图正确性(自动,且是完全验证)**
|
||||
切分是**可逆**操作:把切出的帧按原网格重组,必须与原图**逐字节相同**。
|
||||
|
||||
```
|
||||
切分(原图) → N 帧 → 重组(N 帧) ≡ 原图(逐字节)
|
||||
```
|
||||
|
||||
对全部 139 张多帧图跑重组校验,即**对全部 1006 帧的完全验证**,成本近零。行优先/1 基这类错误会被当场抓住——比靠读图判断可靠得多。
|
||||
|
||||
**③ prefab 结构(自动)**
|
||||
落地后回读 prefab:节点数、层级归属(group → 父节点)、z 序、Widget 参数,与中间描述逐项比对,差异即报错。
|
||||
|
||||
**④ 视觉验证(半自动,抽样)**
|
||||
用 MCP `cocos_capture` 截图,与原项目同界面截图并排比对。有 ①②③ 兜底后,这一层只需回答「规则本身定得对不对」(某按钮该锚右却锚了中),而非「转换器有没有 bug」。
|
||||
|
||||
### 4.3 与已交付工具链的衔接
|
||||
|
||||
切出的 1306 张图落进 `framework/ui/atlas-*`,正是 `2026-08-27-ui-asset-and-skin-design.md` §3.1 定义的**可覆盖白名单那 8 个目录**。因此:
|
||||
|
||||
- `check-skin` 的「框架可覆盖资源」将从**当前 0 个变为 1306 个**——换肤机制首次有真实对象
|
||||
- 该 spec §6 中三条一直 ⏸ 的承重假设(Auto Atlas 构建期打包、meta 尺寸行为、Spine)首次有真实素材可验证
|
||||
|
||||
**A 落地时应顺手复测这些假设**,而非任其继续悬挂。
|
||||
|
||||
### 4.4 幂等与可重跑
|
||||
|
||||
- 中间描述是纯函数产物,转换器可任意重跑
|
||||
- prefab 落地以**层为单位**,支持局部重跑(改了某界面的规则只重落该层)
|
||||
- 散图切分同样幂等;重跑前需清理旧产物,避免残留帧文件混入图集
|
||||
|
||||
---
|
||||
|
||||
## 5. 待验证项
|
||||
|
||||
| # | 待验证 | 验证方法 | 失败后的备选 |
|
||||
|---|---|---|---|
|
||||
| 1 | Auto Atlas 尺寸上限:1306 张图分 4 bucket,某 bucket 可能超单张图集 2048×2048 | 落地后构建,检查图集张数与尺寸 | 按加载时机进一步细分 bucket(如 `atlas-hall-1/2`) |
|
||||
| 2 | 包体:1306 张图全部进每个子游戏包 | 第一次真实构建后量实际包体 | 评估是否将低频界面(如 `Help`/`Protol`)转为远程 bundle |
|
||||
| 3 | MCP 批量建节点的可行性与耗时(991 节点) | 先跑 `Login_Layer` 18 个对象计时外推 | 写编辑器扩展批量落地 |
|
||||
| 4 | 三区锚点规则的实际命中率 | 抽样 5 个界面视觉比对 | 调整分界阈值,或改为逐元素人工标注 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 非目标(YAGNI)
|
||||
|
||||
- **不做嵌套节点树的进一步细分**。转换只产出「Layer → Group → 对象」两层结构;更细的节点树由用户在编辑器中人工组织。
|
||||
- **不识别按钮/交互组件**。事件信息保留在 `legacy`,由 C 处理。
|
||||
- **不迁移任何平台逻辑**。本 spec 只做静态 UI;逻辑属 C。
|
||||
- **不处理图片本身**(背景拉伸只设 Widget,不做缩放/重采样;美术后续手动更换)。
|
||||
- **不重命名资源为语义名**(§3.6)。
|
||||
- **不做逐帧动画的时间轴**。`flags.animation` 只做标记。
|
||||
|
||||
---
|
||||
|
||||
## 7. 与其它 spec 的接口
|
||||
|
||||
- **`2026-08-27-ui-asset-and-skin-design.md`**:切出的散图落入其定义的 8 个可覆盖目录;bucket 划分沿用其 §2 规则 3 的目录表;label 样式取自其 §4 的 `theme.fonts`。
|
||||
- **子系统 B(引擎机制映射,待写)**:需要 A 产出的 `legacy.objectId` / `groupId` / `events`;其产出的属性号对照表是 C 的前置。
|
||||
- **子系统 C(平台逻辑移植,待写)**:依赖 A 的 ObjectID → 节点路径全局索引(§1.3)与 B 的 API 映射表。
|
||||
- **组件替换 spec(待写)**:`SeatView` / 按人数布局。牌桌内的 `Player_Head_Score_Layer`(60 个对象)由 A 转出静态版本后,该 spec 决定哪些改为按人数动态生成。
|
||||
Reference in New Issue
Block a user