取景(issue 04) - 新增 viewport-fitter.js:让立绘层缩放跟随背景图的 cover 缩放——背景放大多少倍、 特效就放大多少倍。原先背景走 cover、立绘层走 contain,比例一变两者就分叉。 16:9 下与作者设定逐像素一致(kv37 为 0 差异)。实测证伪了"contain 会裁掉作者视口" 这个最初的判断:contain 在任何比例下都完整含住作者视口。 - 应用方式为原地改写 config.viewport 字段后调 setViewport(),不重建播放器; 维护挂在 config.frame 上按画布比例增量进行(config.success 早于 setViewport,拿不到视口)。 WE 属性(issue 05) - project.json 新增 bgm combo(所有壁纸音源的并集 + 「随预设」);所选项不属于当前壁纸时 音频回落该壁纸默认,选择本身保留。 - fps 限流包裹实例 drawFrame 并接管排程。修掉两个实测缺陷:朴素丢帧会把 rAF 抖动放大成 整拍丢失(目标 30fps 实测只有 22.5fps);fps:0 被真值判断吞掉导致无法取消限流。 打包(issue 06) - project.json version 升至 3 并更新描述;preset 的 combo 取值保持 xilian/kv37 不变。 - preview.gif 821 KB → 347 KB(每 2 帧取 1 帧 + 128 色不抖动,逐帧与源帧像素完全相同)。 - index.html 加内联 favicon,消除 CEF 自动请求 /favicon.ico 造成的 404 报错。 其它 - dist/scripts/wallpaper-engine.js 移到 tools/:它没有任何入口引用,且环境判据依赖真实 WE 并不存在的 wallpaperRegisterListener,一旦按它自己的说明挂进 index.html 会在生产环境自我激活。 - 修正死文件检查:原检查会拿文件名匹配到文件自身的内容,使这类文件漏网。 - 新增离线算式验证与 resize / 属性 / 全量验收三套浏览器测试脚本。
55 lines
2.9 KiB
JavaScript
55 lines
2.9 KiB
JavaScript
// 量出一个预设在某分辨率下的取景几何,用来决定"该给什么视口"。
|
|
// 输出:作者声明的取景矩形、播放器扫描出的骨架边界、当前相机实际可见的世界矩形。
|
|
// 用法:node tools/frame.mjs <port> <presetId> <W> <H>
|
|
|
|
import { withPage, sleep } from "./cdp.mjs";
|
|
|
|
const [port, preset, w, h] = process.argv.slice(2);
|
|
if (!port || !preset || !w || !h) {
|
|
console.error("usage: node tools/frame.mjs <port> <presetId> <W> <H>");
|
|
process.exit(2);
|
|
}
|
|
|
|
const EXPR = `(function () {
|
|
var p = window.__player;
|
|
if (!p || !p.skeleton) return { error: "player/skeleton 未就绪" };
|
|
var cv = p.currentViewport;
|
|
var cam = p.sceneRenderer.camera;
|
|
var cw = p.canvas.width, ch = p.canvas.height;
|
|
// 用播放器自己的投影取真实可见世界矩形,不靠对 zoom 语义的猜测
|
|
var tl = cam.screenToWorld(new spine.Vector3(0, 0, 0), cw, ch);
|
|
var br = cam.screenToWorld(new spine.Vector3(cw, ch, 0), cw, ch);
|
|
var offset = new spine.Vector2(), size = new spine.Vector2();
|
|
p.skeleton.getBounds(offset, size, new Array(2));
|
|
var vis = { x: tl.x, y: br.y, w: br.x - tl.x, h: tl.y - br.y };
|
|
return {
|
|
canvas: { w: cw, h: ch, aspect: +(cw / ch).toFixed(4) },
|
|
base: { x: +cv.x.toFixed(2), y: +cv.y.toFixed(2), w: +cv.width.toFixed(2), h: +cv.height.toFixed(2) },
|
|
pads: { l: +cv.padLeft.toFixed(2), r: +cv.padRight.toFixed(2), b: +cv.padBottom.toFixed(2), t: +cv.padTop.toFixed(2) },
|
|
resolved: { x: +(cv.x - cv.padLeft).toFixed(2), y: +(cv.y - cv.padBottom).toFixed(2), w: +(cv.width + cv.padLeft + cv.padRight).toFixed(2), h: +(cv.height + cv.padBottom + cv.padTop).toFixed(2) },
|
|
visible: { x: +vis.x.toFixed(2), y: +vis.y.toFixed(2), w: +vis.w.toFixed(2), h: +vis.h.toFixed(2), aspect: +(vis.w / vis.h).toFixed(4) },
|
|
skeletonBounds: { x: +offset.x.toFixed(2), y: +offset.y.toFixed(2), w: +size.x.toFixed(2), h: +size.y.toFixed(2) },
|
|
skeletonInView: {
|
|
coversView: (offset.x <= vis.x && offset.y <= vis.y && offset.x + size.x >= vis.x + vis.w && offset.y + size.y >= vis.y + vis.h),
|
|
overflowLeft: +(vis.x - offset.x).toFixed(2),
|
|
overflowRight: +((offset.x + size.x) - (vis.x + vis.w)).toFixed(2),
|
|
overflowTop: +((offset.y + size.y) - (vis.y + vis.h)).toFixed(2),
|
|
overflowBottom: +(vis.y - offset.y).toFixed(2),
|
|
},
|
|
};
|
|
})()`;
|
|
|
|
const result = await withPage({ width: Number(w), height: Number(h), profile: "tools/.cache/edge-frame" }, async ({ send, evaluate }) => {
|
|
const url = `http://127.0.0.1:${port}/index.html?__props=${encodeURIComponent(JSON.stringify({ preset }))}&__freeze=1`;
|
|
await send("Page.navigate", { url });
|
|
for (let i = 0; i < 200; i++) {
|
|
if (await evaluate("!!(window.__player && window.__player.skeleton)")) break;
|
|
await sleep(150);
|
|
}
|
|
await sleep(400);
|
|
return evaluate(EXPR);
|
|
});
|
|
|
|
console.log(`${preset} @ ${w}x${h}`);
|
|
console.log(JSON.stringify(result, null, 2));
|