实测 ObjectType 0/1/3 不存在; 2=782 Sprite, 4=209 EditBox; 5/6 运行时动态创建 实测 SelfPos=OriginPos=1 (top-left, 无复杂锚点公式) + FrameStyle 分布 (单帧 728, 多帧 54, EditBox 209) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
16 KiB
原工程数据模型 (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 个<Spirit ID="...">IDs:完全匹配gameabc_Object.json中这 9 个 ID 的GroupID = 3(145)或GroupID = 95(其他 8 个)也完全匹配 XML 的BelongGroupID字段
gameabc_Project.json schema
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
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,空对象占位
示例:
{"Property":{"LayerID":8,"LayerName":"Notice_Layer"},"ObjectList":[3122,145,3121,3017,5,235,604,3057,15]}
gameabc_GroupList.json schema
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(核心)
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 映射(实测 - 完整版)
实测 gameabc_Object.json 991 个非空对象 + gameabc.min.js 逆向:
| ObjectType | 含义 | ObjectList 出现次数 | 示例 | Cocos 对应 |
|---|---|---|---|---|
| 0 | (不存在) | 0 | — | — |
| 1 | (不存在) | 0 | — | — |
| 2 | Image/Sprite | 782/991 | id 1 (友乐背景, 1280×720 全屏), id 5 (通知, 150×70) | cc.Sprite (单帧) 或 cc.Sprite _type=1 (SLICED 9-slice) |
| 3 | (不存在静态) | 0 | — | — |
| 4 | Text/EditBox | 209/991 | id 65 (战绩文字内容), id 145 (公告内容) | cc.Label 或 cc.EditBox (含 Text + FontSize) |
| 5 | (运行时动态) | 0 (静态) | gameabc.min.js 中 obj.uidata 子对象 (按钮 controller, SizeStyle 控件) |
不需要静态迁移 (动态生成) |
| 6 | (运行时动态) | 0 (静态) | gameabc.min.js 中 this.ObjectType = 6 (SizeStyle 控件初始化) |
不需要静态迁移 |
🟢 关键发现:ObjectList 静态数据只有 ObjectType 2 (Sprite) 和 4 (Text/EditBox)。Type 5/6 是 gameabc 运行时动态创建的对象(如 button controller、SizeStyle),不需要持久化迁移。
🟢 SelfPos=OriginPos=1 对所有 991 object 都成立 → 所有 object 都用 top-left 锚点(X,Y 即左上角绝对坐标,无需复杂 OriginPos 公式)。
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 字段(实测 - 完整版)
| 字段 | 类型 | 全工程分布 | 含义 |
|---|---|---|---|
FrameStyle |
int | None: 209 (EditBox), 0: 728 (单帧 Sprite), 1: 54 (多帧 Sprite) | 0=单帧, 1=多帧动画 |
FrameIndex |
int | 54 个 Sprite 用 | 当前显示的帧索引 (0 到 FrameStyle 总帧数-1) |
TextFrames |
str | 多帧配置字符串 (待具体逆向) | (目前空字符串) |
🟢 实测 FrameStyle 分布: 单帧 Sprite 占主导 (728/991=73%), 多帧 Sprite 占 54/991=5%, EditBox/Text 无帧 (21%)。多帧 sprite 需用 atlas 多张散图 (
gameabc_Image.bmp+frame_all字段 → 拆成多 PNG,参考 playbook §关键迁移规则)。
字体字段(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
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 字段)
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
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
type VoiceRecord = { id: number; file: string; }; // file = "0000N.mp3"
type VoiceFileList = (VoiceRecord | {})[];
gameabc_GameTxt.json schema
type GameTxtList = (string | {})[]; // 长度 ≈ 274,多语言文案槽位
用法:Object.GameTxtStyle 是 GameTxtList 的索引 → 取字符串作为显示文本(支持多语言)。
Layer XML ↔ gameabc_Layer.json ↔ gameabc_Object.json 关系
Layer00008.xml gameabc_Layer.json gameabc_Object.json
───────────────────── ───────────────────── ──────────────────────
<Spirit ID="145"> LayerList[8] ObjectList[144]
<Property ObjectList: [ "Property":
Name="公告内容" 3122, 145, 3121, ObjectID: 145,
ImgResID="..."/> 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(等用户指示):
- 继续看 gameabc.min.js 完整 ObjectType 0-5 映射
- 读 FrameStyle/FrameIndex/TextFrames 在多帧 sprite 下的具体公式
- 读 OriginID + OriginPos + SelfPos 在 9-slice 布局的具体公式
- 输出原工程数据模型完整 spec 文档(已在本文件中)
- 给用户报告 + 等指示继续 UI 迁移