mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-04 14:24:53 +08:00
[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.
This commit is contained in:
parent
c79f89b1eb
commit
e290569748
@ -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
Normal file
BIN
spine-ts/spine-pixi/example/assets/spine_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
125
spine-ts/spine-pixi/example/slot-objects.html
Normal file
125
spine-ts/spine-pixi/example/slot-objects.html
Normal file
@ -0,0 +1,125 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>spine-pixi</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/pixi.js@7.2.4/dist/pixi.min.js"></script>
|
||||
<script src="../dist/iife/spine-pixi.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/tweakpane@3.1.9/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>
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user