mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-26 03:31:24 +08:00
[ts][pixi-v7] Allow to define a bounds providers for the Spine game object. See #2734.
This commit is contained in:
parent
9fb49c2166
commit
f8bcbb3e59
@ -53,6 +53,7 @@
|
|||||||
<li><a href="/spine-pixi-v7/example/physics3.html">Physics III</a></li>
|
<li><a href="/spine-pixi-v7/example/physics3.html">Physics III</a></li>
|
||||||
<li><a href="/spine-pixi-v7/example/physics4.html">Physics IV</a></li>
|
<li><a href="/spine-pixi-v7/example/physics4.html">Physics IV</a></li>
|
||||||
<li><a href="/spine-pixi-v7/example/slot-objects.html">Slot Objects</a></li>
|
<li><a href="/spine-pixi-v7/example/slot-objects.html">Slot Objects</a></li>
|
||||||
|
<li><a href="/spine-pixi-v7/example/bounds.html">Bounds</a></li>
|
||||||
<li><a href="/spine-pixi-v7/example/bunnymark.html?count=500">Bunny Mark</a></li>
|
<li><a href="/spine-pixi-v7/example/bunnymark.html?count=500">Bunny Mark</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<li>PixiJS v8</li>
|
<li>PixiJS v8</li>
|
||||||
@ -79,6 +80,7 @@
|
|||||||
<li><a href="/spine-pixi-v8/example/physics3.html">Physics III</a></li>
|
<li><a href="/spine-pixi-v8/example/physics3.html">Physics III</a></li>
|
||||||
<li><a href="/spine-pixi-v8/example/physics4.html">Physics IV</a></li>
|
<li><a href="/spine-pixi-v8/example/physics4.html">Physics IV</a></li>
|
||||||
<li><a href="/spine-pixi-v8/example/slot-objects.html">Slot Objects</a></li>
|
<li><a href="/spine-pixi-v8/example/slot-objects.html">Slot Objects</a></li>
|
||||||
|
<li><a href="/spine-pixi-v8/example/bounds.html">Bounds</a></li>
|
||||||
<li><a href="/spine-pixi-v8/example/bunnymark.html?count=500&renderer=webgpu">Bunny Mark</a></li>
|
<li><a href="/spine-pixi-v8/example/bunnymark.html?count=500&renderer=webgpu">Bunny Mark</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<li>Phaser</li>
|
<li>Phaser</li>
|
||||||
|
|||||||
12
spine-ts/package-lock.json
generated
12
spine-ts/package-lock.json
generated
@ -155,6 +155,17 @@
|
|||||||
"@pixi/core": "7.4.2"
|
"@pixi/core": "7.4.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@pixi/events": {
|
||||||
|
"version": "7.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@pixi/events/-/events-7.4.2.tgz",
|
||||||
|
"integrity": "sha512-Jw/w57heZjzZShIXL0bxOvKB+XgGIevyezhGtfF2ZSzQoSBWo+Fj1uE0QwKd0RIaXegZw/DhSmiMJSbNmcjifA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"peerDependencies": {
|
||||||
|
"@pixi/core": "7.4.2",
|
||||||
|
"@pixi/display": "7.4.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@pixi/extensions": {
|
"node_modules/@pixi/extensions": {
|
||||||
"version": "7.4.2",
|
"version": "7.4.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -3181,6 +3192,7 @@
|
|||||||
"@pixi/assets": "^7.2.4",
|
"@pixi/assets": "^7.2.4",
|
||||||
"@pixi/core": "^7.2.4",
|
"@pixi/core": "^7.2.4",
|
||||||
"@pixi/display": "^7.2.4",
|
"@pixi/display": "^7.2.4",
|
||||||
|
"@pixi/events": "^7.2.4",
|
||||||
"@pixi/graphics": "^7.2.4",
|
"@pixi/graphics": "^7.2.4",
|
||||||
"@pixi/mesh": "^7.2.4",
|
"@pixi/mesh": "^7.2.4",
|
||||||
"@pixi/text": "^7.2.4"
|
"@pixi/text": "^7.2.4"
|
||||||
|
|||||||
122
spine-ts/spine-pixi-v7/example/bounds.html
Normal file
122
spine-ts/spine-pixi-v7/example/bounds.html
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>spine-pixi-v7</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/pixi.js@7.4.2/dist/pixi.min.js"></script>
|
||||||
|
<script src="../dist/iife/spine-pixi-v7.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({alias: "spineboyData", src: "./assets/spineboy-pro.skel"});
|
||||||
|
PIXI.Assets.add({alias: "spineboyAtlas", src: "./assets/spineboy-pma.atlas"});
|
||||||
|
await PIXI.Assets.load(["spineboyData", "spineboyAtlas"]);
|
||||||
|
|
||||||
|
|
||||||
|
// Create the spine display object
|
||||||
|
const spineboy1 = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: .2 });
|
||||||
|
|
||||||
|
const spineboy2 = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: .2,
|
||||||
|
boundsProvider: new spine.SetupPoseBoundsProvider(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const spineboy3 = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: .2,
|
||||||
|
boundsProvider: new spine.SkinsAndAnimationBoundsProvider("portal", undefined, undefined, false),
|
||||||
|
});
|
||||||
|
|
||||||
|
const spineboy4 = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: .2,
|
||||||
|
boundsProvider: new spine.SkinsAndAnimationBoundsProvider("portal", undefined, undefined, true),
|
||||||
|
});
|
||||||
|
|
||||||
|
const spineboy5 = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: .2,
|
||||||
|
boundsProvider: new spine.AABBRectangleBoundsProvider(-100, -100, 100, 100),
|
||||||
|
});
|
||||||
|
|
||||||
|
const maxHeight = spineboy3.getBounds().height;
|
||||||
|
const scaleFactor = 1 / (maxHeight * 5 / window.innerHeight);
|
||||||
|
const scaledMaxHeight = maxHeight * scaleFactor;
|
||||||
|
|
||||||
|
const texts = [
|
||||||
|
"Default bounds: dynamic, recomputed when queried",
|
||||||
|
"Set up pose bound: fixed, based on setup pose",
|
||||||
|
"Skin and animations based bound: fixed, the max AABB rectangle containing the skeleton with the given skin and given animations (clipping is ignored)",
|
||||||
|
"Skin and animations based bound: same as above, but with clipping true. The bounds is smaller because clipped attachments' parts are not considered",
|
||||||
|
"AABB Rectangle bounds: fixed, manually provided bounds. The origin is in skeleton root and size are in skeleton space",
|
||||||
|
]
|
||||||
|
|
||||||
|
const pointerOn = [];
|
||||||
|
|
||||||
|
const elements = [spineboy1, spineboy2, spineboy3, spineboy4, spineboy5].map((spineboy, i) => {
|
||||||
|
// const elements = [spineboy1].map((spineboy, i) => {
|
||||||
|
|
||||||
|
spineboy.x = window.innerWidth / 2;
|
||||||
|
spineboy.y = window.innerHeight / 2 + spineboy.getBounds().height / 2;
|
||||||
|
|
||||||
|
const x = 300 * scaleFactor;
|
||||||
|
|
||||||
|
// spineboy placement
|
||||||
|
spineboy.scale.set(scaleFactor);
|
||||||
|
spineboy.state.setAnimation(0, "portal", true);
|
||||||
|
spineboy.x = x;
|
||||||
|
spineboy.y = 70 * scaleFactor + (window.innerHeight / 10 * (1 + 2*i));
|
||||||
|
|
||||||
|
app.stage.addChild(spineboy);
|
||||||
|
|
||||||
|
// yellow rectangle to show bounds
|
||||||
|
const graphics = new PIXI.Graphics();
|
||||||
|
app.stage.addChild(graphics);
|
||||||
|
|
||||||
|
// text
|
||||||
|
const basicText = new PIXI.Text(
|
||||||
|
texts[i],
|
||||||
|
{
|
||||||
|
fontSize: 20 * scaleFactor,
|
||||||
|
fill: "white",
|
||||||
|
wordWrap: true,
|
||||||
|
wordWrapWidth: 400 * scaleFactor,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
basicText.x = x + scaledMaxHeight + 0 * scaleFactor;
|
||||||
|
basicText.y = scaledMaxHeight * (i + .5);
|
||||||
|
basicText.anchor.set(0, 0.5);
|
||||||
|
app.stage.addChild(basicText);
|
||||||
|
|
||||||
|
// pointer events
|
||||||
|
spineboy.eventMode = "static";
|
||||||
|
spineboy.cursor = "pointer";
|
||||||
|
spineboy.on("pointerenter", () => pointerOn[i] = true);
|
||||||
|
spineboy.on("pointerleave", () => pointerOn[i] = false);
|
||||||
|
|
||||||
|
return [spineboy, graphics];
|
||||||
|
})
|
||||||
|
|
||||||
|
app.ticker.add((delta) => {
|
||||||
|
elements.forEach(([spineboy, graphic], i) => {
|
||||||
|
const bound = spineboy.getBounds();
|
||||||
|
graphic.clear();
|
||||||
|
graphic.lineStyle(2, 0xfeeb77);
|
||||||
|
graphic.beginFill(0xff0000, pointerOn[i] ? .2 : 0);
|
||||||
|
graphic.drawRect(bound.x, bound.y, bound.width, bound.height);
|
||||||
|
graphic.endFill();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -39,6 +39,7 @@
|
|||||||
"@pixi/graphics": "^7.2.4",
|
"@pixi/graphics": "^7.2.4",
|
||||||
"@pixi/text": "^7.2.4",
|
"@pixi/text": "^7.2.4",
|
||||||
"@pixi/assets": "^7.2.4",
|
"@pixi/assets": "^7.2.4",
|
||||||
"@pixi/mesh": "^7.2.4"
|
"@pixi/mesh": "^7.2.4",
|
||||||
|
"@pixi/events": "^7.2.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -43,6 +43,7 @@ import {
|
|||||||
SkeletonClipping,
|
SkeletonClipping,
|
||||||
SkeletonData,
|
SkeletonData,
|
||||||
SkeletonJson,
|
SkeletonJson,
|
||||||
|
Skin,
|
||||||
Utils,
|
Utils,
|
||||||
Vector2,
|
Vector2,
|
||||||
} from "@esotericsoftware/spine-core";
|
} from "@esotericsoftware/spine-core";
|
||||||
@ -51,11 +52,12 @@ import { SlotMesh } from "./SlotMesh.js";
|
|||||||
import { DarkSlotMesh } from "./DarkSlotMesh.js";
|
import { DarkSlotMesh } from "./DarkSlotMesh.js";
|
||||||
import type { ISpineDebugRenderer, SpineDebugRenderer } from "./SpineDebugRenderer.js";
|
import type { ISpineDebugRenderer, SpineDebugRenderer } from "./SpineDebugRenderer.js";
|
||||||
import { Assets } from "@pixi/assets";
|
import { Assets } from "@pixi/assets";
|
||||||
import type { IPointData } from "@pixi/core";
|
import { IPointData, Point, Rectangle } from "@pixi/core";
|
||||||
import { Ticker } from "@pixi/core";
|
import { Ticker } from "@pixi/core";
|
||||||
import type { IDestroyOptions, DisplayObject } from "@pixi/display";
|
import type { IDestroyOptions, DisplayObject } from "@pixi/display";
|
||||||
import { Container } from "@pixi/display";
|
import { Bounds, Container } from "@pixi/display";
|
||||||
import { Graphics } from "@pixi/graphics";
|
import { Graphics } from "@pixi/graphics";
|
||||||
|
import "@pixi/events";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use SpineFromOptions and SpineOptions.
|
* @deprecated Use SpineFromOptions and SpineOptions.
|
||||||
@ -97,6 +99,9 @@ export interface SpineFromOptions {
|
|||||||
* If `undefined`, use the dark tint renderer if at least one slot has tint black
|
* If `undefined`, use the dark tint renderer if at least one slot has tint black
|
||||||
*/
|
*/
|
||||||
darkTint?: boolean;
|
darkTint?: boolean;
|
||||||
|
|
||||||
|
/** The bounds provider to use. If undefined the bounds will be dynamic, calculated when requested and based on the current frame. */
|
||||||
|
boundsProvider?: SpineBoundsProvider,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface SpineOptions {
|
export interface SpineOptions {
|
||||||
@ -108,6 +113,9 @@ export interface SpineOptions {
|
|||||||
|
|
||||||
/** See {@link SpineFromOptions.darkTint}. */
|
/** See {@link SpineFromOptions.darkTint}. */
|
||||||
darkTint?: boolean;
|
darkTint?: boolean;
|
||||||
|
|
||||||
|
/** See {@link SpineFromOptions.boundsProvider}. */
|
||||||
|
boundsProvider?: SpineBoundsProvider,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -122,6 +130,138 @@ export interface SpineEvents {
|
|||||||
start: [trackEntry: TrackEntry];
|
start: [trackEntry: TrackEntry];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A bounds provider calculates the bounding box for a skeleton, which is then assigned as the size of the SpineGameObject. */
|
||||||
|
export interface SpineBoundsProvider {
|
||||||
|
/** Returns the bounding box for the skeleton, in skeleton space. */
|
||||||
|
calculateBounds (gameObject: Spine): {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A bounds provider that provides a fixed size given by the user. */
|
||||||
|
export class AABBRectangleBoundsProvider implements SpineBoundsProvider {
|
||||||
|
constructor (
|
||||||
|
private x: number,
|
||||||
|
private y: number,
|
||||||
|
private width: number,
|
||||||
|
private height: number,
|
||||||
|
) { }
|
||||||
|
calculateBounds () {
|
||||||
|
return { x: this.x, y: this.y, width: this.width, height: this.height };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A bounds provider that calculates the bounding box from the setup pose. */
|
||||||
|
export class SetupPoseBoundsProvider implements SpineBoundsProvider {
|
||||||
|
/**
|
||||||
|
* @param clipping If true, clipping attachments are used to compute the bounds. False, by default.
|
||||||
|
*/
|
||||||
|
constructor (
|
||||||
|
private clipping = false,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
calculateBounds (gameObject: Spine) {
|
||||||
|
if (!gameObject.skeleton) return { x: 0, y: 0, width: 0, height: 0 };
|
||||||
|
// Make a copy of animation state and skeleton as this might be called while
|
||||||
|
// the skeleton in the GameObject has already been heavily modified. We can not
|
||||||
|
// reconstruct that state.
|
||||||
|
const skeleton = new Skeleton(gameObject.skeleton.data);
|
||||||
|
skeleton.setToSetupPose();
|
||||||
|
skeleton.updateWorldTransform(Physics.update);
|
||||||
|
const bounds = skeleton.getBoundsRect(this.clipping ? new SkeletonClipping() : undefined);
|
||||||
|
return bounds.width == Number.NEGATIVE_INFINITY
|
||||||
|
? { x: 0, y: 0, width: 0, height: 0 }
|
||||||
|
: bounds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A bounds provider that calculates the bounding box by taking the maximumg bounding box for a combination of skins and specific animation. */
|
||||||
|
export class SkinsAndAnimationBoundsProvider
|
||||||
|
implements SpineBoundsProvider {
|
||||||
|
/**
|
||||||
|
* @param animation The animation to use for calculating the bounds. If null, the setup pose is used.
|
||||||
|
* @param skins The skins to use for calculating the bounds. If empty, the default skin is used.
|
||||||
|
* @param timeStep The time step to use for calculating the bounds. A smaller time step means more precision, but slower calculation.
|
||||||
|
* @param clipping If true, clipping attachments are used to compute the bounds. False, by default.
|
||||||
|
*/
|
||||||
|
constructor (
|
||||||
|
private animation: string | null,
|
||||||
|
private skins: string[] = [],
|
||||||
|
private timeStep: number = 0.05,
|
||||||
|
private clipping = false,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
calculateBounds (gameObject: Spine): {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
} {
|
||||||
|
if (!gameObject.skeleton || !gameObject.state)
|
||||||
|
return { x: 0, y: 0, width: 0, height: 0 };
|
||||||
|
// Make a copy of animation state and skeleton as this might be called while
|
||||||
|
// the skeleton in the GameObject has already been heavily modified. We can not
|
||||||
|
// reconstruct that state.
|
||||||
|
const animationState = new AnimationState(gameObject.state.data);
|
||||||
|
const skeleton = new Skeleton(gameObject.skeleton.data);
|
||||||
|
const clipper = this.clipping ? new SkeletonClipping() : undefined;
|
||||||
|
const data = skeleton.data;
|
||||||
|
if (this.skins.length > 0) {
|
||||||
|
let customSkin = new Skin("custom-skin");
|
||||||
|
for (const skinName of this.skins) {
|
||||||
|
const skin = data.findSkin(skinName);
|
||||||
|
if (skin == null) continue;
|
||||||
|
customSkin.addSkin(skin);
|
||||||
|
}
|
||||||
|
skeleton.setSkin(customSkin);
|
||||||
|
}
|
||||||
|
skeleton.setToSetupPose();
|
||||||
|
|
||||||
|
const animation = this.animation != null ? data.findAnimation(this.animation!) : null;
|
||||||
|
|
||||||
|
if (animation == null) {
|
||||||
|
skeleton.updateWorldTransform(Physics.update);
|
||||||
|
const bounds = skeleton.getBoundsRect(clipper);
|
||||||
|
return bounds.width == Number.NEGATIVE_INFINITY
|
||||||
|
? { x: 0, y: 0, width: 0, height: 0 }
|
||||||
|
: bounds;
|
||||||
|
} else {
|
||||||
|
let minX = Number.POSITIVE_INFINITY,
|
||||||
|
minY = Number.POSITIVE_INFINITY,
|
||||||
|
maxX = Number.NEGATIVE_INFINITY,
|
||||||
|
maxY = Number.NEGATIVE_INFINITY;
|
||||||
|
animationState.clearTracks();
|
||||||
|
animationState.setAnimationWith(0, animation, false);
|
||||||
|
const steps = Math.max(animation.duration / this.timeStep, 1.0);
|
||||||
|
for (let i = 0; i < steps; i++) {
|
||||||
|
const delta = i > 0 ? this.timeStep : 0;
|
||||||
|
animationState.update(delta);
|
||||||
|
animationState.apply(skeleton);
|
||||||
|
skeleton.update(delta);
|
||||||
|
skeleton.updateWorldTransform(Physics.update);
|
||||||
|
|
||||||
|
const bounds = skeleton.getBoundsRect(clipper);
|
||||||
|
minX = Math.min(minX, bounds.x);
|
||||||
|
minY = Math.min(minY, bounds.y);
|
||||||
|
maxX = Math.max(maxX, bounds.x + bounds.width);
|
||||||
|
maxY = Math.max(maxY, bounds.y + bounds.height);
|
||||||
|
}
|
||||||
|
const bounds = {
|
||||||
|
x: minX,
|
||||||
|
y: minY,
|
||||||
|
width: maxX - minX,
|
||||||
|
height: maxY - minY,
|
||||||
|
};
|
||||||
|
return bounds.width == Number.NEGATIVE_INFINITY
|
||||||
|
? { x: 0, y: 0, width: 0, height: 0 }
|
||||||
|
: bounds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The class to instantiate a {@link Spine} game object in Pixi.
|
* The class to instantiate a {@link Spine} game object in Pixi.
|
||||||
* The static method {@link Spine.from} should be used to instantiate a Spine game object.
|
* The static method {@link Spine.from} should be used to instantiate a Spine game object.
|
||||||
@ -186,6 +326,27 @@ export class Spine extends Container {
|
|||||||
private darkColor = new Color();
|
private darkColor = new Color();
|
||||||
private clippingVertAux = new Float32Array(6);
|
private clippingVertAux = new Float32Array(6);
|
||||||
|
|
||||||
|
private _boundsProvider?: SpineBoundsProvider;
|
||||||
|
/** The bounds provider to use. If undefined the bounds will be dynamic, calculated when requested and based on the current frame. */
|
||||||
|
public get boundsProvider (): SpineBoundsProvider | undefined {
|
||||||
|
return this._boundsProvider;
|
||||||
|
}
|
||||||
|
public set boundsProvider (value: SpineBoundsProvider | undefined) {
|
||||||
|
this._boundsProvider = value;
|
||||||
|
if (value) {
|
||||||
|
this._boundsSpineID = -1;
|
||||||
|
this._boundsSpineDirty = true;
|
||||||
|
this.interactiveChildren = false;
|
||||||
|
} else {
|
||||||
|
this.interactiveChildren = true;
|
||||||
|
this.hitArea = null;
|
||||||
|
}
|
||||||
|
this.calculateBounds();
|
||||||
|
}
|
||||||
|
private _boundsPoint = new Point();
|
||||||
|
private _boundsSpineID = -1;
|
||||||
|
private _boundsSpineDirty = true;
|
||||||
|
|
||||||
constructor (options: SpineOptions | SkeletonData, oldOptions?: ISpineOptions) {
|
constructor (options: SpineOptions | SkeletonData, oldOptions?: ISpineOptions) {
|
||||||
if (options instanceof SkeletonData) {
|
if (options instanceof SkeletonData) {
|
||||||
options = {
|
options = {
|
||||||
@ -215,6 +376,8 @@ export class Spine extends Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.autoUpdate = options?.autoUpdate ?? true;
|
this.autoUpdate = options?.autoUpdate ?? true;
|
||||||
|
|
||||||
|
this.boundsProvider = options.boundsProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -615,6 +778,52 @@ export class Spine extends Container {
|
|||||||
Spine.clipper.clipEnd();
|
Spine.clipper.clipEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
calculateBounds () {
|
||||||
|
if (!this._boundsProvider) {
|
||||||
|
super.calculateBounds();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const transform = this.transform;
|
||||||
|
if (this._boundsSpineID === transform._worldID) return;
|
||||||
|
|
||||||
|
this.updateBounds();
|
||||||
|
|
||||||
|
const bounds = this._localBounds;
|
||||||
|
const p = this._boundsPoint;
|
||||||
|
|
||||||
|
p.set(bounds.minX, bounds.minY);
|
||||||
|
transform.worldTransform.apply(p, p);
|
||||||
|
this._bounds.minX = p.x
|
||||||
|
this._bounds.minY = p.y;
|
||||||
|
|
||||||
|
p.set(bounds.maxX, bounds.maxY)
|
||||||
|
transform.worldTransform.apply(p, p);
|
||||||
|
this._bounds.maxX = p.x
|
||||||
|
this._bounds.maxY = p.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBounds () {
|
||||||
|
if (!this._boundsProvider || !this._boundsSpineDirty) return;
|
||||||
|
|
||||||
|
this._boundsSpineDirty = false;
|
||||||
|
|
||||||
|
if (!this._localBounds) {
|
||||||
|
this._localBounds = new Bounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
const boundsSpine = this._boundsProvider.calculateBounds(this);
|
||||||
|
|
||||||
|
const bounds = this._localBounds;
|
||||||
|
bounds.clear();
|
||||||
|
bounds.minX = boundsSpine.x;
|
||||||
|
bounds.minY = boundsSpine.y;
|
||||||
|
bounds.maxX = boundsSpine.x + boundsSpine.width;
|
||||||
|
bounds.maxY = boundsSpine.y + boundsSpine.height;
|
||||||
|
|
||||||
|
this.hitArea = this._localBounds.getRectangle();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the position of the bone given in input through a {@link IPointData}.
|
* Set the position of the bone given in input through a {@link IPointData}.
|
||||||
* @param bone: the bone name or the bone instance to set the position
|
* @param bone: the bone name or the bone instance to set the position
|
||||||
@ -733,20 +942,19 @@ export class Spine extends Container {
|
|||||||
return Spine.oldFrom(paramOne, atlasAssetName!, options);
|
return Spine.oldFrom(paramOne, atlasAssetName!, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { skeleton, atlas, scale = 1, darkTint, autoUpdate } = paramOne;
|
const { skeleton, atlas, scale = 1, darkTint, autoUpdate, boundsProvider } = paramOne;
|
||||||
const cacheKey = `${skeleton}-${atlas}-${scale}`;
|
const cacheKey = `${skeleton}-${atlas}-${scale}`;
|
||||||
let skeletonData = Spine.skeletonCache[cacheKey];
|
let skeletonData = Spine.skeletonCache[cacheKey];
|
||||||
if (skeletonData) {
|
if (!skeletonData) {
|
||||||
return new Spine({ skeletonData, darkTint, autoUpdate });
|
const skeletonAsset = Assets.get<any | Uint8Array>(skeleton);
|
||||||
|
const atlasAsset = Assets.get<TextureAtlas>(atlas);
|
||||||
|
const attachmentLoader = new AtlasAttachmentLoader(atlasAsset);
|
||||||
|
let parser = skeletonAsset instanceof Uint8Array ? new SkeletonBinary(attachmentLoader) : new SkeletonJson(attachmentLoader);
|
||||||
|
parser.scale = scale;
|
||||||
|
skeletonData = parser.readSkeletonData(skeletonAsset);
|
||||||
|
Spine.skeletonCache[cacheKey] = skeletonData;
|
||||||
}
|
}
|
||||||
const skeletonAsset = Assets.get<any | Uint8Array>(skeleton);
|
return new Spine({ skeletonData, darkTint, autoUpdate, boundsProvider });
|
||||||
const atlasAsset = Assets.get<TextureAtlas>(atlas);
|
|
||||||
const attachmentLoader = new AtlasAttachmentLoader(atlasAsset);
|
|
||||||
let parser = skeletonAsset instanceof Uint8Array ? new SkeletonBinary(attachmentLoader) : new SkeletonJson(attachmentLoader);
|
|
||||||
parser.scale = scale;
|
|
||||||
skeletonData = parser.readSkeletonData(skeletonAsset);
|
|
||||||
Spine.skeletonCache[cacheKey] = skeletonData;
|
|
||||||
return new Spine({ skeletonData, darkTint, autoUpdate });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -368,7 +368,7 @@ export class Spine extends ViewContainer {
|
|||||||
this._autoUpdate = value;
|
this._autoUpdate = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public _boundsProvider?: SpineBoundsProvider;
|
private _boundsProvider?: SpineBoundsProvider;
|
||||||
/** The bounds provider to use. If undefined the bounds will be dynamic, calculated when requested and based on the current frame. */
|
/** The bounds provider to use. If undefined the bounds will be dynamic, calculated when requested and based on the current frame. */
|
||||||
public get boundsProvider (): SpineBoundsProvider | undefined {
|
public get boundsProvider (): SpineBoundsProvider | undefined {
|
||||||
return this._boundsProvider;
|
return this._boundsProvider;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user