feat(art): 《山海行纪》资产一期批产(锚 r1)——112 镜头 2.72MB 图集入库
矿彩国风锚 r1 落地:P1 80 镜头(图 1 tileset+三常规兽各 6 帧+词缀精英/光环/双 Boss 22 帧含立绘+UI 13+底图)
+P2 44 镜头(主角 13 帧定妆帧参考图法跨帧身份一致+法器图标 12+弹道命中特效 15+拾取 4);
5 图集 xingji-{map1,beasts,boss,ui,actors}+tiles+bg 共 2,911,369B≈2.72MB(首屏切面≈2.03MB,boss 页 1.03MB=
懒加载分包面,分包决策归开发席 M4);机检 C1–C5 全绿。累计 127 呼 ¥4.19、首过 92%,远离升档 B 判据;
clean-room 五条全程(兽名不入 prompt/伪字物件化改词)。
主会话修机检门两态口径:C2/C3 keyed 中间件只活在批产工作区(ASSET_WORK 不入仓),整目录缺席=仓库态
复跑降级警示跳过、部分缺席仍判错(消除席位环境绿/CI 必红假门,仓库态复跑 rc=0);
顺手修 asset-atlas 局部重打包整写 manifest 丢条目缺陷。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ -0,0 +1,943 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "xingji-actors.webp",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 1104,
|
||||
"h": 1130
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "hero-attack-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 224,
|
||||
"h": 221
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 224,
|
||||
"h": 219
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 3,
|
||||
"w": 224,
|
||||
"h": 219
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-zhendi-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 192,
|
||||
"h": 192
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 190,
|
||||
"h": 192
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 228,
|
||||
"w": 190,
|
||||
"h": 192
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "hero-hurt-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 187,
|
||||
"h": 192
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
"w": 186,
|
||||
"h": 192
|
||||
},
|
||||
"frame": {
|
||||
"x": 199,
|
||||
"y": 228,
|
||||
"w": 186,
|
||||
"h": 192
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "hero-idle-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 140,
|
||||
"h": 192
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 140,
|
||||
"h": 192
|
||||
},
|
||||
"frame": {
|
||||
"x": 233,
|
||||
"y": 3,
|
||||
"w": 140,
|
||||
"h": 192
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "hero-idle-03",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 192,
|
||||
"h": 180
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 192,
|
||||
"h": 180
|
||||
},
|
||||
"frame": {
|
||||
"x": 391,
|
||||
"y": 3,
|
||||
"w": 192,
|
||||
"h": 180
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "hero-idle-04",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 167,
|
||||
"h": 192
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
"w": 166,
|
||||
"h": 192
|
||||
},
|
||||
"frame": {
|
||||
"x": 391,
|
||||
"y": 189,
|
||||
"w": 166,
|
||||
"h": 192
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "hero-run-05",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 154,
|
||||
"h": 192
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
"w": 153,
|
||||
"h": 192
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 426,
|
||||
"w": 153,
|
||||
"h": 192
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "hero-run-06",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 192,
|
||||
"h": 187
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 192,
|
||||
"h": 186
|
||||
},
|
||||
"frame": {
|
||||
"x": 162,
|
||||
"y": 426,
|
||||
"w": 192,
|
||||
"h": 186
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "hero-hurt-02",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 190,
|
||||
"h": 192
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
"w": 188,
|
||||
"h": 191
|
||||
},
|
||||
"frame": {
|
||||
"x": 360,
|
||||
"y": 426,
|
||||
"w": 188,
|
||||
"h": 191
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "hero-run-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 192,
|
||||
"h": 171
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 191,
|
||||
"h": 170
|
||||
},
|
||||
"frame": {
|
||||
"x": 589,
|
||||
"y": 3,
|
||||
"w": 191,
|
||||
"h": 170
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "hero-run-04",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 192,
|
||||
"h": 191
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 191,
|
||||
"h": 190
|
||||
},
|
||||
"frame": {
|
||||
"x": 589,
|
||||
"y": 179,
|
||||
"w": 191,
|
||||
"h": 190
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "hero-idle-02",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 192,
|
||||
"h": 190
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
"w": 190,
|
||||
"h": 190
|
||||
},
|
||||
"frame": {
|
||||
"x": 554,
|
||||
"y": 387,
|
||||
"w": 190,
|
||||
"h": 190
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "hero-run-02",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 192,
|
||||
"h": 175
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 190,
|
||||
"h": 173
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 624,
|
||||
"w": 190,
|
||||
"h": 173
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "hero-run-03",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 192,
|
||||
"h": 171
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 2,
|
||||
"y": 0,
|
||||
"w": 190,
|
||||
"h": 171
|
||||
},
|
||||
"frame": {
|
||||
"x": 199,
|
||||
"y": 623,
|
||||
"w": 190,
|
||||
"h": 171
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-tianlei-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 117,
|
||||
"h": 176
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 3,
|
||||
"y": 0,
|
||||
"w": 111,
|
||||
"h": 174
|
||||
},
|
||||
"frame": {
|
||||
"x": 395,
|
||||
"y": 623,
|
||||
"w": 111,
|
||||
"h": 174
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-hit-fire-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 152,
|
||||
"h": 160
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 152,
|
||||
"h": 160
|
||||
},
|
||||
"frame": {
|
||||
"x": 512,
|
||||
"y": 623,
|
||||
"w": 152,
|
||||
"h": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-hit-ice-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 160,
|
||||
"h": 160
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 160,
|
||||
"h": 160
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 803,
|
||||
"w": 160,
|
||||
"h": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-hit-thunder-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 160,
|
||||
"h": 160
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 160,
|
||||
"h": 160
|
||||
},
|
||||
"frame": {
|
||||
"x": 169,
|
||||
"y": 803,
|
||||
"w": 160,
|
||||
"h": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-icon-fuling-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 36,
|
||||
"h": 160
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 36,
|
||||
"h": 160
|
||||
},
|
||||
"frame": {
|
||||
"x": 335,
|
||||
"y": 803,
|
||||
"w": 36,
|
||||
"h": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-icon-hanyuan-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 154,
|
||||
"h": 160
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 154,
|
||||
"h": 160
|
||||
},
|
||||
"frame": {
|
||||
"x": 377,
|
||||
"y": 803,
|
||||
"w": 154,
|
||||
"h": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-icon-jinge-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 34,
|
||||
"h": 160
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 33,
|
||||
"h": 160
|
||||
},
|
||||
"frame": {
|
||||
"x": 537,
|
||||
"y": 789,
|
||||
"w": 33,
|
||||
"h": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-icon-shanzi-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 160,
|
||||
"h": 160
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 160,
|
||||
"h": 160
|
||||
},
|
||||
"frame": {
|
||||
"x": 576,
|
||||
"y": 789,
|
||||
"w": 160,
|
||||
"h": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-icon-zhendi-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 160,
|
||||
"h": 145
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 160,
|
||||
"h": 144
|
||||
},
|
||||
"frame": {
|
||||
"x": 786,
|
||||
"y": 3,
|
||||
"w": 160,
|
||||
"h": 144
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-icon-poyue-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 155,
|
||||
"h": 160
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
"w": 153,
|
||||
"h": 159
|
||||
},
|
||||
"frame": {
|
||||
"x": 670,
|
||||
"y": 583,
|
||||
"w": 153,
|
||||
"h": 159
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-icon-tianlei-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 53,
|
||||
"h": 160
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 52,
|
||||
"h": 159
|
||||
},
|
||||
"frame": {
|
||||
"x": 750,
|
||||
"y": 375,
|
||||
"w": 52,
|
||||
"h": 159
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-icon-xinghuo-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 140,
|
||||
"h": 160
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 138,
|
||||
"h": 159
|
||||
},
|
||||
"frame": {
|
||||
"x": 829,
|
||||
"y": 153,
|
||||
"w": 138,
|
||||
"h": 159
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-fuling-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 134,
|
||||
"h": 160
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 2,
|
||||
"w": 133,
|
||||
"h": 158
|
||||
},
|
||||
"frame": {
|
||||
"x": 829,
|
||||
"y": 318,
|
||||
"w": 133,
|
||||
"h": 158
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-icon-fengren-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 153,
|
||||
"h": 160
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 152,
|
||||
"h": 158
|
||||
},
|
||||
"frame": {
|
||||
"x": 742,
|
||||
"y": 748,
|
||||
"w": 152,
|
||||
"h": 158
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-icon-fentian-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 160,
|
||||
"h": 159
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 156,
|
||||
"h": 158
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 969,
|
||||
"w": 156,
|
||||
"h": 158
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-icon-leifu-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 159,
|
||||
"h": 160
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 158,
|
||||
"h": 158
|
||||
},
|
||||
"frame": {
|
||||
"x": 165,
|
||||
"y": 969,
|
||||
"w": 158,
|
||||
"h": 158
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-icon-xuanming-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 160,
|
||||
"h": 157
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 5,
|
||||
"y": 0,
|
||||
"w": 154,
|
||||
"h": 157
|
||||
},
|
||||
"frame": {
|
||||
"x": 329,
|
||||
"y": 969,
|
||||
"w": 154,
|
||||
"h": 157
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-fengren-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 115,
|
||||
"h": 128
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 115,
|
||||
"h": 128
|
||||
},
|
||||
"frame": {
|
||||
"x": 489,
|
||||
"y": 969,
|
||||
"w": 115,
|
||||
"h": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-hanyuan-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 128,
|
||||
"h": 120
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 128,
|
||||
"h": 120
|
||||
},
|
||||
"frame": {
|
||||
"x": 829,
|
||||
"y": 482,
|
||||
"w": 128,
|
||||
"h": 120
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-jinge-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
"w": 127,
|
||||
"h": 128
|
||||
},
|
||||
"frame": {
|
||||
"x": 829,
|
||||
"y": 608,
|
||||
"w": 127,
|
||||
"h": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-poyue-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 128,
|
||||
"h": 56
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 128,
|
||||
"h": 54
|
||||
},
|
||||
"frame": {
|
||||
"x": 742,
|
||||
"y": 912,
|
||||
"w": 128,
|
||||
"h": 54
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-shanzi-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"frame": {
|
||||
"x": 610,
|
||||
"y": 972,
|
||||
"w": 128,
|
||||
"h": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-xuanming-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"frame": {
|
||||
"x": 744,
|
||||
"y": 972,
|
||||
"w": 128,
|
||||
"h": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "pickup-gem-02",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"frame": {
|
||||
"x": 973,
|
||||
"y": 3,
|
||||
"w": 128,
|
||||
"h": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-fentian-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 128,
|
||||
"h": 121
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 127,
|
||||
"h": 120
|
||||
},
|
||||
"frame": {
|
||||
"x": 973,
|
||||
"y": 137,
|
||||
"w": 127,
|
||||
"h": 120
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-leifu-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 128,
|
||||
"h": 85
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 2,
|
||||
"y": 2,
|
||||
"w": 126,
|
||||
"h": 81
|
||||
},
|
||||
"frame": {
|
||||
"x": 973,
|
||||
"y": 263,
|
||||
"w": 126,
|
||||
"h": 81
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "pickup-orb-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 100,
|
||||
"h": 112
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 99,
|
||||
"h": 112
|
||||
},
|
||||
"frame": {
|
||||
"x": 968,
|
||||
"y": 350,
|
||||
"w": 99,
|
||||
"h": 112
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "pickup-coin-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 84,
|
||||
"h": 96
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 83,
|
||||
"h": 96
|
||||
},
|
||||
"frame": {
|
||||
"x": 968,
|
||||
"y": 468,
|
||||
"w": 83,
|
||||
"h": 96
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "pickup-gem-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 95,
|
||||
"h": 96
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 95,
|
||||
"h": 96
|
||||
},
|
||||
"frame": {
|
||||
"x": 963,
|
||||
"y": 570,
|
||||
"w": 95,
|
||||
"h": 96
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "fx-xinghuo-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 96,
|
||||
"h": 94
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 95,
|
||||
"h": 93
|
||||
},
|
||||
"frame": {
|
||||
"x": 962,
|
||||
"y": 672,
|
||||
"w": 95,
|
||||
"h": 93
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "http://github.com/odrick/free-tex-packer-cli",
|
||||
"version": "0.3.0"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 459 KiB |
@ -0,0 +1,628 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "xingji-beasts.webp",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 1051,
|
||||
"h": 916
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "elite-taowu-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 224,
|
||||
"h": 155
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 224,
|
||||
"h": 155
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 3,
|
||||
"w": 224,
|
||||
"h": 155
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "elite-zheng-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 224,
|
||||
"h": 185
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 224,
|
||||
"h": 185
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 164,
|
||||
"w": 224,
|
||||
"h": 185
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "elite-qiongqi-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 224,
|
||||
"h": 151
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 223,
|
||||
"h": 150
|
||||
},
|
||||
"frame": {
|
||||
"x": 233,
|
||||
"y": 3,
|
||||
"w": 223,
|
||||
"h": 150
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "qiongqi-attack-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 192,
|
||||
"h": 168
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 192,
|
||||
"h": 167
|
||||
},
|
||||
"frame": {
|
||||
"x": 233,
|
||||
"y": 159,
|
||||
"w": 192,
|
||||
"h": 167
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "qiongqi-move-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 192,
|
||||
"h": 178
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 192,
|
||||
"h": 178
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 355,
|
||||
"w": 192,
|
||||
"h": 178
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "qiongqi-move-02",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 191,
|
||||
"h": 192
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 191,
|
||||
"h": 192
|
||||
},
|
||||
"frame": {
|
||||
"x": 233,
|
||||
"y": 332,
|
||||
"w": 191,
|
||||
"h": 192
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "taowu-attack-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 192,
|
||||
"h": 192
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 192,
|
||||
"h": 192
|
||||
},
|
||||
"frame": {
|
||||
"x": 462,
|
||||
"y": 3,
|
||||
"w": 192,
|
||||
"h": 192
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "taowu-death-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 192,
|
||||
"h": 165
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 192,
|
||||
"h": 165
|
||||
},
|
||||
"frame": {
|
||||
"x": 431,
|
||||
"y": 201,
|
||||
"w": 192,
|
||||
"h": 165
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "taowu-hurt-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 192,
|
||||
"h": 115
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 192,
|
||||
"h": 113
|
||||
},
|
||||
"frame": {
|
||||
"x": 430,
|
||||
"y": 372,
|
||||
"w": 192,
|
||||
"h": 113
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "taowu-idle-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 192,
|
||||
"h": 178
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 192,
|
||||
"h": 177
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 539,
|
||||
"w": 192,
|
||||
"h": 177
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "taowu-move-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 186,
|
||||
"h": 192
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 186,
|
||||
"h": 192
|
||||
},
|
||||
"frame": {
|
||||
"x": 430,
|
||||
"y": 491,
|
||||
"w": 186,
|
||||
"h": 192
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "taowu-move-02",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 192,
|
||||
"h": 156
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 192,
|
||||
"h": 155
|
||||
},
|
||||
"frame": {
|
||||
"x": 201,
|
||||
"y": 530,
|
||||
"w": 192,
|
||||
"h": 155
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "zheng-death-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 192,
|
||||
"h": 130
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 192,
|
||||
"h": 128
|
||||
},
|
||||
"frame": {
|
||||
"x": 660,
|
||||
"y": 3,
|
||||
"w": 192,
|
||||
"h": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "elite-aura-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 182,
|
||||
"h": 192
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 182,
|
||||
"h": 191
|
||||
},
|
||||
"frame": {
|
||||
"x": 660,
|
||||
"y": 137,
|
||||
"w": 182,
|
||||
"h": 191
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "elite-aura-02",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 192,
|
||||
"h": 188
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 191,
|
||||
"h": 186
|
||||
},
|
||||
"frame": {
|
||||
"x": 629,
|
||||
"y": 334,
|
||||
"w": 191,
|
||||
"h": 186
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "qiongqi-hurt-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 181,
|
||||
"h": 192
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 181,
|
||||
"h": 191
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 722,
|
||||
"w": 181,
|
||||
"h": 191
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "qiongqi-idle-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 182,
|
||||
"h": 192
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 180,
|
||||
"h": 191
|
||||
},
|
||||
"frame": {
|
||||
"x": 190,
|
||||
"y": 722,
|
||||
"w": 180,
|
||||
"h": 191
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "zheng-hurt-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 192,
|
||||
"h": 164
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 191,
|
||||
"h": 163
|
||||
},
|
||||
"frame": {
|
||||
"x": 622,
|
||||
"y": 526,
|
||||
"w": 191,
|
||||
"h": 163
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "zheng-idle-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 176,
|
||||
"h": 192
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 176,
|
||||
"h": 191
|
||||
},
|
||||
"frame": {
|
||||
"x": 376,
|
||||
"y": 695,
|
||||
"w": 176,
|
||||
"h": 191
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "zheng-move-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 183,
|
||||
"h": 192
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 183,
|
||||
"h": 191
|
||||
},
|
||||
"frame": {
|
||||
"x": 558,
|
||||
"y": 695,
|
||||
"w": 183,
|
||||
"h": 191
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "qiongqi-death-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 192,
|
||||
"h": 125
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 190,
|
||||
"h": 123
|
||||
},
|
||||
"frame": {
|
||||
"x": 858,
|
||||
"y": 3,
|
||||
"w": 190,
|
||||
"h": 123
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "zheng-attack-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 192,
|
||||
"h": 147
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
"w": 190,
|
||||
"h": 146
|
||||
},
|
||||
"frame": {
|
||||
"x": 858,
|
||||
"y": 132,
|
||||
"w": 190,
|
||||
"h": 146
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "zheng-move-02",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 192,
|
||||
"h": 163
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 187,
|
||||
"h": 162
|
||||
},
|
||||
"frame": {
|
||||
"x": 848,
|
||||
"y": 284,
|
||||
"w": 187,
|
||||
"h": 162
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "affix-flame-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 91,
|
||||
"h": 96
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
"w": 89,
|
||||
"h": 96
|
||||
},
|
||||
"frame": {
|
||||
"x": 747,
|
||||
"y": 695,
|
||||
"w": 89,
|
||||
"h": 96
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "affix-frost-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 89,
|
||||
"h": 96
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 89,
|
||||
"h": 96
|
||||
},
|
||||
"frame": {
|
||||
"x": 747,
|
||||
"y": 797,
|
||||
"w": 89,
|
||||
"h": 96
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "affix-swift-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 95,
|
||||
"h": 96
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 95,
|
||||
"h": 96
|
||||
},
|
||||
"frame": {
|
||||
"x": 819,
|
||||
"y": 526,
|
||||
"w": 95,
|
||||
"h": 96
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "affix-thunder-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 96,
|
||||
"h": 96
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 96,
|
||||
"h": 96
|
||||
},
|
||||
"frame": {
|
||||
"x": 920,
|
||||
"y": 452,
|
||||
"w": 96,
|
||||
"h": 96
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "affix-venom-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 95,
|
||||
"h": 96
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 95,
|
||||
"h": 96
|
||||
},
|
||||
"frame": {
|
||||
"x": 920,
|
||||
"y": 554,
|
||||
"w": 95,
|
||||
"h": 96
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "affix-armor-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 96,
|
||||
"h": 94
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 6,
|
||||
"y": 3,
|
||||
"w": 90,
|
||||
"h": 91
|
||||
},
|
||||
"frame": {
|
||||
"x": 842,
|
||||
"y": 656,
|
||||
"w": 90,
|
||||
"h": 91
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "http://github.com/odrick/free-tex-packer-cli",
|
||||
"version": "0.3.0"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 433 KiB |
481
game-runtime/games/shanhai-xingji/assets/atlas/xingji-boss.json
Normal file
@ -0,0 +1,481 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "xingji-boss.webp",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 1692,
|
||||
"h": 1557
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "luwu-portrait-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 384,
|
||||
"h": 375
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 384,
|
||||
"h": 373
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 3,
|
||||
"w": 384,
|
||||
"h": 373
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "paoxiao-portrait-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 384,
|
||||
"h": 340
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 382,
|
||||
"h": 338
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 382,
|
||||
"w": 382,
|
||||
"h": 338
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "luwu-attack-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 320,
|
||||
"h": 320
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 320,
|
||||
"h": 320
|
||||
},
|
||||
"frame": {
|
||||
"x": 393,
|
||||
"y": 3,
|
||||
"w": 320,
|
||||
"h": 320
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "luwu-attack-02",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 320,
|
||||
"h": 320
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 320,
|
||||
"h": 319
|
||||
},
|
||||
"frame": {
|
||||
"x": 393,
|
||||
"y": 329,
|
||||
"w": 320,
|
||||
"h": 319
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "luwu-attack-03",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 320,
|
||||
"h": 292
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 2,
|
||||
"w": 320,
|
||||
"h": 290
|
||||
},
|
||||
"frame": {
|
||||
"x": 719,
|
||||
"y": 3,
|
||||
"w": 320,
|
||||
"h": 290
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "luwu-hurt-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 320,
|
||||
"h": 320
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 320,
|
||||
"h": 320
|
||||
},
|
||||
"frame": {
|
||||
"x": 719,
|
||||
"y": 299,
|
||||
"w": 320,
|
||||
"h": 320
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "luwu-move-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 320,
|
||||
"h": 301
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 320,
|
||||
"h": 300
|
||||
},
|
||||
"frame": {
|
||||
"x": 719,
|
||||
"y": 625,
|
||||
"w": 320,
|
||||
"h": 300
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "luwu-move-02",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 320,
|
||||
"h": 311
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 320,
|
||||
"h": 309
|
||||
},
|
||||
"frame": {
|
||||
"x": 391,
|
||||
"y": 654,
|
||||
"w": 320,
|
||||
"h": 309
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "luwu-roar-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 313,
|
||||
"h": 320
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 313,
|
||||
"h": 320
|
||||
},
|
||||
"frame": {
|
||||
"x": 717,
|
||||
"y": 931,
|
||||
"w": 313,
|
||||
"h": 320
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "paoxiao-attack-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 320,
|
||||
"h": 316
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 320,
|
||||
"h": 316
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 726,
|
||||
"w": 320,
|
||||
"h": 316
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "paoxiao-attack-03",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 320,
|
||||
"h": 308
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 320,
|
||||
"h": 308
|
||||
},
|
||||
"frame": {
|
||||
"x": 329,
|
||||
"y": 969,
|
||||
"w": 320,
|
||||
"h": 308
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "paoxiao-death-02",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 320,
|
||||
"h": 227
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 320,
|
||||
"h": 224
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 1048,
|
||||
"w": 320,
|
||||
"h": 224
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "paoxiao-hurt-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 320,
|
||||
"h": 267
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 320,
|
||||
"h": 267
|
||||
},
|
||||
"frame": {
|
||||
"x": 1045,
|
||||
"y": 3,
|
||||
"w": 320,
|
||||
"h": 267
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "paoxiao-move-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 320,
|
||||
"h": 316
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 320,
|
||||
"h": 316
|
||||
},
|
||||
"frame": {
|
||||
"x": 1045,
|
||||
"y": 276,
|
||||
"w": 320,
|
||||
"h": 316
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "paoxiao-move-02",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 320,
|
||||
"h": 312
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 320,
|
||||
"h": 312
|
||||
},
|
||||
"frame": {
|
||||
"x": 1045,
|
||||
"y": 598,
|
||||
"w": 320,
|
||||
"h": 312
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "paoxiao-roar-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 320,
|
||||
"h": 301
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 2,
|
||||
"w": 320,
|
||||
"h": 299
|
||||
},
|
||||
"frame": {
|
||||
"x": 1045,
|
||||
"y": 916,
|
||||
"w": 320,
|
||||
"h": 299
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "paoxiao-death-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 320,
|
||||
"h": 278
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 319,
|
||||
"h": 276
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 1278,
|
||||
"w": 319,
|
||||
"h": 276
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "luwu-death-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 320,
|
||||
"h": 307
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 318,
|
||||
"h": 307
|
||||
},
|
||||
"frame": {
|
||||
"x": 1036,
|
||||
"y": 1221,
|
||||
"w": 318,
|
||||
"h": 307
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "luwu-death-02",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 320,
|
||||
"h": 273
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 2,
|
||||
"y": 0,
|
||||
"w": 318,
|
||||
"h": 271
|
||||
},
|
||||
"frame": {
|
||||
"x": 655,
|
||||
"y": 1257,
|
||||
"w": 318,
|
||||
"h": 271
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "luwu-idle-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 320,
|
||||
"h": 254
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 318,
|
||||
"h": 254
|
||||
},
|
||||
"frame": {
|
||||
"x": 328,
|
||||
"y": 1283,
|
||||
"w": 318,
|
||||
"h": 254
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "paoxiao-attack-02",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 320,
|
||||
"h": 300
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 2,
|
||||
"y": 0,
|
||||
"w": 318,
|
||||
"h": 300
|
||||
},
|
||||
"frame": {
|
||||
"x": 1371,
|
||||
"y": 3,
|
||||
"w": 318,
|
||||
"h": 300
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "paoxiao-idle-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 320,
|
||||
"h": 289
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 2,
|
||||
"y": 2,
|
||||
"w": 318,
|
||||
"h": 286
|
||||
},
|
||||
"frame": {
|
||||
"x": 1371,
|
||||
"y": 309,
|
||||
"w": 318,
|
||||
"h": 286
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "http://github.com/odrick/free-tex-packer-cli",
|
||||
"version": "0.3.0"
|
||||
}
|
||||
}
|
||||
BIN
game-runtime/games/shanhai-xingji/assets/atlas/xingji-boss.webp
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
271
game-runtime/games/shanhai-xingji/assets/atlas/xingji-map1.json
Normal file
@ -0,0 +1,271 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "xingji-map1.webp",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 719,
|
||||
"h": 738
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "event-rift-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 208,
|
||||
"h": 204
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 208,
|
||||
"h": 202
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 3,
|
||||
"w": 208,
|
||||
"h": 202
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "deco-bone-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 176,
|
||||
"h": 167
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 176,
|
||||
"h": 165
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 211,
|
||||
"w": 176,
|
||||
"h": 165
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "deco-rock-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 174,
|
||||
"h": 176
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 174,
|
||||
"h": 176
|
||||
},
|
||||
"frame": {
|
||||
"x": 217,
|
||||
"y": 3,
|
||||
"w": 174,
|
||||
"h": 176
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "deco-totem-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 129,
|
||||
"h": 176
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 128,
|
||||
"h": 176
|
||||
},
|
||||
"frame": {
|
||||
"x": 217,
|
||||
"y": 185,
|
||||
"w": 128,
|
||||
"h": 176
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "deco-totem-02",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 171,
|
||||
"h": 176
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 170,
|
||||
"h": 176
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 382,
|
||||
"w": 170,
|
||||
"h": 176
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "event-chest-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 157,
|
||||
"h": 176
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 157,
|
||||
"h": 176
|
||||
},
|
||||
"frame": {
|
||||
"x": 179,
|
||||
"y": 382,
|
||||
"w": 157,
|
||||
"h": 176
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "deco-reed-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 166,
|
||||
"h": 176
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 165,
|
||||
"h": 175
|
||||
},
|
||||
"frame": {
|
||||
"x": 397,
|
||||
"y": 3,
|
||||
"w": 165,
|
||||
"h": 175
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "deco-rock-02",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 174,
|
||||
"h": 176
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
"w": 172,
|
||||
"h": 175
|
||||
},
|
||||
"frame": {
|
||||
"x": 342,
|
||||
"y": 367,
|
||||
"w": 172,
|
||||
"h": 175
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "deco-runestone-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 176,
|
||||
"h": 169
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 175,
|
||||
"h": 167
|
||||
},
|
||||
"frame": {
|
||||
"x": 351,
|
||||
"y": 185,
|
||||
"w": 175,
|
||||
"h": 167
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "event-spring-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 176,
|
||||
"h": 171
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
"w": 175,
|
||||
"h": 171
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 564,
|
||||
"w": 175,
|
||||
"h": 171
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "deco-moss-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 171,
|
||||
"h": 176
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 169,
|
||||
"h": 174
|
||||
},
|
||||
"frame": {
|
||||
"x": 342,
|
||||
"y": 548,
|
||||
"w": 169,
|
||||
"h": 174
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "event-shrine-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 148,
|
||||
"h": 176
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 1,
|
||||
"w": 148,
|
||||
"h": 174
|
||||
},
|
||||
"frame": {
|
||||
"x": 568,
|
||||
"y": 3,
|
||||
"w": 148,
|
||||
"h": 174
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "http://github.com/odrick/free-tex-packer-cli",
|
||||
"version": "0.3.0"
|
||||
}
|
||||
}
|
||||
BIN
game-runtime/games/shanhai-xingji/assets/atlas/xingji-map1.webp
Normal file
|
After Width: | Height: | Size: 139 KiB |
271
game-runtime/games/shanhai-xingji/assets/atlas/xingji-ui.json
Normal file
@ -0,0 +1,271 @@
|
||||
{
|
||||
"textures": [
|
||||
{
|
||||
"image": "xingji-ui.webp",
|
||||
"format": "RGBA8888",
|
||||
"size": {
|
||||
"w": 911,
|
||||
"h": 846
|
||||
},
|
||||
"scale": 1,
|
||||
"frames": [
|
||||
{
|
||||
"filename": "ui-bar-boss-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 512,
|
||||
"h": 164
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 2,
|
||||
"w": 510,
|
||||
"h": 161
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 3,
|
||||
"w": 510,
|
||||
"h": 161
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "ui-bar-frame-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 512,
|
||||
"h": 105
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 2,
|
||||
"y": 2,
|
||||
"w": 508,
|
||||
"h": 101
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 170,
|
||||
"w": 508,
|
||||
"h": 101
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "ui-card-frame-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 277,
|
||||
"h": 384
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 277,
|
||||
"h": 384
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 277,
|
||||
"w": 277,
|
||||
"h": 384
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "ui-card-frame-02",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 277,
|
||||
"h": 384
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 277,
|
||||
"h": 384
|
||||
},
|
||||
"frame": {
|
||||
"x": 286,
|
||||
"y": 277,
|
||||
"w": 277,
|
||||
"h": 384
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "ui-btn-02",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 384,
|
||||
"h": 177
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 383,
|
||||
"h": 176
|
||||
},
|
||||
"frame": {
|
||||
"x": 3,
|
||||
"y": 667,
|
||||
"w": 383,
|
||||
"h": 176
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "ui-btn-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 384,
|
||||
"h": 173
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"w": 382,
|
||||
"h": 171
|
||||
},
|
||||
"frame": {
|
||||
"x": 392,
|
||||
"y": 667,
|
||||
"w": 382,
|
||||
"h": 171
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "ui-joystick-base-01",
|
||||
"rotated": false,
|
||||
"trimmed": true,
|
||||
"sourceSize": {
|
||||
"w": 184,
|
||||
"h": 192
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 2,
|
||||
"y": 0,
|
||||
"w": 180,
|
||||
"h": 192
|
||||
},
|
||||
"frame": {
|
||||
"x": 569,
|
||||
"y": 3,
|
||||
"w": 180,
|
||||
"h": 192
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "ui-badge-tide-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"frame": {
|
||||
"x": 569,
|
||||
"y": 201,
|
||||
"w": 128,
|
||||
"h": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "ui-badge-wave-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"frame": {
|
||||
"x": 569,
|
||||
"y": 335,
|
||||
"w": 128,
|
||||
"h": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "ui-orb-hp-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"frame": {
|
||||
"x": 569,
|
||||
"y": 469,
|
||||
"w": 128,
|
||||
"h": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "ui-slot-item-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 128,
|
||||
"h": 128
|
||||
},
|
||||
"frame": {
|
||||
"x": 780,
|
||||
"y": 3,
|
||||
"w": 128,
|
||||
"h": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"filename": "ui-joystick-knob-01",
|
||||
"rotated": false,
|
||||
"trimmed": false,
|
||||
"sourceSize": {
|
||||
"w": 96,
|
||||
"h": 96
|
||||
},
|
||||
"spriteSourceSize": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 96,
|
||||
"h": 96
|
||||
},
|
||||
"frame": {
|
||||
"x": 780,
|
||||
"y": 137,
|
||||
"w": 96,
|
||||
"h": 96
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"app": "http://github.com/odrick/free-tex-packer-cli",
|
||||
"version": "0.3.0"
|
||||
}
|
||||
}
|
||||
BIN
game-runtime/games/shanhai-xingji/assets/atlas/xingji-ui.webp
Normal file
|
After Width: | Height: | Size: 198 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 132 KiB |
77
game-runtime/games/shanhai-xingji/assets/manifest.json
Normal file
@ -0,0 +1,77 @@
|
||||
{
|
||||
"batch": "xingji-a1",
|
||||
"note": "《山海行纪》资产一期 manifest:图集/底图/tile 表与字节账。命名 role-state-frame;帧坐标在各图集同名 json(Phaser3 版式,LittleJS 按 frames 换算)。",
|
||||
"anchor": "../../../docs-design/山海风格版-批次一/anchor-pack.json",
|
||||
"styleDiscipline": "styleWords 单源=锚包 r1;生图台账(逐呼 prompt/seed/成败)=同目录 manifest.jsonl,batch=xingji-a1",
|
||||
"generatedAt": "2026-07-07T07:31:00.125Z",
|
||||
"atlases": {
|
||||
"xingji-map1": [
|
||||
{
|
||||
"page": "xingji-map1.webp",
|
||||
"json": "xingji-map1.json",
|
||||
"frames": 12,
|
||||
"webpBytes": 142472
|
||||
}
|
||||
],
|
||||
"xingji-beasts": [
|
||||
{
|
||||
"page": "xingji-beasts.webp",
|
||||
"json": "xingji-beasts.json",
|
||||
"frames": 29,
|
||||
"webpBytes": 443840
|
||||
}
|
||||
],
|
||||
"xingji-boss": [
|
||||
{
|
||||
"page": "xingji-boss.webp",
|
||||
"json": "xingji-boss.json",
|
||||
"frames": 22,
|
||||
"webpBytes": 1057434
|
||||
}
|
||||
],
|
||||
"xingji-ui": [
|
||||
{
|
||||
"page": "xingji-ui.webp",
|
||||
"json": "xingji-ui.json",
|
||||
"frames": 12,
|
||||
"webpBytes": 202748
|
||||
}
|
||||
],
|
||||
"xingji-actors": [
|
||||
{
|
||||
"page": "xingji-actors.webp",
|
||||
"json": "xingji-actors.json",
|
||||
"frames": 44,
|
||||
"webpBytes": 470310
|
||||
}
|
||||
]
|
||||
},
|
||||
"tiles": {
|
||||
"file": "tiles/tiles-map1.webp",
|
||||
"tileSize": 128,
|
||||
"cols": 8,
|
||||
"rows": 6,
|
||||
"order": "a=0-15 基底 / b=16-31 暗变奏 / c=32-47 刻痕变奏(索引=行×8+列)",
|
||||
"bytes": 240842
|
||||
},
|
||||
"bg": [
|
||||
{
|
||||
"id": "ui-panel-sortie-01",
|
||||
"file": "bg/ui-panel-sortie-01.webp",
|
||||
"bytes": 135152,
|
||||
"adopted": false
|
||||
},
|
||||
{
|
||||
"id": "bg-map1-vista-01",
|
||||
"file": "bg/bg-map1-vista-01.webp",
|
||||
"bytes": 164100,
|
||||
"adopted": true
|
||||
}
|
||||
],
|
||||
"bytes": {
|
||||
"atlasWebp": 2316804,
|
||||
"tilesWebp": 240842,
|
||||
"bgWebp": 299252,
|
||||
"total": 2856898
|
||||
}
|
||||
}
|
||||
BIN
game-runtime/games/shanhai-xingji/assets/tiles/tiles-map1.webp
Normal file
|
After Width: | Height: | Size: 235 KiB |
149
game-runtime/games/shanhai-xingji/scripts/asset-atlas.mjs
Normal file
@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env node
|
||||
// 《山海行纪》资产批产 · 图集打包 + 底图转码 + 资产 manifest(迁移自批次一 atlas-pack.mjs)
|
||||
// 职责:
|
||||
// ① keyed/{group}/*.png 按消费分组打 2048² 图集(free-tex-packer,MaxRects,trim),
|
||||
// png 中转 → webp q80 落 assets/atlas/xingji-{group}*.webp + 同名坐标 json(image 指 webp);
|
||||
// ② bg 组镜头(整幅底图)不打包,直接转码 webp q80 落 assets/bg/{id}.webp;
|
||||
// ③ 汇总写 assets/manifest.json:图集分页/帧数/字节 + tiles 几何 + bg 清单 + 锚包指针
|
||||
// ——图集字节数即两本账(契约包内账/首屏传输账)的实测输入。
|
||||
// 用法: node scripts/asset-atlas.mjs [--group beasts] (环境变量 ASSET_WORK 同 asset-gen.mjs)
|
||||
import { readFileSync, writeFileSync, readdirSync, mkdirSync, statSync, existsSync, rmSync, copyFileSync } from 'node:fs';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { dirname, join, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const GAME = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const BATCH1REL = '../../../docs-design/山海风格版-批次一'; // 锚包单源(相对本游戏根,仅指针不复制)
|
||||
const WORK = process.env.ASSET_WORK || join(tmpdir(), 'xingji-a1-work');
|
||||
const KEYED = join(WORK, 'keyed');
|
||||
const ATLAS_DIR = join(GAME, 'assets', 'atlas');
|
||||
const BG_DIR = join(GAME, 'assets', 'bg');
|
||||
const onlyGroup = process.argv.includes('--group') ? process.argv[process.argv.indexOf('--group') + 1] : null;
|
||||
|
||||
const { shots, batch } = JSON.parse(readFileSync(join(GAME, 'scripts', 'asset-shots.json'), 'utf8'));
|
||||
// boss 独立成组:双 Boss 10/20 分钟才登场,是天然的懒加载分包面(首屏账不背它)
|
||||
const GROUPS = ['map1', 'beasts', 'boss', 'ui', 'actors'];
|
||||
mkdirSync(ATLAS_DIR, { recursive: true });
|
||||
mkdirSync(BG_DIR, { recursive: true });
|
||||
|
||||
// PIL 转码 webp q80(与批次一同法:free-tex-packer 不直出 webp)
|
||||
function toWebp(src, dest, quality = 80) {
|
||||
execFileSync('python3', ['-c',
|
||||
`from PIL import Image\nImage.open(${JSON.stringify(src)}).save(${JSON.stringify(dest)}, quality=${quality}, method=6)`,
|
||||
], { stdio: 'inherit', timeout: 60_000 });
|
||||
}
|
||||
|
||||
const manifest = {
|
||||
batch,
|
||||
note: '《山海行纪》资产一期 manifest:图集/底图/tile 表与字节账。命名 role-state-frame;帧坐标在各图集同名 json(Phaser3 版式,LittleJS 按 frames 换算)。',
|
||||
anchor: `${BATCH1REL}/anchor-pack.json`,
|
||||
styleDiscipline: 'styleWords 单源=锚包 r1;生图台账(逐呼 prompt/seed/成败)=同目录 manifest.jsonl,batch=xingji-a1',
|
||||
generatedAt: new Date().toISOString(),
|
||||
atlases: {}, tiles: null, bg: [], bytes: { atlasWebp: 0, tilesWebp: 0, bgWebp: 0, total: 0 },
|
||||
};
|
||||
|
||||
// 断点续跑语义:局部重打包(--group / 工作区缺该组 keyed)时,未重打的组与缺源 bg 从既有
|
||||
// manifest 继承盘上产物,避免局部跑整写 manifest 时丢掉其余图集条目(工作区是临时目录,
|
||||
// 断点恢复后常常只需重打单组;继承字节以实盘 statSync 为准,防账实漂移)。
|
||||
const mfPath = join(GAME, 'assets', 'manifest.json');
|
||||
const prevManifest = existsSync(mfPath) ? JSON.parse(readFileSync(mfPath, 'utf8')) : null;
|
||||
function carryGroup(group, why) {
|
||||
const name = `xingji-${group}`;
|
||||
const prev = prevManifest?.atlases?.[name];
|
||||
if (!prev) { console.log(`[${group}] ${why},无既有 manifest 条目可继承,跳过`); return; }
|
||||
const pages = prev.filter(p => existsSync(join(ATLAS_DIR, p.page)) && existsSync(join(ATLAS_DIR, p.json)));
|
||||
if (pages.length < prev.length) console.warn(`[${group}] 继承时 ${prev.length - pages.length} 页盘上缺失,已丢弃(机检 C4 会兜)`);
|
||||
if (!pages.length) return;
|
||||
for (const p of pages) { p.webpBytes = statSync(join(ATLAS_DIR, p.page)).size; manifest.bytes.atlasWebp += p.webpBytes; }
|
||||
manifest.atlases[name] = pages;
|
||||
console.log(`[${group}] ${why},继承既有产物(${pages.length} 页,不重编码)`);
|
||||
}
|
||||
|
||||
// ---- ① 分组图集 ----
|
||||
for (const group of GROUPS) {
|
||||
if (onlyGroup && group !== onlyGroup) { carryGroup(group, `--group=${onlyGroup} 局部跑`); continue; }
|
||||
const inDir = join(KEYED, group);
|
||||
if (!existsSync(inDir)) { carryGroup(group, '无 keyed 输入'); continue; }
|
||||
const pngs = readdirSync(inDir).filter(f => f.endsWith('.png'));
|
||||
if (!pngs.length) { carryGroup(group, 'keyed 目录为空'); continue; }
|
||||
const name = `xingji-${group}`;
|
||||
const packTmp = join(WORK, 'atlas-tmp', group);
|
||||
rmSync(packTmp, { recursive: true, force: true });
|
||||
mkdirSync(packTmp, { recursive: true });
|
||||
// free-tex-packer 工程文件(schema 核自 free-tex-packer-cli@0.3.0,承批次一)
|
||||
const project = {
|
||||
savePath: packTmp,
|
||||
images: pngs.map(f => ({ name: f, path: join(inDir, f) })),
|
||||
folders: [],
|
||||
packOptions: {
|
||||
textureName: name, width: 2048, height: 2048, fixedSize: false, powerOfTwo: false,
|
||||
padding: 2, extrude: 1, allowRotation: false, detectIdentical: true,
|
||||
allowTrim: true, trimMode: 'trim', alphaThreshold: 0,
|
||||
exporter: 'Phaser3', removeFileExtension: true, prependFolderName: false,
|
||||
textureFormat: 'png', base64Export: false, scale: 1, packer: 'MaxRectsPacker',
|
||||
},
|
||||
};
|
||||
const proj = join(packTmp, `${name}.ftpp`);
|
||||
writeFileSync(proj, JSON.stringify(project, null, 2));
|
||||
execFileSync('free-tex-packer-cli', ['--project', proj, '--output', packTmp], { stdio: 'inherit', timeout: 120_000 });
|
||||
|
||||
const pages = readdirSync(packTmp).filter(f => f.startsWith(name) && f.endsWith('.png'));
|
||||
const entry = [];
|
||||
for (const png of pages.sort()) {
|
||||
const base = png.replace(/\.png$/, '');
|
||||
const webp = join(ATLAS_DIR, `${base}.webp`);
|
||||
toWebp(join(packTmp, png), webp);
|
||||
const jsonSrc = join(packTmp, `${base}.json`);
|
||||
const j = JSON.parse(readFileSync(jsonSrc, 'utf8'));
|
||||
for (const t of j.textures || []) if (t.image) t.image = t.image.replace(/\.png$/, '.webp');
|
||||
const jsonDest = join(ATLAS_DIR, `${base}.json`);
|
||||
writeFileSync(jsonDest, JSON.stringify(j, null, 2));
|
||||
const frames = (j.textures || []).reduce((s, t) => s + (t.frames?.length || 0), 0);
|
||||
const bytes = statSync(webp).size;
|
||||
manifest.bytes.atlasWebp += bytes;
|
||||
entry.push({ page: `${base}.webp`, json: `${base}.json`, frames, webpBytes: bytes });
|
||||
console.log(`[${group}] 页 ${base}:${frames} 帧 | webp ${(bytes / 1024).toFixed(0)}KB`);
|
||||
}
|
||||
manifest.atlases[name] = entry;
|
||||
}
|
||||
|
||||
// ---- ② bg 底图直转码 ----
|
||||
const RAW = join(WORK, 'raw');
|
||||
const BATCH1 = resolve(GAME, BATCH1REL);
|
||||
for (const s of shots.filter(x => x.group === 'bg')) {
|
||||
let src = null;
|
||||
if (s.adopt) src = join(BATCH1, s.adopt);
|
||||
else for (const ext of ['.jpg', '.jpeg', '.png']) { const p = join(RAW, s.id + ext); if (existsSync(p)) { src = p; break; } }
|
||||
if (!src || !existsSync(src)) {
|
||||
// 缺源(gen 原片在已清工作区)时继承盘上既有 bg 产物,不整写丢账
|
||||
const prev = prevManifest?.bg?.find(b => b.id === s.id);
|
||||
const kept = prev && existsSync(join(GAME, prev.file));
|
||||
if (kept) {
|
||||
prev.bytes = statSync(join(GAME, prev.file)).size;
|
||||
manifest.bytes.bgWebp += prev.bytes;
|
||||
manifest.bg.push(prev);
|
||||
console.log(`[bg] ${s.id} 缺源,继承既有产物(${(prev.bytes / 1024).toFixed(0)}KB)`);
|
||||
} else console.log(`[bg] ${s.id} 缺源,跳过`);
|
||||
continue;
|
||||
}
|
||||
const dest = join(BG_DIR, `${s.id}.webp`);
|
||||
toWebp(src, dest, 72); // 整幅底图走 q72:全屏铺底件,细节容忍度高于精灵
|
||||
const bytes = statSync(dest).size;
|
||||
manifest.bytes.bgWebp += bytes;
|
||||
manifest.bg.push({ id: s.id, file: `bg/${s.id}.webp`, bytes, adopted: !!s.adopt });
|
||||
console.log(`[bg] ${s.id} → webp ${(bytes / 1024).toFixed(0)}KB${s.adopt ? '(收编批次一)' : ''}`);
|
||||
}
|
||||
|
||||
// ---- ③ tiles 几何登记(产物由 asset-tiles.py 生成)----
|
||||
const tilesWebp = join(GAME, 'assets', 'tiles', 'tiles-map1.webp');
|
||||
if (existsSync(tilesWebp)) {
|
||||
const bytes = statSync(tilesWebp).size;
|
||||
manifest.tiles = { file: 'tiles/tiles-map1.webp', tileSize: 128, cols: 8, rows: 6,
|
||||
order: 'a=0-15 基底 / b=16-31 暗变奏 / c=32-47 刻痕变奏(索引=行×8+列)', bytes };
|
||||
manifest.bytes.tilesWebp = bytes;
|
||||
}
|
||||
|
||||
manifest.bytes.total = manifest.bytes.atlasWebp + manifest.bytes.tilesWebp + manifest.bytes.bgWebp;
|
||||
writeFileSync(join(GAME, 'assets', 'manifest.json'), JSON.stringify(manifest, null, 2));
|
||||
console.log(`\nassets/manifest.json 已写:图集 ${(manifest.bytes.atlasWebp / 1024).toFixed(0)}KB + tiles ${(manifest.bytes.tilesWebp / 1024).toFixed(0)}KB + bg ${(manifest.bytes.bgWebp / 1024).toFixed(0)}KB = 合计 ${(manifest.bytes.total / 1024).toFixed(0)}KB(webp,两本账实测输入)`);
|
||||
109
game-runtime/games/shanhai-xingji/scripts/asset-check.mjs
Normal file
@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env node
|
||||
// 《山海行纪》资产批产 · 机检门(纪律:每批机检 尺寸/透明底/命名,不过门不入图集账)
|
||||
// 检查面:
|
||||
// C1 命名:全部镜头 id 符合 role-state-frame(^[a-z0-9]+(-[a-z0-9]+)*-\d{2}$);
|
||||
// C2 keyed 覆盖:sprite 镜头逐一有 keyed png,尺寸 ≤ pack 上限;
|
||||
// C3 透明底:keyed png 为 RGBA 且边框 alpha≈0、内容非空(python/PIL 批查,单进程);
|
||||
// C4 图集对账:每个 sprite 镜头的 id 在其分组图集 json 里恰有一帧;页尺寸 ≤2048²;
|
||||
// C5 产物在位:tiles 表几何正确(1024×768)、bg webp 在位、assets/manifest.json 字节账与实盘一致。
|
||||
// 用法: node scripts/asset-check.mjs [--stage keyed|full](默认 full;keyed=生图后图集前的中检)
|
||||
import { readFileSync, writeFileSync, readdirSync, existsSync, statSync } from 'node:fs';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { dirname, join, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const GAME = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const WORK = process.env.ASSET_WORK || join(tmpdir(), 'xingji-a1-work');
|
||||
const KEYED = join(WORK, 'keyed');
|
||||
const stage = process.argv.includes('--stage') ? process.argv[process.argv.indexOf('--stage') + 1] : 'full';
|
||||
const { shots } = JSON.parse(readFileSync(join(GAME, 'scripts', 'asset-shots.json'), 'utf8'));
|
||||
|
||||
const errs = [], warns = [];
|
||||
const sprites = shots.filter(s => s.type === 'sprite');
|
||||
|
||||
// C1 命名纪律
|
||||
const NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*-\d{2}$/;
|
||||
for (const s of shots) if (!NAME_RE.test(s.id)) errs.push(`C1 命名违例:${s.id}`);
|
||||
|
||||
// C2 keyed 覆盖 + 尺寸上限(PNG IHDR 直读宽高,无依赖)
|
||||
function pngInfo(p) {
|
||||
const b = readFileSync(p);
|
||||
return { w: b.readUInt32BE(16), h: b.readUInt32BE(20), colorType: b[25] };
|
||||
}
|
||||
// keyed 中间件只活在批产工作区(ASSET_WORK,不入仓):整目录缺席=从仓库态复跑(CI/验收),
|
||||
// C2/C3 是生产期中检、无对象可查,降级为警示跳过;部分缺席才是真不一致,仍判错。
|
||||
const keyedPaths = {};
|
||||
const keyedAbsent = !existsSync(KEYED);
|
||||
if (keyedAbsent) warns.push('C2/C3 跳过:keyed 工作区不在(生产期中检项;仓库态审计走 C1/C4/C5)');
|
||||
for (const s of keyedAbsent ? [] : sprites) {
|
||||
const p = join(KEYED, s.group, `${s.id}.png`);
|
||||
if (!existsSync(p)) { errs.push(`C2 缺 keyed:${s.group}/${s.id}.png`); continue; }
|
||||
keyedPaths[s.id] = p;
|
||||
const { w, h, colorType } = pngInfo(p);
|
||||
if (s.pack && Math.max(w, h) > s.pack) errs.push(`C2 超 pack 上限:${s.id} ${w}x${h} > ${s.pack}`);
|
||||
if (colorType !== 6) errs.push(`C3 非 RGBA(colorType=${colorType}):${s.id}`);
|
||||
}
|
||||
|
||||
// C3 透明底与内容非空(单个 python 进程批查全部 keyed)
|
||||
if (Object.keys(keyedPaths).length) {
|
||||
const py = `
|
||||
import sys, json
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
out = {}
|
||||
for sid, p in json.load(open(sys.argv[1])).items():
|
||||
a = np.asarray(Image.open(p).convert('RGBA'))[:, :, 3]
|
||||
border = np.concatenate([a[0, :], a[-1, :], a[:, 0], a[:, -1]]).astype(float)
|
||||
out[sid] = { 'borderMax': int(border.max()), 'opaquePct': float((a > 16).mean()) }
|
||||
print(json.dumps(out))`;
|
||||
const tmpJson = join(WORK, 'check-keyed.json');
|
||||
writeFileSync(tmpJson, JSON.stringify(keyedPaths));
|
||||
const res = JSON.parse(execFileSync('python3', ['-c', py, tmpJson], { encoding: 'utf8', timeout: 120_000 }));
|
||||
for (const [sid, r] of Object.entries(res)) {
|
||||
if (r.borderMax > 48) warns.push(`C3 边框 alpha 偏高(${r.borderMax}):${sid}(主体可能贴边/裁切)`);
|
||||
if (r.opaquePct < 0.02) errs.push(`C3 内容近空(不透明占比 ${(r.opaquePct * 100).toFixed(1)}%):${sid}`);
|
||||
if (r.opaquePct > 0.98) warns.push(`C3 几乎无透明(占比 ${(r.opaquePct * 100).toFixed(1)}%):${sid}(疑似抠图失败整幅保留)`);
|
||||
}
|
||||
}
|
||||
|
||||
if (stage === 'full') {
|
||||
// C4 图集对账
|
||||
const ATLAS_DIR = join(GAME, 'assets', 'atlas');
|
||||
const frameOwner = {};
|
||||
if (existsSync(ATLAS_DIR)) {
|
||||
for (const f of readdirSync(ATLAS_DIR).filter(f => f.endsWith('.json'))) {
|
||||
const j = JSON.parse(readFileSync(join(ATLAS_DIR, f), 'utf8'));
|
||||
for (const t of j.textures || []) {
|
||||
if (t.size && (t.size.w > 2048 || t.size.h > 2048)) errs.push(`C4 页超 2048²:${f} ${t.size.w}x${t.size.h}`);
|
||||
for (const fr of t.frames || []) frameOwner[fr.filename] = (frameOwner[fr.filename] || 0) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const s of sprites) {
|
||||
const n = frameOwner[s.id] || 0;
|
||||
if (n === 0) errs.push(`C4 图集缺帧:${s.id}`);
|
||||
else if (n > 1) errs.push(`C4 帧重复 ${n} 处:${s.id}`);
|
||||
}
|
||||
// C5 产物在位 + 字节账对账
|
||||
const tiles = join(GAME, 'assets', 'tiles', 'tiles-map1.webp');
|
||||
if (!existsSync(tiles)) errs.push('C5 缺 tiles-map1.webp');
|
||||
for (const s of shots.filter(x => x.group === 'bg')) {
|
||||
if (!existsSync(join(GAME, 'assets', 'bg', `${s.id}.webp`))) errs.push(`C5 缺 bg:${s.id}.webp`);
|
||||
}
|
||||
const mfPath = join(GAME, 'assets', 'manifest.json');
|
||||
if (!existsSync(mfPath)) errs.push('C5 缺 assets/manifest.json');
|
||||
else {
|
||||
const mf = JSON.parse(readFileSync(mfPath, 'utf8'));
|
||||
let actual = 0;
|
||||
for (const pages of Object.values(mf.atlases || {})) for (const p of pages) actual += statSync(join(ATLAS_DIR, p.page)).size;
|
||||
if (mf.tiles && existsSync(tiles)) actual += statSync(tiles).size;
|
||||
for (const b of mf.bg || []) actual += statSync(join(GAME, 'assets', b.file)).size;
|
||||
if (actual !== mf.bytes.total) errs.push(`C5 字节账不符:manifest ${mf.bytes.total} vs 实盘 ${actual}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const w of warns) console.log(`⚠ ${w}`);
|
||||
for (const e of errs) console.error(`✗ ${e}`);
|
||||
console.log(`\n机检(${stage}):${errs.length === 0 ? '全绿' : errs.length + ' 项不过'} / 警示 ${warns.length} 项 / sprite ${sprites.length} 镜头`);
|
||||
process.exit(errs.length ? 1 : 0);
|
||||
@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python3
|
||||
# 《山海行纪》资产批产 · 拼板图(迁移自批次一 contact-sheet.py;眼验入口)
|
||||
# 用法: python3 scripts/asset-contact-sheet.py <输出.png> [--prio P1] [--group beasts] [--cols 8] [--title 标题] [--raw]
|
||||
# python3 scripts/asset-contact-sheet.py --files <输出.png> <标题> <标签=路径>...
|
||||
# 默认取 keyed 后精灵(所见即入图集帧);--raw 取生图原片;bleed 镜头(tiles/bg)始终取原片。
|
||||
# 中文标题字体:环境变量 FONT_TTF 或批次一同款霞鹜文楷(OFL)。
|
||||
import sys, os, json, tempfile
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
GAME = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
WORK = os.environ.get('ASSET_WORK') or os.path.join(tempfile.gettempdir(), 'xingji-a1-work')
|
||||
BATCH1 = os.path.normpath(os.path.join(GAME, '..', '..', '..', 'docs-design', '山海风格版-批次一'))
|
||||
FONT_TTF = os.environ.get('FONT_TTF') or '/private/tmp/claude-501/-Users-lili-Project-games-development-ai/69248bd4-16ac-4906-aea1-4157edf7b10f/scratchpad/deps/LXGWWenKai-Regular.ttf'
|
||||
|
||||
def load_font(size):
|
||||
try: return ImageFont.truetype(FONT_TTF, size)
|
||||
except Exception: return ImageFont.load_default()
|
||||
|
||||
def make_sheet(items, out_path, cols, title, tile_w=300, tile_h=330):
|
||||
label_h, title_h, gap = 26, 52, 8
|
||||
rows = (len(items) + cols - 1) // cols
|
||||
W = cols * (tile_w + gap) + gap
|
||||
H = title_h + rows * (tile_h + label_h + gap) + gap
|
||||
sheet = Image.new('RGB', (W, H), (30, 34, 32))
|
||||
d = ImageDraw.Draw(sheet)
|
||||
d.text((gap + 4, 10), title, fill=(232, 197, 106), font=load_font(28))
|
||||
f_label = load_font(17)
|
||||
for i, (label, path) in enumerate(items):
|
||||
r, c = divmod(i, cols)
|
||||
x = gap + c * (tile_w + gap)
|
||||
y = title_h + r * (tile_h + label_h + gap)
|
||||
try:
|
||||
im = Image.open(path)
|
||||
if im.mode == 'RGBA': # 透明底铺浅格,眼验抠图质量
|
||||
bgc = Image.new('RGB', im.size, (58, 62, 58))
|
||||
bgc.paste(im, (0, 0), im)
|
||||
im = bgc
|
||||
else:
|
||||
im = im.convert('RGB')
|
||||
except Exception as e:
|
||||
d.text((x, y), f'{label}: 打开失败 {e}', fill=(200, 80, 80), font=f_label); continue
|
||||
im.thumbnail((tile_w, tile_h))
|
||||
sheet.paste(im, (x + (tile_w - im.width) // 2, y + (tile_h - im.height) // 2))
|
||||
d.rectangle([x, y + tile_h, x + tile_w, y + tile_h + label_h], fill=(20, 23, 21))
|
||||
d.text((x + 5, y + tile_h + 3), label, fill=(220, 220, 210), font=f_label)
|
||||
sheet.save(out_path)
|
||||
print(f'拼板完成:{out_path} ({W}x{H}, {len(items)} 格, {os.path.getsize(out_path)//1024}KB)')
|
||||
|
||||
def locate(shot, prefer_raw):
|
||||
"""镜头 → 现存图:keyed 优先(sprite),bleed/raw 回原片,adopt 回批次一源。"""
|
||||
cands = []
|
||||
if shot['type'] == 'sprite' and not prefer_raw:
|
||||
cands.append(os.path.join(WORK, 'keyed', shot['group'], shot['id'] + '.png'))
|
||||
for ext in ('.jpg', '.jpeg', '.png', '.webp'):
|
||||
cands.append(os.path.join(WORK, 'raw', shot['id'] + ext))
|
||||
if shot.get('adopt'):
|
||||
cands.append(os.path.join(BATCH1, shot['adopt']))
|
||||
for p in cands:
|
||||
if os.path.exists(p): return p
|
||||
return None
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1 and sys.argv[1] == '--files':
|
||||
out, title = sys.argv[2], sys.argv[3]
|
||||
items = [(kv.split('=', 1)[0], kv.split('=', 1)[1]) for kv in sys.argv[4:]]
|
||||
make_sheet(items, out, cols=min(6, max(1, len(items))), title=title)
|
||||
sys.exit(0)
|
||||
out = sys.argv[1]
|
||||
argv = sys.argv[2:]
|
||||
prio = group = None; cols = 8; title = '山海行纪 · 资产一期'; raw = False
|
||||
i = 0
|
||||
while i < len(argv):
|
||||
if argv[i] == '--prio': prio = argv[i+1]; i += 2
|
||||
elif argv[i] == '--group': group = argv[i+1]; i += 2
|
||||
elif argv[i] == '--cols': cols = int(argv[i+1]); i += 2
|
||||
elif argv[i] == '--title': title = argv[i+1]; i += 2
|
||||
elif argv[i] == '--raw': raw = True; i += 1
|
||||
else: sys.exit(f'未知参数 {argv[i]}')
|
||||
shots = json.load(open(os.path.join(GAME, 'scripts', 'asset-shots.json'), encoding='utf-8'))['shots']
|
||||
items, missing = [], []
|
||||
for s in shots:
|
||||
if prio and s['prio'] != prio: continue
|
||||
if group and s['group'] != group: continue
|
||||
p = locate(s, raw)
|
||||
if p: items.append((s['id'], p))
|
||||
else: missing.append(s['id'])
|
||||
if missing: print(f"缺图 {len(missing)}:{','.join(missing)}")
|
||||
make_sheet(items, out, cols=cols, title=title)
|
||||
115
game-runtime/games/shanhai-xingji/scripts/asset-gen.mjs
Normal file
@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env node
|
||||
// 《山海行纪》资产批产 · 生图批跑(迁移自 docs-design/山海风格版-批次一/scripts/gen-batch.mjs)
|
||||
// 用法:
|
||||
// node scripts/asset-gen.mjs --prio P1 # 按优先级批跑(跳过已存在,可断点续跑)
|
||||
// node scripts/asset-gen.mjs --group beasts # 只跑一个消费分组
|
||||
// node scripts/asset-gen.mjs --only zheng-move-01 --seed-bump 100000 --force # 单镜头重摇
|
||||
// node scripts/asset-gen.mjs --only tile-a-00 --subject-extra ",定向补词" --force
|
||||
// 环境变量 ASSET_WORK=工作目录(默认系统临时目录 xingji-a1-work;raw/ 放生图原片)
|
||||
// 纪律(承批次一,逐条生效):
|
||||
// - styleWords 单源 = 批次一 anchor-pack.json(r1),运行时拼接,绝不在本仓复制一份锚;
|
||||
// - prompt = promptPrefix + 镜头 subject + styleWordsShared + perGame.xingji(sprite|bleed) + 后缀,原样入账;
|
||||
// - 每次外呼 150s 超时,失败退避重试 ≤3(内容过滤 exit=10 不重试,配额 exit=4 全批中止);
|
||||
// - 每次调用成败都追加批次一 manifest.jsonl 一行(batch=xingji-a1),逐张 prompt/参数/耗时可审计;
|
||||
// - adopt 收编镜头(批次一定稿样张直接入产线)不外呼,由 asset-key.py 直接取源;
|
||||
// - 串行 + 1s 间隔防限流;--subject-ref 仅当镜头带 ref 字段(创始人拍的定妆帧参考图工艺)。
|
||||
import { readFileSync, existsSync, mkdirSync, renameSync, appendFileSync, readdirSync, rmSync } from 'node:fs';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { dirname, join, resolve, extname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const GAME = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const BATCH1 = resolve(GAME, '..', '..', '..', 'docs-design', '山海风格版-批次一'); // 锚包与台账单源
|
||||
const WORK = process.env.ASSET_WORK || join(tmpdir(), 'xingji-a1-work');
|
||||
const RAW = join(WORK, 'raw');
|
||||
const MANIFEST = join(BATCH1, 'manifest.jsonl');
|
||||
const TMP = join(WORK, 'tmp-gen');
|
||||
mkdirSync(RAW, { recursive: true });
|
||||
mkdirSync(TMP, { recursive: true });
|
||||
|
||||
// ---- 参数解析 ----
|
||||
const args = process.argv.slice(2);
|
||||
const getArg = (name) => { const i = args.indexOf(name); return i >= 0 ? args[i + 1] : undefined; };
|
||||
const onlyIds = getArg('--only')?.split(',').map(s => s.trim()).filter(Boolean);
|
||||
const onlyGroup = getArg('--group');
|
||||
const onlyPrio = getArg('--prio');
|
||||
const seedBump = Number(getArg('--seed-bump') || 0);
|
||||
const subjectExtra = getArg('--subject-extra') || '';
|
||||
const force = args.includes('--force');
|
||||
|
||||
// ---- 读锚包(单源)与镜头单 ----
|
||||
const anchor = JSON.parse(readFileSync(join(BATCH1, 'anchor-pack.json'), 'utf8'));
|
||||
const { shots, batch } = JSON.parse(readFileSync(join(GAME, 'scripts', 'asset-shots.json'), 'utf8'));
|
||||
|
||||
// prompt 拼装(锚包 r1 口径,与批次一 gen-batch 完全同构;行纪固定取 perGame.xingji)
|
||||
function buildPrompt(shot) {
|
||||
const kind = shot.type === 'sprite' ? 'sprite' : 'bleed';
|
||||
const per = anchor.perGame.xingji[kind];
|
||||
const suffix = shot.type === 'sprite' ? anchor.spriteSuffix : anchor.fullBleedSuffix;
|
||||
return `${anchor.promptPrefix}${shot.subject}${subjectExtra}。${anchor.styleWordsShared}。${per}。${suffix}`;
|
||||
}
|
||||
|
||||
// 追加台账一行(逐次调用,成败都记;续写批次一 manifest.jsonl)
|
||||
function ledger(entry) {
|
||||
appendFileSync(MANIFEST, JSON.stringify({ ts: new Date().toISOString(), batch, ...entry }) + '\n');
|
||||
}
|
||||
|
||||
// 调 mmx 一次(150s 超时);返回 { ok, file?, error?, ms, exit }
|
||||
function callMmx(shot, prompt, seedUsed) {
|
||||
for (const f of readdirSync(TMP)) if (f.startsWith(shot.id)) rmSync(join(TMP, f)); // 防串档
|
||||
const cli = ['image', 'generate', '--prompt', prompt,
|
||||
'--width', String(shot.w), '--height', String(shot.h),
|
||||
'--seed', String(seedUsed), '--out-dir', TMP, '--out-prefix', shot.id,
|
||||
'--quiet', '--non-interactive'];
|
||||
if (shot.ref) cli.push('--subject-ref', `type=character,image=${resolve(BATCH1, shot.ref)}`);
|
||||
const t0 = Date.now();
|
||||
const r = spawnSync('mmx', cli, { timeout: 150_000, encoding: 'utf8' });
|
||||
const ms = Date.now() - t0;
|
||||
if (r.error?.code === 'ETIMEDOUT' || r.signal) return { ok: false, error: 'timeout-150s', ms, exit: 5 };
|
||||
if (r.status !== 0) return { ok: false, error: (r.stderr || r.stdout || '').trim().slice(0, 300) || `exit=${r.status}`, ms, exit: r.status ?? 1 };
|
||||
const saved = (r.stdout || '').split('\n').map(s => s.trim()).find(s => s && existsSync(s))
|
||||
|| readdirSync(TMP).filter(f => f.startsWith(shot.id)).map(f => join(TMP, f))[0];
|
||||
if (!saved) return { ok: false, error: 'exit=0 但未找到产物文件', ms, exit: 1 };
|
||||
return { ok: true, file: saved, ms, exit: 0 };
|
||||
}
|
||||
|
||||
// ---- 主循环:串行 ----
|
||||
const todo = shots.filter(s => !s.adopt
|
||||
&& (!onlyIds || onlyIds.includes(s.id))
|
||||
&& (!onlyGroup || s.group === onlyGroup)
|
||||
&& (!onlyPrio || s.prio === onlyPrio));
|
||||
let done = 0, skipped = 0, failed = 0, calls = 0;
|
||||
for (const shot of todo) {
|
||||
const existing = readdirSync(RAW).find(f => f.startsWith(shot.id + '.'));
|
||||
if (existing && !force) { skipped++; continue; }
|
||||
|
||||
const prompt = buildPrompt(shot);
|
||||
const seedUsed = (shot.seed || 0) + seedBump;
|
||||
let result = null;
|
||||
for (let attempt = 1; attempt <= 3; attempt++) {
|
||||
result = callMmx(shot, prompt, seedUsed);
|
||||
calls++;
|
||||
ledger({ kind: 'gen-call', id: shot.id, game: 'xingji', prio: shot.prio, group: shot.group,
|
||||
type: shot.type, w: shot.w, h: shot.h, seed: seedUsed, seedBump, attempt,
|
||||
model: 'image-01', ref: shot.ref || undefined, prompt,
|
||||
status: result.ok ? 'ok' : 'fail', error: result.error, durationMs: result.ms });
|
||||
if (result.ok) break;
|
||||
if (result.exit === 10) { console.error(`[${shot.id}] 内容过滤(exit=10),不重试`); break; }
|
||||
if (result.exit === 4) { console.error(`[${shot.id}] 配额超限(exit=4),全批中止`); process.exit(4); }
|
||||
if (attempt < 3) { const wait = attempt * 5000; console.error(`[${shot.id}] 第${attempt}次失败(${result.error}),${wait / 1000}s 后重试`); spawnSync('sleep', [String(wait / 1000)]); }
|
||||
}
|
||||
if (result.ok) {
|
||||
const ext = extname(result.file) || '.png';
|
||||
const dest = join(RAW, shot.id + ext.toLowerCase());
|
||||
renameSync(result.file, dest);
|
||||
done++;
|
||||
console.log(`[${shot.id}] OK ${Math.round(result.ms / 1000)}s -> ${dest}`);
|
||||
} else {
|
||||
failed++;
|
||||
console.error(`[${shot.id}] FAILED: ${result.error}`);
|
||||
}
|
||||
spawnSync('sleep', ['1']); // 限流间隔
|
||||
}
|
||||
console.log(`\n批跑完成:成 ${done} / 跳过 ${skipped} / 败 ${failed};本次外呼 ${calls} 次(≈¥${(calls * 0.033).toFixed(2)} 仓内实测口径)`);
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
165
game-runtime/games/shanhai-xingji/scripts/asset-key.py
Normal file
@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env python3
|
||||
# 《山海行纪》资产批产 · 色键抠图批处理(迁移自批次一 key-out.py,算法同源)
|
||||
# 职责:镜头单里全部 sprite 镜头 → 透明底精灵 png,按消费分组落 keyed/{group}/{id}.png。
|
||||
# - gen 镜头取 ASSET_WORK/raw/{id}.jpg;adopt 收编镜头直接取批次一定稿样张(不外呼);
|
||||
# - 每张:四角采样定底色 → 双阈值 alpha → 连通域除杂(印章/落款/尘点)→ 裁切留边
|
||||
# → 可选镜像归一(flip:统一面朝右)→ 内容盒缩到 pack 上限(入图集前定尺);
|
||||
# - bleed 类(tiles/bg)不在本stage处理(不抠图),归 asset-tiles.py / asset-atlas.mjs。
|
||||
# 用法: python3 scripts/asset-key.py [--only id1,id2] [--prio P1] [--group beasts] [--force]
|
||||
# [--tol 28] [--soft 52] [--pad 8] (环境变量 ASSET_WORK 同 asset-gen.mjs)
|
||||
import sys, os, json, tempfile
|
||||
from collections import deque
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
GAME = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
BATCH1 = os.path.normpath(os.path.join(GAME, '..', '..', '..', 'docs-design', '山海风格版-批次一'))
|
||||
WORK = os.environ.get('ASSET_WORK') or os.path.join(tempfile.gettempdir(), 'xingji-a1-work')
|
||||
RAW, KEYED = os.path.join(WORK, 'raw'), os.path.join(WORK, 'keyed')
|
||||
|
||||
def parse_args(argv):
|
||||
opt = {'only': None, 'prio': None, 'group': None, 'force': False, 'tol': 28.0, 'soft': 52.0, 'pad': 8}
|
||||
i = 0
|
||||
while i < len(argv):
|
||||
a = argv[i]
|
||||
if a == '--only': opt['only'] = set(argv[i+1].split(',')); i += 2
|
||||
elif a == '--prio': opt['prio'] = argv[i+1]; i += 2
|
||||
elif a == '--group': opt['group'] = argv[i+1]; i += 2
|
||||
elif a == '--force': opt['force'] = True; i += 1
|
||||
elif a in ('--tol', '--soft'): opt[a[2:]] = float(argv[i+1]); i += 2
|
||||
elif a == '--pad': opt['pad'] = int(argv[i+1]); i += 2
|
||||
else: sys.exit(f'未知参数 {a}')
|
||||
return opt
|
||||
|
||||
def dilate(mask, it):
|
||||
# 简易方块膨胀:把被丢弃域向外扩几圈,连羽化边一起清
|
||||
m = mask.copy()
|
||||
for _ in range(it):
|
||||
m = m | np.roll(m, 1, 0) | np.roll(m, -1, 0) | np.roll(m, 1, 1) | np.roll(m, -1, 1)
|
||||
return m
|
||||
|
||||
def key_one(src, tol, soft, pad):
|
||||
"""色键抠图核(与批次一 key-out.py 同源):返回 RGBA ndarray 或 None。"""
|
||||
im = Image.open(src).convert('RGB')
|
||||
a = np.asarray(im, dtype=np.float32)
|
||||
h, w, _ = a.shape
|
||||
c = 12 # ① 四角 12px 块中位数 = 背景色
|
||||
corners = np.concatenate([a[:c, :c].reshape(-1, 3), a[:c, -c:].reshape(-1, 3),
|
||||
a[-c:, :c].reshape(-1, 3), a[-c:, -c:].reshape(-1, 3)])
|
||||
bg = np.median(corners, axis=0)
|
||||
dist = np.sqrt(((a - bg) ** 2).sum(axis=2))
|
||||
alpha = np.clip((dist - tol) / max(soft - tol, 1e-6), 0.0, 1.0) # ② 双阈值羽化
|
||||
s = 4 # ③ 1/4 尺度连通域除杂
|
||||
fg = (dist > (tol + soft) / 2)[::s, ::s]
|
||||
H, W = fg.shape
|
||||
lab = np.zeros(fg.shape, dtype=np.int32)
|
||||
sizes, boxes, nid = {}, {}, 0
|
||||
for y in range(H):
|
||||
for x in range(W):
|
||||
if fg[y, x] and lab[y, x] == 0:
|
||||
nid += 1
|
||||
q = deque([(y, x)]); lab[y, x] = nid
|
||||
n, y0, y1, x0, x1 = 0, y, y, x, x
|
||||
while q:
|
||||
cy, cx = q.popleft(); n += 1
|
||||
y0, y1, x0, x1 = min(y0, cy), max(y1, cy), min(x0, cx), max(x1, cx)
|
||||
for dy, dx in ((1, 0), (-1, 0), (0, 1), (0, -1)):
|
||||
ny, nx = cy + dy, cx + dx
|
||||
if 0 <= ny < H and 0 <= nx < W and fg[ny, nx] and lab[ny, nx] == 0:
|
||||
lab[ny, nx] = nid; q.append((ny, nx))
|
||||
sizes[nid], boxes[nid] = n, (y0, y1, x0, x1)
|
||||
if not sizes:
|
||||
return None, bg, 0
|
||||
main = max(sizes, key=sizes.get)
|
||||
my0, my1, mx0, mx1 = boxes[main]
|
||||
grow = max(H, W) // 20 # 主体框外扩 5%:贴身小件(飘带/火星/链梢)保留
|
||||
keep = set()
|
||||
for i_, n in sizes.items():
|
||||
by0, by1, bx0, bx1 = boxes[i_]
|
||||
near = not (bx1 < mx0 - grow or bx0 > mx1 + grow or by1 < my0 - grow or by0 > my1 + grow)
|
||||
if i_ == main or (near and n >= 0.03 * sizes[main]) or n >= 0.20 * sizes[main]:
|
||||
keep.add(i_)
|
||||
dropped = len(sizes) - len(keep)
|
||||
drop_small = fg & ~np.isin(lab, list(keep))
|
||||
drop_big = np.kron(dilate(drop_small, 2), np.ones((s, s), dtype=bool))[:h, :w]
|
||||
alpha[drop_big] = 0.0
|
||||
ys, xs = np.nonzero(alpha > 0.02) # ④ 内容裁切 + 留边
|
||||
if len(ys) == 0:
|
||||
return None, bg, dropped
|
||||
y0, y1 = max(int(ys.min()) - pad, 0), min(int(ys.max()) + pad + 1, h)
|
||||
x0, x1 = max(int(xs.min()) - pad, 0), min(int(xs.max()) + pad + 1, w)
|
||||
rgba = np.dstack([a.astype(np.uint8), (alpha * 255).astype(np.uint8)])[y0:y1, x0:x1]
|
||||
return rgba, bg, dropped
|
||||
|
||||
def solid_interior(rgba):
|
||||
"""封闭内域回填:UI 框件的中央空白区在色键下会被啃成噪点。
|
||||
从边界对「透明域」洪泛,凡不与边界连通的透明像素判为框内封闭区,回填原色为实心
|
||||
(卡面/槽面得到可承程序排字的实底)。"""
|
||||
a = rgba[:, :, 3]
|
||||
h, w = a.shape
|
||||
outside = np.zeros((h, w), dtype=bool)
|
||||
trans = a < 250 # 半透噪点一并纳入;外缘羽化与外界连通、不受回填影响
|
||||
q = deque()
|
||||
for x in range(w):
|
||||
for y in (0, h - 1):
|
||||
if trans[y, x] and not outside[y, x]: outside[y, x] = True; q.append((y, x))
|
||||
for y in range(h):
|
||||
for x in (0, w - 1):
|
||||
if trans[y, x] and not outside[y, x]: outside[y, x] = True; q.append((y, x))
|
||||
while q:
|
||||
cy, cx = q.popleft()
|
||||
for dy, dx in ((1, 0), (-1, 0), (0, 1), (0, -1)):
|
||||
ny, nx = cy + dy, cx + dx
|
||||
if 0 <= ny < h and 0 <= nx < w and trans[ny, nx] and not outside[ny, nx]:
|
||||
outside[ny, nx] = True; q.append((ny, nx))
|
||||
interior = trans & ~outside
|
||||
rgba[:, :, 3] = np.where(interior, 255, a)
|
||||
return rgba, int(interior.sum())
|
||||
|
||||
def find_src(shot):
|
||||
"""定位镜头源图:adopt→批次一定稿;gen→工作区 raw/。"""
|
||||
if shot.get('adopt'):
|
||||
p = os.path.join(BATCH1, shot['adopt'])
|
||||
return p if os.path.exists(p) else None
|
||||
for ext in ('.jpg', '.jpeg', '.png', '.webp'):
|
||||
p = os.path.join(RAW, shot['id'] + ext)
|
||||
if os.path.exists(p): return p
|
||||
return None
|
||||
|
||||
if __name__ == '__main__':
|
||||
opt = parse_args(sys.argv[1:])
|
||||
shots = json.load(open(os.path.join(GAME, 'scripts', 'asset-shots.json'), encoding='utf-8'))['shots']
|
||||
todo = [s for s in shots if s['type'] == 'sprite'
|
||||
and (not opt['only'] or s['id'] in opt['only'])
|
||||
and (not opt['prio'] or s['prio'] == opt['prio'])
|
||||
and (not opt['group'] or s['group'] == opt['group'])]
|
||||
ok, miss, skip = 0, [], 0
|
||||
for s in todo:
|
||||
out_dir = os.path.join(KEYED, s['group'])
|
||||
out = os.path.join(out_dir, f"{s['id']}.png")
|
||||
if os.path.exists(out) and not opt['force']:
|
||||
skip += 1; continue
|
||||
src = find_src(s)
|
||||
if not src:
|
||||
miss.append(s['id']); print(f"[{s['id']}] 缺源图(未生成或收编源不存在),跳过"); continue
|
||||
rgba, bg, dropped = key_one(src, opt['tol'], opt['soft'], opt['pad'])
|
||||
if rgba is None:
|
||||
miss.append(s['id']); print(f"[{s['id']}] 警告:未检出前景,跳过"); continue
|
||||
filled = 0
|
||||
if s.get('solidInterior'): # UI 框件:封闭内域回填为实心
|
||||
rgba, filled = solid_interior(rgba)
|
||||
im = Image.fromarray(rgba, 'RGBA')
|
||||
if s.get('flip'): # 镜像归一:帧集统一面朝画面右方
|
||||
im = im.transpose(Image.FLIP_LEFT_RIGHT)
|
||||
cap = s.get('pack')
|
||||
if cap and max(im.size) > cap: # 内容盒定尺:入图集前缩到消费尺寸
|
||||
r = cap / max(im.size)
|
||||
im = im.resize((max(1, round(im.width * r)), max(1, round(im.height * r))), Image.LANCZOS)
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
im.save(out)
|
||||
ok += 1
|
||||
print(f"[{s['id']}] 底色 rgb({int(bg[0])},{int(bg[1])},{int(bg[2])}) 丢杂 {dropped}"
|
||||
+ (f" 内域回填 {filled}px" if filled else '')
|
||||
+ f" → {im.width}x{im.height}{'(flip)' if s.get('flip') else ''} {os.path.getsize(out)//1024}KB")
|
||||
print(f"\n抠图完成:成 {ok} / 跳过 {skip} / 缺源 {len(miss)}" + (f":{','.join(miss)}" if miss else ''))
|
||||
sys.exit(1 if miss else 0)
|
||||
1362
game-runtime/games/shanhai-xingji/scripts/asset-shots.json
Normal file
93
game-runtime/games/shanhai-xingji/scripts/asset-tiles.py
Normal file
@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
# 《山海行纪》资产批产 · 图1 tileset 合成(生图满幅地表 → 定格网格 tile 表)
|
||||
# 思路:三张 1024² 满幅地表纹理(a 基底/b 暗变奏/c 刻痕变奏)各缩到 512² 后切 4×4=16 块
|
||||
# 128px tile,按 a→b→c 顺序排进 8 列 × 6 行的单张 1024×768 tile 表——同表切块天然可
|
||||
# 互拼(纹理连续来自同一幅画),tile 索引 = 行×8+列,LittleJS tile(index,128) 直接消费。
|
||||
# 用法: python3 scripts/asset-tiles.py (环境变量 ASSET_WORK 同 asset-gen.mjs)
|
||||
# 产物: assets/tiles/tiles-map1.webp(q80,进包体账)+ 工作区 png 开发查看副本。
|
||||
import os, sys, tempfile
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
GAME = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
WORK = os.environ.get('ASSET_WORK') or os.path.join(tempfile.gettempdir(), 'xingji-a1-work')
|
||||
RAW = os.path.join(WORK, 'raw')
|
||||
OUT_DIR = os.path.join(GAME, 'assets', 'tiles')
|
||||
TILE, COLS = 128, 8
|
||||
SHEETS = ['tile-a-00', 'tile-b-00', 'tile-c-00'] # 与 asset-shots.json 的 tiles 组对齐
|
||||
INSET = 24 # 内缩裁切:去掉生图四周细框线,再整幅重铺
|
||||
|
||||
|
||||
def patch_seals(im):
|
||||
"""印章修补:mmx 国风生图系统性伴生红印(批次一工艺发现3)。
|
||||
tiles 不走连通域除杂,这里按朱砂色域逐连通域检出印章块(一图可能多枚),
|
||||
每枚用画面中心区域同尺寸纹理覆盖;大面积红(画面本身偏红)不动。"""
|
||||
a = np.asarray(im, dtype=np.int16)
|
||||
r, g, b = a[:, :, 0], a[:, :, 1], a[:, :, 2]
|
||||
seal = (r > 110) & (r - g > 60) & (r - b > 55) & (g < 90) # 朱砂印泥红:R 显著高、G 压低(区别于赭金斑)
|
||||
if seal.mean() > 0.05:
|
||||
return im, 0
|
||||
# 1/4 尺度 BFS 分岛:多枚印章逐一修,避免并框误判
|
||||
s = 4
|
||||
m4 = seal[::s, ::s].copy()
|
||||
H, W = m4.shape
|
||||
patched = 0
|
||||
from collections import deque
|
||||
for y in range(H):
|
||||
for x in range(W):
|
||||
if not m4[y, x]:
|
||||
continue
|
||||
q = deque([(y, x)]); m4[y, x] = False
|
||||
n, y0, y1, x0, x1 = 0, y, y, x, x
|
||||
while q:
|
||||
cy, cx = q.popleft(); n += 1
|
||||
y0, y1, x0, x1 = min(y0, cy), max(y1, cy), min(x0, cx), max(x1, cx)
|
||||
for dy, dx in ((1, 0), (-1, 0), (0, 1), (0, -1), (1, 1), (1, -1), (-1, 1), (-1, -1)):
|
||||
ny, nx = cy + dy, cx + dx
|
||||
if 0 <= ny < H and 0 <= nx < W and m4[ny, nx]:
|
||||
m4[ny, nx] = False; q.append((ny, nx))
|
||||
if n < 12: # 岛太小(<~200 全尺寸像素)= 画面固有红点,不动
|
||||
continue
|
||||
gy0, gy1 = max(0, y0 * s - 12), min(im.height, (y1 + 1) * s + 12)
|
||||
gx0, gx1 = max(0, x0 * s - 12), min(im.width, (x1 + 1) * s + 12)
|
||||
if (gy1 - gy0) > im.height // 3 or (gx1 - gx0) > im.width // 3:
|
||||
continue # 异常大块不自动动,留人检
|
||||
ph, pw = gy1 - gy0, gx1 - gx0
|
||||
cy0, cx0 = im.height // 2 - ph // 2, im.width // 2 - pw // 2
|
||||
im.paste(im.crop((cx0, cy0, cx0 + pw, cy0 + ph)), (gx0, gy0))
|
||||
patched += 1
|
||||
return im, patched
|
||||
|
||||
def find_raw(sid):
|
||||
for ext in ('.jpg', '.jpeg', '.png', '.webp'):
|
||||
p = os.path.join(RAW, sid + ext)
|
||||
if os.path.exists(p): return p
|
||||
return None
|
||||
|
||||
if __name__ == '__main__':
|
||||
srcs = []
|
||||
for sid in SHEETS:
|
||||
p = find_raw(sid)
|
||||
if not p: sys.exit(f'缺 tile 源图 {sid}(先跑 asset-gen.mjs --group tiles)')
|
||||
srcs.append(p)
|
||||
rows_per_sheet = 16 // COLS # 每张源图贡献 16 块 → 2 行
|
||||
sheet = Image.new('RGB', (COLS * TILE, len(SHEETS) * rows_per_sheet * TILE))
|
||||
for si, p in enumerate(srcs):
|
||||
im = Image.open(p).convert('RGB')
|
||||
im = im.crop((INSET, INSET, im.width - INSET, im.height - INSET)) # 去边框线
|
||||
im, patched = patch_seals(im)
|
||||
if patched: print(f'[{SHEETS[si]}] 检出印章 {patched} 枚,已用中心纹理修补')
|
||||
im = im.resize((512, 512), Image.LANCZOS)
|
||||
for i in range(16): # 源图 4×4 切块,按序排入 8 列网格
|
||||
sx, sy = (i % 4) * TILE, (i // 4) * TILE
|
||||
gi = si * 16 + i
|
||||
dx, dy = (gi % COLS) * TILE, (gi // COLS) * TILE
|
||||
sheet.paste(im.crop((sx, sy, sx + TILE, sy + TILE)), (dx, dy))
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
webp = os.path.join(OUT_DIR, 'tiles-map1.webp')
|
||||
png_dev = os.path.join(WORK, 'tiles-map1.png')
|
||||
sheet.save(webp, quality=80, method=6)
|
||||
sheet.save(png_dev)
|
||||
kb = os.path.getsize(webp) / 1024
|
||||
print(f'tile 表完成:{sheet.width}x{sheet.height},{len(SHEETS) * 16} tiles @{TILE}px,'
|
||||
f'索引=行×{COLS}+列(a=0-15/b=16-31/c=32-47) → {webp}({kb:.0f}KB webp)')
|
||||