diff --git a/cocoscreator_projects/scripts/test/ui-frames.test.mjs b/cocoscreator_projects/scripts/test/ui-frames.test.mjs new file mode 100644 index 0000000..5801dd6 --- /dev/null +++ b/cocoscreator_projects/scripts/test/ui-frames.test.mjs @@ -0,0 +1,70 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { frameRect, padWidth, frameFileName, frameNames } from '../ui-migration/frames.mjs'; + +// 实测样例:00014.png 450x280,网格 3x4,单帧 150x70 +const IMG_00014 = { id: 14, w_all: 450, h_all: 280, w: 3, h: 4, frame_all: 12, bmp: '00014.png', w1: 150, h1: 70 }; +// 13 列 1 行 +const IMG_00019 = { id: 19, w_all: 221, h_all: 28, w: 13, h: 1, frame_all: 13, bmp: '00019.png', w1: 17, h1: 28 }; +const IMG_SINGLE = { id: 1, w_all: 1280, h_all: 720, w: 1, h: 1, frame_all: 1, bmp: '00001.png', w1: 1280, h1: 720 }; + +test('frameRect 帧 1 是左上角(1 基)', () => { + assert.deepEqual(frameRect(IMG_00014, 1), { x: 0, y: 0, width: 150, height: 70 }); +}); + +test('frameRect 行优先:帧 4 换到第二行首列', () => { + // idx=3 → col = 3%3 = 0, row = 1 + assert.deepEqual(frameRect(IMG_00014, 4), { x: 0, y: 70, width: 150, height: 70 }); +}); + +test('frameRect 实证样例:「通知」FrameIndex=10 → 第 3 行第 0 列', () => { + // 已由读图验证:00014.png 第 3 行第 0 列即「通知」 + assert.deepEqual(frameRect(IMG_00014, 10), { x: 0, y: 210, width: 150, height: 70 }); +}); + +test('frameRect 实证样例:「设置」FrameIndex=11 → 第 3 行第 1 列', () => { + assert.deepEqual(frameRect(IMG_00014, 11), { x: 150, y: 210, width: 150, height: 70 }); +}); + +test('frameRect 实证样例:「战绩」FrameIndex=4 → 第 1 行第 0 列', () => { + assert.deepEqual(frameRect(IMG_00014, 4), { x: 0, y: 70, width: 150, height: 70 }); +}); + +test('frameRect 单行网格逐列推进', () => { + assert.deepEqual(frameRect(IMG_00019, 1), { x: 0, y: 0, width: 17, height: 28 }); + assert.deepEqual(frameRect(IMG_00019, 13), { x: 204, y: 0, width: 17, height: 28 }); +}); + +test('frameRect FrameIndex=0 按帧 1 处理', () => { + assert.deepEqual(frameRect(IMG_00014, 0), frameRect(IMG_00014, 1)); +}); + +test('frameRect 越界抛 RangeError 且带图名', () => { + assert.throws(() => frameRect(IMG_00014, 13), RangeError); + assert.throws(() => frameRect(IMG_00014, 13), /00014\.png/); +}); + +test('padWidth 按帧总数决定补零位数', () => { + assert.equal(padWidth(9), 1); + assert.equal(padWidth(12), 2); + assert.equal(padWidth(100), 3); +}); + +test('frameFileName 1 基且补零,保证字典序等于帧序', () => { + assert.equal(frameFileName('00014.png', 1, 12), '00014_01.png'); + assert.equal(frameFileName('00014.png', 10, 12), '00014_10.png'); + const names = [frameFileName('00014.png', 2, 12), frameFileName('00014.png', 10, 12)]; + assert.deepEqual([...names].sort(), ['00014_02.png', '00014_10.png'], '字典序必须等于帧序'); +}); + +test('frameFileName 单帧图原样返回', () => { + assert.equal(frameFileName('00001.png', 1, 1), '00001.png'); +}); + +test('frameNames 返回全部帧,下标 0 对应帧 1', () => { + const names = frameNames(IMG_00014); + assert.equal(names.length, 12); + assert.equal(names[0], '00014_01.png'); + assert.equal(names[11], '00014_12.png'); + assert.deepEqual(frameNames(IMG_SINGLE), ['00001.png']); +}); diff --git a/cocoscreator_projects/scripts/ui-migration/frames.mjs b/cocoscreator_projects/scripts/ui-migration/frames.mjs new file mode 100644 index 0000000..2dc113a --- /dev/null +++ b/cocoscreator_projects/scripts/ui-migration/frames.mjs @@ -0,0 +1,39 @@ +/** + * 帧矩形与命名。见 spec §0.3、§3。 + * 帧号 1 基、行优先——已由读取 00014.png 的网格内容与 11 个按钮的 FrameIndex 对照实证。 + */ + +/** 帧号 → 源矩形。FrameIndex=0 按帧 1 处理(实测仅 2 个对象如此)。 */ +export function frameRect(image, frameIndex) { + const idx = (frameIndex || 1) - 1; + if (idx < 0 || idx >= image.frame_all) { + throw new RangeError( + `帧号越界: ${image.bmp} frameIndex=${frameIndex}(有效范围 1..${image.frame_all})`, + ); + } + return { + x: (idx % image.w) * image.w1, + y: Math.floor(idx / image.w) * image.h1, + width: image.w1, + height: image.h1, + }; +} + +/** 补零位数:保证字典序 = 帧序。 */ +export function padWidth(frameCount) { + return String(frameCount).length; +} + +/** 00014.png + 帧 5/共 12 → 00014_05.png;单帧图原样返回。 */ +export function frameFileName(bmp, frameIndex, frameCount) { + if (frameCount <= 1) return bmp; + const base = bmp.replace(/\.png$/i, ''); + const n = String(frameIndex || 1).padStart(padWidth(frameCount), '0'); + return `${base}_${n}.png`; +} + +/** 该图全部帧的文件名,下标 0 对应帧 1。 */ +export function frameNames(image) { + return Array.from({ length: image.frame_all }, (_, i) => + frameFileName(image.bmp, i + 1, image.frame_all)); +}