矿彩国风锚 r1 定稿:《山海行纪》30 帧(hero×6/狰梼杌穷奇×15/法器×3/UI×2/场景/兽潮/Boss/拾取)+ 《山海巡异录》12 张(卡面 3 稀有度×2 职业/敌兽 2/场景 2/结算 2);同名兽狰两款同谱面可辨= 「一条美术管线供两款」首检成立;clean-room 五条全程生效(prompt 零现代作品专名、兽形古籍演绎、 参考图仅自产、全量留痕 manifest.jsonl 含 12 张淘汰逐张原因)。 产线三件真跑演示:色键抠图+除杂(角落印章自动清)/图集 2 页 png 4006KB→webp 599KB(实测修正: free-tex-packer 不直出 webp 补 PIL 转码)/字体子集 780 字→176KB woff2(LXGW OFL)。 成本良率实测:57 呼 ¥1.44(官方口径)/¥1.88(实测口径),首过率 34/42=81%,重摇≤4 轮填满; 工艺发现 7 条入批次 README(兽名不入 prompt/「鲛」prime 龙形/subject-ref 证伪/UI 伪字走程序排字等)。 创始人裁定(2026-07-07):锁锚开批产——两款按锚 r1 进 M3/M4 资产批产,主角跨帧漂移用 「定妆帧作参考图+关键姿态逐帧」工艺压,不换通道。台账:作战清单 M2 波全记账+_index 三行 M2 已交+设定档裁定戳。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
61 lines
3.5 KiB
JavaScript
Executable File
61 lines
3.5 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
// 产线件② 图集打包:透明底精灵 → 2048² 图集(png + webp q80)+ Phaser3 坐标 JSON
|
|
// 用法: node atlas-pack.mjs [输入目录=../demo/keyed] [输出目录=../demo/atlas] [图集名=shanhai-batch1]
|
|
// 实现:free-tex-packer-cli(MIT,--project 工程文件驱动)打包出 png+json;
|
|
// 实测 free-tex-packer-core 的 textureFormat 只支持 png/jpg、不直出 webp(材料档 §5 设想有偏),
|
|
// 故补一步 Pillow 转码 webp q80,JSON 同步改指 webp —— 包体账按 webp 计。
|
|
// LittleJS 侧同一份 json 的 frames 坐标可直接换算 tile 索引;Phaser 侧 load.atlas 原生可用。
|
|
import { readFileSync, writeFileSync, readdirSync, mkdirSync, statSync } from 'node:fs';
|
|
import { execFileSync } from 'node:child_process';
|
|
import { dirname, join, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
const inDir = resolve(process.argv[2] || join(HERE, '..', 'demo', 'keyed'));
|
|
const outDir = resolve(process.argv[3] || join(HERE, '..', 'demo', 'atlas'));
|
|
const name = process.argv[4] || 'shanhai-batch1';
|
|
mkdirSync(outDir, { recursive: true });
|
|
|
|
const pngs = readdirSync(inDir).filter(f => f.endsWith('.png'));
|
|
if (!pngs.length) { console.error(`输入目录无 png:${inDir}`); process.exit(1); }
|
|
|
|
// free-tex-packer 工程文件(schema 逐字核自 free-tex-packer-cli@0.3.0 index.js)
|
|
const project = {
|
|
savePath: outDir,
|
|
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(outDir, `${name}.ftpp`);
|
|
writeFileSync(proj, JSON.stringify(project, null, 2));
|
|
execFileSync('free-tex-packer-cli', ['--project', proj, '--output', outDir], { stdio: 'inherit', timeout: 120_000 });
|
|
|
|
// png → webp q80(PIL),JSON 改指 webp;两格式都保留(png 供开发查看,webp 进包体账)
|
|
// 注意:装不下单页时 free-tex-packer 自动分页,产物带 -0/-1 后缀 —— 按通配收集
|
|
const pages = readdirSync(outDir).filter(f => f.startsWith(name) && f.endsWith('.png'));
|
|
const kb = p => (statSync(p).size / 1024).toFixed(0);
|
|
let totalFrames = 0, pngKB = 0, webpKB = 0;
|
|
for (const png of pages) {
|
|
const base = png.replace(/\.png$/, '');
|
|
const pngPath = join(outDir, png);
|
|
const webpPath = join(outDir, `${base}.webp`);
|
|
execFileSync('python3', ['-c',
|
|
`from PIL import Image\nImage.open(${JSON.stringify(pngPath)}).save(${JSON.stringify(webpPath)}, quality=80, method=6)`,
|
|
], { stdio: 'inherit', timeout: 60_000 });
|
|
const jsonPath = join(outDir, `${base}.json`);
|
|
const j = JSON.parse(readFileSync(jsonPath, 'utf8'));
|
|
for (const t of j.textures || []) if (t.image) t.image = t.image.replace(/\.png$/, '.webp');
|
|
writeFileSync(jsonPath, JSON.stringify(j, null, 2));
|
|
const frames = (j.textures || []).reduce((s, t) => s + (t.frames?.length || 0), 0);
|
|
totalFrames += frames; pngKB += Number(kb(pngPath)); webpKB += Number(kb(webpPath));
|
|
console.log(` 页 ${base}:${frames} 帧 | png ${kb(pngPath)}KB → webp ${kb(webpPath)}KB`);
|
|
}
|
|
console.log(`图集完成:${pages.length} 页 / ${totalFrames} 帧 | png 合计 ${pngKB}KB → webp 合计 ${webpKB}KB`);
|