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);
}