diff --git a/spine-ts/spine-pixi/example/assets/spine_logo.png b/spine-ts/spine-pixi/example/assets/spine_logo.png new file mode 100644 index 0000000000..40e65c5c91 Binary files /dev/null and b/spine-ts/spine-pixi/example/assets/spine_logo.png differ diff --git a/spine-ts/spine-pixi/example/slot-objects.html b/spine-ts/spine-pixi/example/slot-objects.html new file mode 100644 index 0000000000..493d374fd5 --- /dev/null +++ b/spine-ts/spine-pixi/example/slot-objects.html @@ -0,0 +1,125 @@ + + + + spine-pixi + + + + + + + + + + \ No newline at end of file diff --git a/spine-ts/spine-pixi/src/Spine.ts b/spine-ts/spine-pixi/src/Spine.ts index d32515b918..e32d2516e3 100644 --- a/spine-ts/spine-pixi/src/Spine.ts +++ b/spine-ts/spine-pixi/src/Spine.ts @@ -34,6 +34,7 @@ import { AtlasAttachmentLoader, ClippingAttachment, Color, + MathUtils, MeshAttachment, Physics, RegionAttachment, @@ -224,8 +225,78 @@ export class Spine extends Container { } } - private verticesCache: NumberArrayLike = Utils.newFloatArray(1024); + private slotsObject = new Map(); + private getSlotFromRef(slotRef: number | string | Slot): Slot { + let slot: Slot | null; + if (typeof slotRef === 'number') slot = this.skeleton.slots[slotRef]; + else if (typeof slotRef === 'string') slot = this.skeleton.findSlot(slotRef); + else slot = slotRef; + + if (!slot) throw new Error(`No slot found with the given slot reference: ${slotRef}`); + return slot; + } + /** + * Add a pixi DisplayObject as a child of the Spine object. + * The DisplayObject will be rendered coherently with the draw order of the slot. + * If an attachment is active on the slot, the pixi DisplayObject will be rendered on top of it. + * If the DisplayObject is already attached to the given slot, nothing will happen. + * If the DisplayObject is already attached to another slot, it will be removed from that slot + * before adding it to the given one. + * If another DisplayObject is already attached to this slot, the old one will be removed from this + * slot before adding it to the current one. + * @param slotRef - The slot index, or the slot name, or the Slot where the pixi object will be added to. + * @param pixiObject - The pixi DisplayObject to add. + */ + addSlotObject(slotRef: number | string | Slot, pixiObject: DisplayObject): void { + let slot = this.getSlotFromRef(slotRef); + let oldPixiObject = this.slotsObject.get(slot); + + // search if the pixiObject was already in another slotObject + if (!oldPixiObject) { + for (const [slot, oldPixiObjectAnotherSlot] of this.slotsObject) { + if (oldPixiObjectAnotherSlot === pixiObject) { + this.removeSlotObject(slot, pixiObject); + break; + } + } + } + + if (oldPixiObject === pixiObject) return; + if (oldPixiObject) this.removeChild(oldPixiObject); + + this.slotsObject.set(slot, pixiObject); + this.addChild(pixiObject); + } + /** + * Return the DisplayObject connected to the given slot, if any. + * Otherwise return undefined + * @param pixiObject - The slot index, or the slot name, or the Slot to get the DisplayObject from. + * @returns a DisplayObject if any, undefined otherwise. + */ + getSlotObject(slotRef: number | string | Slot): DisplayObject | undefined { + return this.slotsObject.get(this.getSlotFromRef(slotRef)); + } + /** + * Remove a slot object from the given slot. + * If `pixiObject` is passed and attached to the given slot, remove it from the slot. + * If `pixiObject` is not passed and the given slot has an attached DisplayObject, remove it from the slot. + * @param slotRef - The slot index, or the slot name, or the Slot where the pixi object will be remove from. + * @param pixiObject - Optional, The pixi DisplayObject to remove. + */ + removeSlotObject(slotRef: number | string | Slot, pixiObject?: DisplayObject): void { + let slot = this.getSlotFromRef(slotRef); + let slotObject = this.slotsObject.get(slot); + if (!slotObject) return; + + // if pixiObject is passed, remove only if it is equal to the given one + if (pixiObject && pixiObject !== slotObject) return; + + this.removeChild(slotObject); + this.slotsObject.delete(slot); + } + + private verticesCache: NumberArrayLike = Utils.newFloatArray(1024); private renderMeshes (): void { this.resetMeshes(); @@ -233,8 +304,18 @@ export class Spine extends Container { let uvs: NumberArrayLike | null = null; const drawOrder = this.skeleton.drawOrder; - for (let i = 0, n = drawOrder.length; i < n; i++) { + for (let i = 0, n = drawOrder.length, slotObjectsCounter = 0; i < n; i++) { const slot = drawOrder[i]; + + // render pixi object on the current slot on top of the slot attachment + let pixiObject = this.slotsObject.get(slot); + let zIndex = i + slotObjectsCounter; + if (pixiObject) { + pixiObject.setTransform(slot.bone.worldX, slot.bone.worldY, slot.bone.scaleX, slot.bone.scaleY, slot.bone.getWorldRotationX() * MathUtils.degRad); + pixiObject.zIndex = zIndex + 1; + slotObjectsCounter++; + } + const useDarkColor = slot.darkColor != null; const vertexSize = Spine.clipper.isClipping() ? 2 : useDarkColor ? Spine.DARK_VERTEX_SIZE : Spine.VERTEX_SIZE; if (!slot.bone.active) { @@ -331,7 +412,7 @@ export class Spine extends Container { } const mesh = this.getMeshForSlot(slot); - mesh.zIndex = i; + mesh.zIndex = zIndex; mesh.updateFromSpineData(texture, slot.data.blendMode, slot.data.name, finalVertices, finalVerticesLength, finalIndices, finalIndicesLength, useDarkColor); }