支持spine进行绘制

This commit is contained in:
tianmo committed 2026-11-10 15:50:16 +08:00
1 parent 266c353fd2
commit e89eeaa502
19 files changed
+311 -19

No files matched your search

+115 -3
View File
@@ -1,15 +1,36 @@
import { Container, Matrix } from 'pixi.js'
import { Container, Graphics, Matrix } from 'pixi.js'
import { getSpineAsset } from './spineAssetRegistry'
import type { SpineSceneObject } from './spineTypes'
interface RuntimeEntry {
root: Container
spine: any
skeletonDebug: Graphics
skinMeshDebug: Graphics
runtimeVersion: '4.2' | '4.3'
assetVersion: number
animation: string
}
function attachmentDebugColor(key: string) {
let hash = 2166136261
for (let i = 0; i < key.length; i++) hash = Math.imul(hash ^ key.charCodeAt(i), 16777619)
const hue = (hash >>> 0) % 360
const segment = hue / 60
const chroma = 0.78
const x = chroma * (1 - Math.abs(segment % 2 - 1))
const offset = 1 - chroma
const [red, green, blue] = segment < 1 ? [chroma, x, 0]
: segment < 2 ? [x, chroma, 0]
: segment < 3 ? [0, chroma, x]
: segment < 4 ? [0, x, chroma]
: segment < 5 ? [x, 0, chroma]
: [chroma, 0, x]
return (Math.round((red + offset) * 255) << 16)
| (Math.round((green + offset) * 255) << 8)
| Math.round((blue + offset) * 255)
}
export interface SpineBoneWorldTransform {
x: number
y: number
@@ -91,6 +112,90 @@ export class SpineRuntimeLayer {
entry.root.destroy({ children: true })
}
private drawDebug(entry: RuntimeEntry, object: SpineSceneObject) {
const { skeletonDebug, skinMeshDebug, root, spine } = entry
skeletonDebug.clear()
skinMeshDebug.clear()
skeletonDebug.visible = object.showBones
skinMeshDebug.visible = object.showOnlySkinMesh
|| object.showRegionAttachments
|| object.showClippingAttachments
if (!object.showBones && !skinMeshDebug.visible) return
const screenScale = Math.max(
0.0001,
Math.abs(root.scale.x * (this.container.parent?.scale.x ?? 1)),
Math.abs(root.scale.y * (this.container.parent?.scale.y ?? 1)),
)
const lineWidth = 1 / screenScale
const skeleton = spine.skeleton
const offsetX = Number(skeleton.x) || 0
const offsetY = Number(skeleton.y) || 0
if (object.showBones) {
skeletonDebug.lineStyle(lineWidth, 0x55d6ff, 0.95)
for (const bone of skeleton.bones) {
if (!bone.active || !bone.parent?.active) continue
skeletonDebug.moveTo(offsetX + bone.parent.worldX, offsetY + bone.parent.worldY)
skeletonDebug.lineTo(offsetX + bone.worldX, offsetY + bone.worldY)
}
skeletonDebug.beginFill(0xb8efff, 0.95)
for (const bone of skeleton.bones) {
if (bone.active) skeletonDebug.drawCircle(offsetX + bone.worldX, offsetY + bone.worldY, 2 / screenScale)
}
skeletonDebug.endFill()
}
if (!object.showOnlySkinMesh && !object.showRegionAttachments && !object.showClippingAttachments) return
const activeSlots = skeleton.slots.filter((slot: any) => slot.bone?.active)
for (const slot of activeSlots) {
const attachment = slot.getAttachment()
if (!attachment) continue
const color = attachmentDebugColor(`${slot.data.index}:${attachment.name}`)
const isMesh = Array.isArray(attachment.triangles) && attachment.worldVerticesLength > 0
if (isMesh) {
if (!object.showOnlySkinMesh) continue
skinMeshDebug.lineStyle(lineWidth, color, 0.9)
skinMeshDebug.beginFill(color, 0.18)
const vertices = new Float32Array(attachment.worldVerticesLength)
attachment.computeWorldVertices(slot, 0, attachment.worldVerticesLength, vertices, 0, 2)
for (let i = 0; i < attachment.triangles.length; i += 3) {
const a = attachment.triangles[i] * 2
const b = attachment.triangles[i + 1] * 2
const c = attachment.triangles[i + 2] * 2
skinMeshDebug.drawPolygon([
vertices[a], vertices[a + 1],
vertices[b], vertices[b + 1],
vertices[c], vertices[c + 1],
])
}
skinMeshDebug.endFill()
continue
}
if (!object.showRegionAttachments
|| !attachment.offset
|| !Number.isFinite(attachment.width)
|| !Number.isFinite(attachment.height)
|| typeof attachment.computeWorldVertices !== 'function') {
if (object.showClippingAttachments
&& attachment.endSlot !== undefined
&& attachment.worldVerticesLength > 0
&& typeof attachment.computeWorldVertices === 'function') {
const clippingVertices = new Float32Array(attachment.worldVerticesLength)
attachment.computeWorldVertices(slot, 0, attachment.worldVerticesLength, clippingVertices, 0, 2)
skinMeshDebug.lineStyle(lineWidth, color, 0.9)
skinMeshDebug.drawPolygon(Array.from(clippingVertices))
}
continue
}
const regionVertices = new Float32Array(8)
attachment.computeWorldVertices(slot, regionVertices, 0, 2)
skinMeshDebug.lineStyle(lineWidth, color, 0.9)
skinMeshDebug.drawPolygon(Array.from(regionVertices))
}
}
sync(objects: SpineSceneObject[], frame: number, fps: number) {
for (const [id, entry] of this.entries) {
if (objects.some((object) => object.id === id)) continue
@@ -110,12 +215,18 @@ export class SpineRuntimeLayer {
if (entry) this.destroyEntry(entry)
const root = new Container()
const spine = new asset.SpineClass({ skeletonData: asset.skeletonData, autoUpdate: false, darkTint: false })
const skeletonDebug = new Graphics()
const skinMeshDebug = new Graphics()
skeletonDebug.eventMode = 'none'
skinMeshDebug.eventMode = 'none'
skeletonDebug.visible = false
skinMeshDebug.visible = false
if (asset.skeletonData.defaultSkin) spine.skeleton.setSkin(asset.skeletonData.defaultSkin)
if (asset.runtimeVersion === '4.3') spine.skeleton.setupPoseSlots()
else spine.skeleton.setSlotsToSetupPose()
root.addChild(spine)
root.addChild(spine, skeletonDebug, skinMeshDebug)
this.container.addChild(root)
entry = { root, spine, runtimeVersion: asset.runtimeVersion, assetVersion: object.assetVersion, animation: '' }
entry = { root, spine, skeletonDebug, skinMeshDebug, runtimeVersion: asset.runtimeVersion, assetVersion: object.assetVersion, animation: '' }
this.entries.set(object.id, entry)
}
if (!entry) continue
@@ -144,6 +255,7 @@ export class SpineRuntimeLayer {
track.trackTime = duration > 0 ? Math.min(localTime, duration) : localTime
}
spine.update(0)
this.drawDebug(entry, object)
}
}