新增spine版本支持

This commit is contained in:
tianmo committed 2026-09-01 17:22:18 +08:00
1 parent 8802bbe25e
commit 19278dac01
91 files changed
+69095 -5744

No files matched your search

+48 -10
View File
@@ -9,6 +9,9 @@ import {
type AttachmentLoader,
type Skin,
} from '@esotericsoftware/spine-core'
import { normalizeSpine38JsonFor42 } from '../spine/spine38Compatibility'
import { normalizeSpineVersion, supportedSpineVersionsText, type SupportedSpineVersion } from '../spine/spineVersion'
import type { SpineJsonMap } from './spineJsonTypes'
class ExportValidationAttachmentLoader implements AttachmentLoader {
newRegionAttachment(_skin: Skin, name: string, path: string, _sequence: any) { return new RegionAttachment(name, path) }
@@ -59,12 +62,12 @@ function validateMesh(mesh: Record<string, any>, attachmentName: string, boneCou
if (offset !== vertices.length || decodedVertices !== vertexCount) throw new Error(`拖尾网格“${attachmentName}”的顶点数量与 UV 不一致`)
}
function validateEditorImportStructure(root: Record<string, any>) {
function validateEditorImportStructure(root: Record<string, any>, version: SupportedSpineVersion) {
const bones = Array.isArray(root.bones) ? root.bones : []
const boneByName = new Map(bones.map((bone) => [String(bone.name || ''), bone]))
const weightedMeshSlots = new Set<string>()
for (const bone of bones) {
if (bone.inherit != null && !VALID_INHERIT.has(bone.inherit)) throw new Error(`骨骼“${bone.name || ''}”的 inherit 值“${bone.inherit}”不符合 Spine 4.2 JSON 格式`)
if (bone.inherit != null && !VALID_INHERIT.has(bone.inherit)) throw new Error(`骨骼“${bone.name || ''}”的 inherit 值“${bone.inherit}”不符合 Spine ${version} JSON 格式`)
if (/_particle_\d+_trail_/.test(String(bone.name || ''))) {
const parent = boneByName.get(String(bone.parent || ''))
if (!parent || /_particle_\d+_trail_/.test(String(parent.name || ''))) {
@@ -94,12 +97,47 @@ function validateEditorImportStructure(root: Record<string, any>) {
}
}
/** 下载前使用项目内置 Spine 4.2 Runtime 做一次结构解析,提前拦截无效 JSON。 */
export function validateSpineJson(text: string) {
const root = JSON.parse(text)
validateEditorImportStructure(root)
const reader = new SkeletonJson(new ExportValidationAttachmentLoader())
const data = reader.readSkeletonData(root)
if (!data.bones.length) throw new Error('导出结果缺少骨骼')
return { bones: data.bones.length, slots: data.slots.length, animations: data.animations.length }
function validateSpine38Structure(root: Record<string, any>) {
if (String(root?.skeleton?.spine || '') !== '3.8') throw new Error('Spine 3.8 导出的版本标识必须为 3.8')
for (const bone of root.bones || []) {
if (bone.inherit != null) throw new Error(`Spine 3.8 骨骼“${bone.name || ''}”应使用 transform,不能使用 inherit`)
}
for (const animation of Object.values(root.animations || {}) as SpineJsonMap[]) {
for (const slot of Object.values(animation.slots || {}) as SpineJsonMap[]) {
if (slot.rgba || slot.rgba2) throw new Error('Spine 3.8 插槽颜色时间轴必须使用 color / twoColor')
for (const frame of slot.attachment || []) {
if (!Object.prototype.hasOwnProperty.call(frame, 'name')) throw new Error('Spine 3.8 附件时间轴的隐藏帧必须显式写入 name: null')
}
}
for (const bone of Object.values(animation.bones || {}) as SpineJsonMap[]) {
for (const frame of bone.rotate || []) {
if (frame.value != null || frame.angle == null) throw new Error('Spine 3.8 旋转时间轴必须使用 angle')
}
}
const stack: unknown[] = [animation]
while (stack.length) {
const value = stack.pop()
if (!value || typeof value !== 'object') continue
if (!Array.isArray(value) && Array.isArray((value as SpineJsonMap).curve)) throw new Error('Spine 3.8 不支持 4.x 数组曲线格式')
stack.push(...Object.values(value))
}
}
}
/**
* 下载前使用项目内置运行时解析共同结构,提前拦截无效 JSON。
* 目标版本由 skeleton.spine 决定;校验不会改写导出的版本字段。
*/
export function validateSpineJson(text: string, expectedVersion?: SupportedSpineVersion) {
const root = JSON.parse(text)
const version = normalizeSpineVersion(root?.skeleton?.spine)
if (!version) throw new Error(`导出版本不受支持,仅支持 Spine ${supportedSpineVersionsText()}`)
if (expectedVersion && version !== expectedVersion) throw new Error(`导出版本不一致:设置为 Spine ${expectedVersion},JSON 标记为 Spine ${version}`)
validateEditorImportStructure(root, version)
if (version === '3.8') validateSpine38Structure(root)
const runtimeRoot = version === '3.8' ? normalizeSpine38JsonFor42(JSON.parse(JSON.stringify(root))) : root
const reader = new SkeletonJson(new ExportValidationAttachmentLoader())
const data = reader.readSkeletonData(runtimeRoot)
if (!data.bones.length) throw new Error('导出结果缺少骨骼')
return { version, bones: data.bones.length, slots: data.slots.length, animations: data.animations.length }
}