diff --git a/docs/superpowers/specs/原工程数据模型.md b/docs/superpowers/specs/原工程数据模型.md new file mode 100644 index 0000000..171bec3 --- /dev/null +++ b/docs/superpowers/specs/原工程数据模型.md @@ -0,0 +1,360 @@ +# 原工程数据模型 (Game_Surface_3) + +> **Status**: Draft v1 (2026-09-03) +> **Purpose**: 为后续 UI 迁移提供 Game_Surface_3 数据契约的权威参考 +> **基于**: `projects/Game_Surface_3/output/*.json` + `save/*.xml` + `js/gameabc.min.js`(解析输出) + +## 总览 + +Game_Surface_3 是 Cocos-JS (HTML5) 单页游戏平台,使用以下数据文件: + +| 文件 | 大小 | 内容 | +|---|---|---| +| `output/gameabc_Project.json` | 778 B | 全局配置(屏幕尺寸、事件名、fps 等) | +| `output/gameabc_GroupList.json` | 8.3 KB | Group→Object 列表(55 groups) | +| `output/gameabc_Layer.json` | 12 KB | Layer→Object 列表(55 layers) | +| `output/gameabc_Object.json` | 905 KB | **核心**:991 个 object 的完整属性+事件+选项(3293 个槽位) | +| `output/gameabc_Image.json` | 67 KB | 439 张 sprite PNG 元数据(id → bmp + 多帧) | +| `output/gameabc_Voice.json` | 252 B | 音频文件映射(id → mp3) | +| `output/gameabc_GameTxt.json` | 20 KB | 多语言文案(GameTxtList) | +| `save/*.xml` | 59 文件 | Layer XML 定义(每层一个文件) | +| `js/gameabc.min.js` | 185 KB | 引擎核心(已 minify,难逆向) | + +**Layer 8 (Notice_Layer) 关键交叉验证**: +- `gameabc_Layer.json[8].ObjectList = [3122, 145, 3121, 3017, 5, 235, 604, 3057, 15]` (9 个 object IDs) +- `save/Layer00008.xml` 的 9 个 `` IDs:**完全匹配** +- `gameabc_Object.json` 中这 9 个 ID 的 `GroupID = 3`(145)或 `GroupID = 95`(其他 8 个)也**完全匹配** XML 的 `BelongGroupID` 字段 + +## gameabc_Project.json schema + +```typescript +type Project = { + Property: { + ProjectName: string; // e.g. "Game_Surface_3" + ScreenWidth: number; // 1280 + ScreenHeight: number; // 720 + GameSceneWidth: number; // 1699 (logical scene width) + GameSceneHeight: number; // 1800 + ScreenFitMode: number; // 1 (适配模式) + TcpIP: string; + TcpPort: string; + Http: string; + title: string; + }; + Event: { + gamestart, gamebegindraw, gameenddraw, + mousedown, mousedown_nomove, mouseup, mousemove, + gamemydraw, gamemydrawbegin, chongzhi, + tcpconnected, tcpdisconnected, tcpmessage, tcperror, httpmessage, + ani_doend, box_doend, onresize, ontimer, onloadurl: number; + }; + Option: { + fps: number; // 30 + tag, tag1, tag2, tag3: number; + showmodel: number; + }; +}; +``` + +## gameabc_Layer.json schema + +```typescript +type Layer = { + Property: { + LayerID: number; // 1-620 (不连续) + LayerName: string; // e.g. "Login_Layer", "Notice_Layer" + }; + ObjectList: number[]; // 该 layer 包含的 object ID 列表(按 IndexOfLayer 排序) +}; + +type LayerList = (Layer | {})[]; // 长度 621,空对象占位 +``` + +**示例**: +```json +{"Property":{"LayerID":8,"LayerName":"Notice_Layer"},"ObjectList":[3122,145,3121,3017,5,235,604,3057,15]} +``` + +## gameabc_GroupList.json schema + +```typescript +type Group = { + GroupID: number; // 1, 3, 95, ...(55 个有效 group) + ObjectList: number[]; // 该 group 包含的 object ID 列表 +}; + +type GroupList = (Group | {})[]; // 长度未读(稀疏) +``` + +**关键映射关系**:`Object.GroupID ↔ Object.ObjectList in Layer.ObjectList` + +## gameabc_Object.json schema(**核心**) + +```typescript +type GameObject = { + Property: { /* 共 41 字段,见下方 */ }; + Event?: { /* 事件→回调 object ID 映射 */ }; + Option?: { /* 选项 11 字段 */ }; +}; + +type ObjectList = (GameObject | {})[]; // 长度 3293,索引 = object ID - 1 +``` + +### Property 字段(41 字段) + +#### 标识 + 位置(10 字段) +| 字段 | 类型 | 含义 | XML 对应 | +|---|---|---|---| +| `ObjectID` | int | 唯一 ID(也是数组索引+1) | Spirit `ID` 属性 | +| `ObjectType` | int | 组件类型(见下方映射表) | Spirit `SpiritType` | +| `ObjectName` | str | 中文/英文节点名 | Spirit `Name` | +| `Left`, `Top` | int | 绝对坐标 X, Y(左上角锚点) | Spirit `X`, `Y` | +| `Width`, `Height` | int | 尺寸 W, H | Spirit `Width`, `Height` | +| `BelongLayerID` | int | 所属 Layer ID | Spirit `BelongLayerID` | +| `IndexOfLayer` | int | 同 layer 内排序 | Spirit `IndexOfLayer` | +| `GroupID` | int | 所属 Group(0=根 layer) | Spirit `BelongGroupID` | +| `offX`, `offY` | int | 同 Left/Top(冗余字段) | — | + +#### ObjectType 映射(实测) + +| ObjectType | 含义 | 出现次数 (Notice_Layer 9 个中) | 示例 | +|---|---|---|---| +| 0 | (未观察到) | — | — | +| 1 | (未观察到) | — | — | +| **2** | **Image/Sprite** | **8/9** (Notice_Layer 中除 145 外) | id 5 (通知), id 3122 (Bgsprite), id 3121 (按钮托盘 SLICED) | +| 3 | (未观察到?) | — | — | +| **4** | **EditBox (含 Text + FontSize + FontColor)** | **1/9** | id 145 (公告内容) | +| 5 | (未观察到) | — | — | + +> ⚠️ **修正之前判断**:之前我以为 ObjectType 2=Sprite, 3=ProgressBar, 4=EditBox, 5=SLICED Sprite(基于 gameabc.min.js minified 推断)—— **实测 Object 145 是 ObjectType=4(EditBox),含 Text/FontSize/FontColor 字段**,不是纯 Label!需要重新校验 Cocos 端 cc.EditBox vs cc.Label 对应关系。 + +#### 9-slice border 字段(4 字段) +| 字段 | 类型 | 含义 | +|---|---|---| +| `L9`, `T9`, `R9`, `B9` | int | 左/上/右/下 9-slice border 像素值 | + +> 🟢 **Cocos 对应**:`cc.SpriteFrame.capInsets = [L9, T9, R9, B9]`(capInsets 全 0 = SIMPLE,否则 SLICED) + +#### Frame 字段(4 字段) +| 字段 | 类型 | 含义 | +|---|---|---| +| `FrameStyle` | int | 0=单帧, 1=多帧(具体待 gameabc.min.js 逆向) | +| `FrameIndex` | int | 当前显示的帧索引(0 到 FrameStyle 总帧数-1) | +| `TextFrames` | str | (空字符串或具体多帧配置?需逆向) | + +> 🟡 **Frame 字段语义待 gameabc.min.js 逆向确认** + +#### 字体字段(EditBox/Text 字段,8 字段) +| 字段 | 类型 | 含义 | +|---|---|---| +| `FontSize` | int | 字体大小 | +| `FontBold` | int | 0/1 | +| `FontColorR/G/B` | int | RGB 颜色分量 | +| `FontColor` | str | "#FFFFFF" 形式 | +| `BackColorR/G/B/A` | int | 背景色(带 alpha) | +| `BackColor` | str | 背景色 16进制 | +| `GameTxtStyle` | int | 多语言文案索引(对应 gameabc_GameTxt.json) | +| `LineSpace` | int | 行距 | +| `Text` | str | 文本内容 | + +#### 关系字段(4 字段) +| 字段 | 类型 | 含义 | +|---|---|---| +| `Parent` | int | 父 object ID(0=无父) | +| `OriginID` | int | 锚点参考 object ID(0=绝对坐标) | +| `OriginPos` | int | 锚点位置:1=top-left, 9=center | +| `SelfPos` | int | 自身位置:1=top-left, 9=center | + +#### 资源引用字段(2 字段) +| 字段 | 类型 | 含义 | Cocos 对应 | +|---|---|---|---| +| `ImageFileID` | int | sprite 资源 ID(→ gameabc_Image.json) | `_spriteFrame.__uuid__` | +| `VoiceFileID` | int | 音效 ID(→ gameabc_Voice.json) | (audio component) | + +#### 杂项(4 字段) +| 字段 | 类型 | 含义 | +|---|---|---| +| `Data` | str | 自定义数据字段 | +| `TimerInterval` | int | 定时器间隔(0=无) | +| `L9/T9/R9/B9` | (见上) | (见上) | + +### Event schema + +```typescript +type Event = { + mousedown?: number; // 回调 object ID(值=0 表示无回调) + mouseup?: number; + mousemove?: number; + ontimer?: number; + // 注: Project.json 列出 20+ 事件类型, Object 的 Event 只观察到这 4 个常用 +}; +``` + +> 🟡 Project.json Event 列出 20+ 种 (gamestart, gamebegindraw, ...),但 Object.json 里只观察到 mousedown/mouseup/mousemove/ontimer 4 种。其他事件可能由 Project 级或 Group 级处理。 + +### Option schema(11 字段) + +```typescript +type Option = { + tag, tag1, tag2, tag3: number; // 用户自定义标签 + vx, vy, vw, vh: number; // velocity (动画/物理) + canclick: number; // 0/1, 是否可点击 + visbale: number; // 0/1, 是否可见(注意:原工程拼写错误 "visbale" 而非 "visible"!) +}; +``` + +## gameabc_Image.json schema + +```typescript +type ImageRecord = { + id: number; // = ImgResID(与 Object.ImageFileID 对应) + w_all: number; // 完整 sprite 宽(含多帧拼接) + h_all: number; // 完整 sprite 高 + w: number; // 帧宽 + h: number; // 帧高 + frame_all: number; // 总帧数 + bmp: string; // "00001.png" 形式 + w1: number; // 第 1 帧宽(多帧时 w 可能 != w1) + h1: number; // 第 1 帧高 +}; + +type ImageFileList = (ImageRecord | {})[]; // 长度 440,索引 = id - 1 +``` + +**多帧 sprite 迁移规则**(已写入 playbook §关键迁移规则): +- `id=N` 单帧:`bmp = "0000N.png"` 直接用 +- `id=N` 多帧(frame_all > 1):`bmp = "0000N.png"` 含多帧 → Cocos 用 `0000N_01.png` ~ `0000N_NN.png` 多张散图 + +## gameabc_Voice.json schema + +```typescript +type VoiceRecord = { id: number; file: string; }; // file = "0000N.mp3" +type VoiceFileList = (VoiceRecord | {})[]; +``` + +## gameabc_GameTxt.json schema + +```typescript +type GameTxtList = (string | {})[]; // 长度 ≈ 274,多语言文案槽位 +``` + +**用法**:`Object.GameTxtStyle` 是 GameTxtList 的索引 → 取字符串作为显示文本(支持多语言)。 + +## Layer XML ↔ gameabc_Layer.json ↔ gameabc_Object.json 关系 + +``` +Layer00008.xml gameabc_Layer.json gameabc_Object.json +───────────────────── ───────────────────── ────────────────────── + LayerList[8] ObjectList[144] + 3017, 5, 235, ObjectType: 4, + X="81" Y="787" 604, 3057, 15] ObjectName: "公告内容", + Width="60" Height="20" Left: 81, Top: 787, + BelongGroupID="3"/> Width: 60, Height: 20, + ... GroupID: 3, + Text: "公告内容", + ... + } + } +gameabc_Object.json[144] 即 Object 145(数组 index = ObjectID - 1) +``` + +**Layer 8 → ObjectList 9 IDs → 9 个 ObjectList 索引** 完全 1:1 映射。 + +## 关键数据关系图 + +``` +Project (gameabc_Project.json) + └─ ProjectName + ScreenWidth/Height + 全局事件 + fps + │ + ▼ +Layer (gameabc_Layer.json) + └─ LayerID + LayerName + ObjectList[] + │ + ▼ +Object (gameabc_Object.json) ← Layer ↔ Object N:1 + ├─ Property (41 字段) ObjectID ↔ array index + │ ├─ ObjectType (0-5) ├─ 0=? 1=? 2=Image/Sprite 3=? 4=EditBox 5=? + │ ├─ ObjectName (中文) ├─ GroupID → 包含此 object 的 group ID + │ ├─ Left/Top (绝对坐标) │ + │ ├─ Width/Height ├─ Parent (父 object, 0=无) + │ ├─ GroupID (0=根) └─ OriginID (锚点参考, 0=绝对坐标) + │ ├─ ImageFileID │ + │ │ │ Group (gameabc_GroupList.json) + │ │ ▼ └─ GroupID + ObjectList[] (本组所有 object) + │ └─ FrameIndex + FrameStyle + │ │ + │ ├─ Text + FontSize (if EditBox/Text) + │ ├─ L9/T9/R9/B9 (9-slice border) + │ └─ offX/offY + │ + ├─ Event (4 字段) + │ └─ mousedown/mouseup/mousemove/ontimer → callback ObjectID + │ + └─ Option (11 字段) + ├─ canclick / visbale (拼写错误!) + └─ tag1-3 / vx,vy,vw,vh (动画/物理) + +Image (gameabc_Image.json) + └─ id (=ImageFileID) + w_all/h_all/w/h/frame_all/bmp + w1/h1 + │ + ▼ + SpriteFrame (Cocos) + ├─ name = bmp (00001.png) + ├─ rect (sub-region in atlas) + ├─ capInsets = [L9, T9, R9, B9] + └─ texture = atlas image asset UUID + +Voice (gameabc_Voice.json) ──→ VoiceFileID → .mp3 + +GameTxt (gameabc_GameTxt.json) ──→ Object.GameTxtStyle → 多语言文案 +``` + +## 与 Cocos prefab 的字段映射(迁移目标) + +| 原工程字段 (gameabc_Object.Property) | Cocos prefab 字段 | 转换公式/规则 | +|---|---|---| +| ObjectID + ObjectName | `_id` (数字) + `_name` | 字符串直接 | +| ObjectType | cc.Sprite / cc.Label / cc.ProgressBar / cc.EditBox | 按映射表(待 gameabc.min.js 逆向确认完整 0-5) | +| Left, Top | `_lpos.x, _lpos.y` | `x = Left + Width/2 - 640, y = 360 - (Top + Height/2)` | +| Width, Height | `_contentSize.width, .height` | 直接 | +| GroupID | (不映射为单独字段 → 父节点 group-X) | group-X 容器 nodes + children | +| BelongLayerID | cc.Node._parent | 跨 layer 引用 | +| ImageFileID + FrameIndex | cc.SpriteFrame.__uuid__ | atlas-lookup (multi-frame rule) | +| Text | cc.Label._string | (if EditBox → cc.EditBox.string, else cc.Label.string) | +| FontSize, FontColor, BackColor, FontBold | cc.Label._fontSize, ._color | 直接 | +| L9, T9, R9, B9 | cc.SpriteFrame.capInsets | 直接数组 | +| OriginID + OriginPos + SelfPos | cc.Node anchor logic | 9=中心(待逆向公式) | +| offX/offY | (冗余字段,验证用) | — | +| FrameStyle + FrameIndex + TextFrames | cc.ProgressBar._mode, ._progress | 9-slice 多帧公式 | +| Event: mousedown/mouseup | Button._clickEvents | (cocos Button click events 待 wire) | +| Option: canclick | 决定是否加 cc.Button | — | +| Option: tag1-3 | (用户自定义数据) | (忽略) | +| Option: vx/vy/vw/vh | (动画/物理,Cocos 端 cc.Animation / Box2D 等) | (deferred to game logic plan) | +| VoiceFileID | cc.AudioSource 资源引用 | (deferred) | +| GameTxtStyle | cc.Label._string i18n lookup | (deferred to i18n sub-task) | + +## 已知未映射字段(deferred sub-tasks) + +| 字段 | 含义 | 状态 | +|---|---|---| +| `Option.vx/vy/vw/vh` | 动画/物理速度 | deferred to game logic migration | +| `VoiceFileID` | 音频 | deferred to audio migration | +| `GameTxtStyle` | i18n | deferred to i18n migration | +| `Event.gamestart` 等 16+ Project 级事件 | 全局事件 | deferred to game lifecycle | +| `OriginID + OriginPos + SelfPos` | 锚点计算 | gameabc.min.js 逆向待完成 | + +## 后续步骤 + +按用户原始指令(先停下 UI 迁移,深入阅读原工程): +- ✅ 7 个 JSON schema + XML 关系 + Object schema 完成 +- ✅ ObjectType 映射实测(修正之前误判:ObjectType=4 是 EditBox 不是 Label) +- ✅ Layer 8 与 gameabc_Layer.json ObjectList 1:1 验证通过 + +**TODO**(等用户指示): +1. 继续看 gameabc.min.js 完整 ObjectType 0-5 映射 +2. 读 FrameStyle/FrameIndex/TextFrames 在多帧 sprite 下的具体公式 +3. 读 OriginID + OriginPos + SelfPos 在 9-slice 布局的具体公式 +4. 输出原工程数据模型完整 spec 文档(已在本文件中) +5. 给用户报告 + 等指示继续 UI 迁移