修改粒子命名规则

This commit is contained in:
tianmo committed 2026-10-14 18:35:26 +08:00
1 parent 1ecbb6fc48
commit aabf4ff693
10 files changed
+235 -97

No files matched your search

+107
View File
@@ -0,0 +1,107 @@
import { build } from 'esbuild'
const source = `
import { buildSpineJson } from './src/export/spineJsonExporter.ts'
import { validateSpineJson } from './src/export/spineJsonValidator.ts'
const frameTexture = { width: 8, height: 8 }
const single = {
id: 1, imageMode: 'single', sequenceFrames: [], textureName: 'star.png', textureFolder: '',
anchorX: 0.5, anchorY: 0.5, blend: 'normal', independentAlpha: 'off', texture: frameTexture,
}
const sequence = {
id: 2, imageMode: 'sequence', sequenceFrames: [
{ name: 'seq_0.png', previewUrl: '', texture: frameTexture },
{ name: 'seq_1.png', previewUrl: '', texture: frameTexture },
{ name: 'seq_2.png', previewUrl: '', texture: frameTexture },
], textureName: 'seq.png', textureFolder: '',
anchorX: 0.5, anchorY: 0.5, blend: 'add', independentAlpha: 'off', texture: frameTexture,
}
const trailState = (x) => ({
bones: [0, 1, 2].map((index) => ({
boneName: 'p_0_trail_' + index, x: x - index * 5, y: 0, rotation: 0,
worldX: x - index * 5, worldY: 0, worldRotation: 0,
})),
width: 20, alpha: 1, colorHex: 0xffffff, resourceId: 1,
})
const particle = (boneName, spawnId, resourceId, imageFrameIndex, x, trail) => ({
active: true, boneName, spawnId, resourceId, imageFrameIndex,
imageVisible: true, colorHex: 0xffffff, alpha: 1,
x, y: 0, rotation: 0, scaleX: 1, scaleY: 1,
worldX: x, worldY: 0, worldRotation: 0, worldScaleX: 1, worldScaleY: 1,
trailState: trail,
})
const frames = Array.from({ length: 6 }, (_, frame) => [
particle('p_0', 1, 1, 0, frame, trailState(frame)),
particle('p_1', 2, 2, frame % 3, frame * 2),
])
const system = {
id: 1, animation: 'animation', visible: true, frames,
config: {
name: 'lizi_1', mode: 'burst', lifeMode: 'fixed', lifeMin: 1, lifeMax: 1, delay: 0,
imageResources: [single, sequence], trail: true,
trailResources: [{
id: 1, texture: frameTexture, textureName: 'trail.png', blend: 'normal', boneCount: 3,
lengthMode: 'fixed', length: 100, widthSizeMode: 'fixed', width: 100, widthMode: 'particle',
}],
},
}
const settings = {
imagesPath: './images/', spineVersion: '4.2', keyframeCurve: 'linear', fps: 30,
omitFps: false, integerFrameAlignment: true, bonePool: true,
excludeEmptyAnimations: false, fileName: 'naming.json',
}
const result = buildSpineJson({ systems: [system], timeline: { fps: 30, animations: ['animation'] }, settings })
validateSpineJson(result.text, '4.2')
const json = JSON.parse(result.text)
const failures = []
const bone = (name) => json.bones.find((item) => item.name === name)
const slot = (name) => json.slots.find((item) => item.name === name)
// 1. root 下的粒子根节点 = 系统名
if (!bone('lizi_1') || bone('lizi_1').parent !== 'root') failures.push('粒子根节点应为 lizi_1 且父级为 root')
// 2. 粒子骨骼 = 系统名_序号(父级为根节点)
for (const name of ['lizi_1_0', 'lizi_1_1']) {
if (!bone(name) || bone(name).parent !== 'lizi_1') failures.push('粒子骨骼命名/父级错误:' + name)
}
// 3. 插槽 = 系统名_序号_颜色模式(挂在对应骨骼下)
if (!slot('lizi_1_0_normal') || slot('lizi_1_0_normal').bone !== 'lizi_1_0') failures.push('插槽应为 lizi_1_0_normal 且挂在 lizi_1_0')
if (!slot('lizi_1_1_add') || slot('lizi_1_1_add').bone !== 'lizi_1_1') failures.push('插槽应为 lizi_1_1_add 且挂在 lizi_1_1')
// 4. 图片附件:单图为系统名;序列帧加序号尾缀
const skin = json.skins[0].attachments
const singleAttachments = Object.keys(skin['lizi_1_0_normal'] || {})
if (JSON.stringify(singleAttachments) !== JSON.stringify(['lizi_1'])) failures.push('单图附件应为 lizi_1,实际 ' + JSON.stringify(singleAttachments))
const sequenceAttachments = Object.keys(skin['lizi_1_1_add'] || {}).sort()
if (JSON.stringify(sequenceAttachments) !== JSON.stringify(['lizi_1_0', 'lizi_1_1', 'lizi_1_2'])) {
failures.push('序列帧附件应为 lizi_1_0/1/2,实际 ' + JSON.stringify(sequenceAttachments))
}
// 动画里的附件时间轴要引用同样的名字
const anim = json.animations.animation
const usedNames = new Set((anim.slots['lizi_1_1_add']?.attachment || []).map((key) => key.name).filter(Boolean))
for (const name of ['lizi_1_0', 'lizi_1_1', 'lizi_1_2']) if (!usedNames.has(name)) failures.push('序列帧动画未引用附件 ' + name)
if (!(anim.slots['lizi_1_0_normal']?.attachment || []).some((key) => key.name === 'lizi_1')) failures.push('单图动画未引用附件 lizi_1')
// 拖尾骨骼同样遵循“系统名_序号”前缀,槽位挂在首个拖尾骨骼下。
for (let index = 0; index < 3; index++) {
const name = 'lizi_1_0_trail_1_' + index
if (!bone(name) || bone(name).parent !== 'lizi_1') failures.push('拖尾骨骼命名/父级错误:' + name)
}
if (!slot('lizi_1_0_trail_1') || slot('lizi_1_0_trail_1').bone !== 'lizi_1_0_trail_1_0') {
failures.push('拖尾插槽应为 lizi_1_0_trail_1 且挂在 lizi_1_0_trail_1_0')
}
if (failures.length) { failures.forEach((message) => console.error('命名规则失败:' + message)); process.exitCode = 1 }
else console.log('导出命名规则校验通过:lizi_1 / lizi_1_0 / lizi_1_0_normal / lizi_1(_序号)')
`
const result = await build({
stdin: { contents: source, resolveDir: process.cwd(), sourcefile: 'verify-spine-naming.ts', loader: 'ts' },
bundle: true,
platform: 'node',
format: 'esm',
write: false,
logLevel: 'silent',
})
const code = Buffer.from(result.outputFiles[0].contents).toString('base64')
await import(`data:text/javascript;base64,${code}`)