Эх сурвалжийг харах

[ts][pixi] Add feature to attach pixi objects to slots

[ts][pixi] Format fix

[ts][pixi] Format fix

[ts][pixi] Use world scale for slot object.
Davide Tantillo 1 жил өмнө
parent
commit
e290569748

+ 1 - 0
spine-ts/index.html

@@ -43,6 +43,7 @@
         <li><a href="/spine-pixi/example/physics2.html">Physics II</a></li>
         <li><a href="/spine-pixi/example/physics3.html">Physics III</a></li>
         <li><a href="/spine-pixi/example/physics4.html">Physics IV</a></li>
+        <li><a href="/spine-pixi/example/slot-objects.html">Slot Objects</a></li>
       </ul>
       <li>Phaser</li>
       <ul>

BIN
spine-ts/spine-pixi/example/assets/spine_logo.png


+ 125 - 0
spine-ts/spine-pixi/example/slot-objects.html

@@ -0,0 +1,125 @@
+<html>
+  <head>
+    <meta charset="UTF-8" />
+    <title>spine-pixi</title>
+    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/pixi.min.js"></script>
+    <script src="../dist/iife/spine-pixi.js"></script>
+    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/tweakpane.min.js"></script>
+    <link rel="stylesheet" href="../../index.css">
+  </head>
+
+  <body>
+    <script>
+      (async function () {
+        var app = new PIXI.Application({
+          width: window.innerWidth,
+          height: window.innerHeight,
+          resolution: window.devicePixelRatio || 1,
+          autoDensity: true,
+          resizeTo: window,
+          backgroundColor: 0x2c3e50,
+          hello: true,
+        });
+        document.body.appendChild(app.view);
+
+        // Pre-load the skeleton data and atlas. You can also load .json skeleton data.
+        PIXI.Assets.add("spineboyData", "./assets/spineboy-pro.skel");
+        PIXI.Assets.add("spineboyAtlas", "./assets/spineboy-pma.atlas");
+        await PIXI.Assets.load(["spineboyData", "spineboyAtlas"]);
+
+        // Create the spine display object
+        const spineboy = spine.Spine.from("spineboyData", "spineboyAtlas", {
+          scale: 0.5,
+        });
+
+        // Set the default mix time to use when transitioning
+        // from one animation to the next.
+        spineboy.state.data.defaultMix = 0.2;
+
+        // Center the spine object on screen.
+        spineboy.x = window.innerWidth / 2;
+        spineboy.y = window.innerHeight / 2 + spineboy.getBounds().height / 2;
+
+        // Set animation "run" on track 0, looped.
+        spineboy.state.setAnimation(0, "walk", true);
+
+        // Add the display object to the stage.
+        app.stage.addChild(spineboy);
+
+        const logo1 = PIXI.Sprite.from('assets/spine_logo.png');
+        const logo2 = PIXI.Sprite.from('assets/spine_logo.png');
+        const logo3 = PIXI.Sprite.from('assets/spine_logo.png');
+        const logo4 = PIXI.Sprite.from('assets/spine_logo.png');
+        const text = new PIXI.Text('Spine Text');
+
+        // putting logo1 on top of the gun
+        spineboy.addSlotObject("gun", logo1);
+
+        // putting logo2 on top of the hand using slot directly and remove the attachment hand
+        let frontFist;
+        setTimeout(() => {
+          frontFist = spineboy.skeleton.findSlot("front-fist");
+          spineboy.addSlotObject(frontFist, logo2);
+          frontFist.setAttachment(null);
+        }, 2000)
+
+        // scaling the bone, will scale the pixi object too
+        setTimeout(() => {
+          frontFist.bone.scaleX = .5
+          frontFist.bone.scaleY = .5
+        }, 3000)
+
+        // adding a pixi text in a slot using slot index
+        let mouth;
+        setTimeout(() => {
+          mouth = spineboy.skeleton.findSlot("mouth");
+          spineboy.addSlotObject(mouth, text);
+        }, 4000)
+
+        // adding one display object to an already "occupied" slot will remove the old one,
+        // and move the given one to the slot
+        setTimeout(() => {
+          spineboy.addSlotObject(mouth, logo1);
+        }, 5000)
+
+        // adding multiple DisplayObjects to a slot using a Container to control their offset, size, ...
+        setTimeout(() => {
+          const container = new PIXI.Container();
+          container.addChild(logo3, logo4);
+          logo3.y = 20;
+          logo3.scale.set(.5);
+          logo4.scale.set(.5);
+          logo4.tint = 0xFF5500;
+          spineboy.addSlotObject("gun", container);
+        }, 6000)
+
+        // removing the container won't automatically destroy the displayObject contained, so take care of them
+        setTimeout(() => {
+          const container = new PIXI.Container();
+          spineboy.removeSlotObject("gun");
+          logo3.destroy();
+          logo4.destroy();
+        }, 7000)
+
+        // removing a specific slot object, that is not in that slot do nothing
+        setTimeout(() => {
+          const container = new PIXI.Container();
+          spineboy.removeSlotObject(frontFist, text);
+          text.destroy();
+        }, 8000)
+
+        // removing a specific slot object
+        setTimeout(() => {
+          const container = new PIXI.Container();
+          spineboy.removeSlotObject(frontFist, logo2);
+          logo2.destroy();
+        }, 9000)
+
+        // resetting the slot with the original attachment
+        setTimeout(() => {
+          frontFist.setToSetupPose();
+        }, 10000)
+      })();
+    </script>
+  </body>
+</html>

+ 84 - 3
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<Slot, DisplayObject>();
+	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.getWorldScaleX(), slot.bone.getWorldScaleX(), 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);
 			}