渲染顺序功能

This commit is contained in:
tianmo committed 2026-10-22 11:16:50 +08:00
1 parent aabf4ff693
commit c041503004
22 files changed
+1904 -1291

No files matched your search

+74 -11
View File
@@ -1,4 +1,4 @@
import { Container } from 'pixi.js'
import { Container, Matrix } from 'pixi.js'
import { getSpineAsset } from './spineAssetRegistry'
import type { SpineSceneObject } from './spineTypes'
@@ -22,11 +22,79 @@ export class SpineRuntimeLayer {
readonly container = new Container()
private entries = new Map<number, RuntimeEntry>()
/** 供渲染层级使用:取某个场景 Spine 的运行时实例。 */
getSpine(objectId: number) {
return this.entries.get(objectId)?.spine || null
}
/** 当前帧的绘制顺序(会跟随动画里的 drawOrder timeline)。 */
getDrawOrder(objectId: number): string[] | null {
const spine = this.entries.get(objectId)?.spine
try {
const drawOrder = spine?.skeleton?.drawOrder
return Array.isArray(drawOrder) ? drawOrder.map((slot: any) => String(slot?.data?.name || '')) : null
} catch {
return null
}
}
/** 插槽 wrapper 相对 Spine 的局部矩阵(与 SpinePixi updateSlotObject 保持一致)。 */
getSlotLocalMatrix(objectId: number, slotName: string): Matrix | null {
const spine = this.entries.get(objectId)?.spine
const slot = spine?.skeleton?.findSlot?.(slotName)
const bone = slot?.bone
if (!bone?.active) return null
return new Matrix(bone.a, bone.c, -bone.b, -bone.d, bone.worldX, bone.worldY)
}
/**
* 插槽 wrapper 在编辑器 world 空间中的父矩阵 = 场景对象 root 变换 × 插槽骨骼矩阵。
* 粒子发射器挂进插槽后用它做逆变换补偿,保持原有的世界坐标表现。
*/
getSlotParentMatrix(
objectId: number,
slotName: string,
root: { x: number; y: number; rotation: number; scaleX: number; scaleY: number },
): Matrix | null {
const local = this.getSlotLocalMatrix(objectId, slotName)
if (!local) return null
const radians = -root.rotation * Math.PI / 180
const cos = Math.cos(radians), sin = Math.sin(radians)
const rootMatrix = new Matrix(
cos * root.scaleX, sin * root.scaleX,
-sin * root.scaleY, cos * root.scaleY,
root.x, -root.y,
)
return rootMatrix.append(local)
}
attachSlotObject(objectId: number, slotName: string, container: Container, followAttachmentTimeline: boolean) {
const spine = this.entries.get(objectId)?.spine
if (!spine?.addSlotObject) return false
try {
spine.addSlotObject(slotName, container, { followAttachmentTimeline })
return true
} catch {
return false
}
}
detachSlotObject(objectId: number, slotName: string, container: Container) {
const spine = this.entries.get(objectId)?.spine
try { spine?.removeSlotObject?.(slotName, container) } catch { /* 运行时已被释放时忽略 */ }
}
private destroyEntry(entry: RuntimeEntry) {
// 先摘掉渲染层级 wrapper,避免随 root 一起销毁编辑器里的粒子发射器。
try { entry.spine?.removeSlotObjects?.() } catch { /* ignore */ }
this.container.removeChild(entry.root)
entry.root.destroy({ children: true })
}
sync(objects: SpineSceneObject[], frame: number, fps: number) {
for (const [id, entry] of this.entries) {
if (objects.some((object) => object.id === id)) continue
this.container.removeChild(entry.root)
entry.root.destroy({ children: true })
this.destroyEntry(entry)
this.entries.delete(id)
}
@@ -34,16 +102,12 @@ export class SpineRuntimeLayer {
const asset = getSpineAsset(object.id)
let entry = this.entries.get(object.id)
if (entry && entry.assetVersion !== object.assetVersion && !asset) {
this.container.removeChild(entry.root)
entry.root.destroy({ children: true })
this.destroyEntry(entry)
this.entries.delete(object.id)
entry = undefined
}
if ((!entry || entry.assetVersion !== object.assetVersion) && asset) {
if (entry) {
this.container.removeChild(entry.root)
entry.root.destroy({ children: true })
}
if (entry) this.destroyEntry(entry)
const root = new Container()
const spine = new asset.SpineClass({ skeletonData: asset.skeletonData, autoUpdate: false, darkTint: false })
if (asset.skeletonData.defaultSkin) spine.skeleton.setSkin(asset.skeletonData.defaultSkin)
@@ -115,8 +179,7 @@ export class SpineRuntimeLayer {
destroy() {
for (const entry of this.entries.values()) {
entry.root.removeFromParent()
entry.root.destroy({ children: true })
this.destroyEntry(entry)
}
this.entries.clear()
this.container.removeFromParent()