From 5bfb51ed0b5d709a1f0c023c5eb34041644528ef Mon Sep 17 00:00:00 2001 From: Eugene Avtuhov Date: Tue, 21 Oct 2025 10:40:24 +0200 Subject: [PATCH] [ts][pixi-v7][pixi-v8] Add static createOptions for Spine init config to simplify subclassing; deprecate from(). (#2950) * [ts][pixi-v7] extract Spine initialization data into getSpineInitData method * [ts][pixi-v8] extract Spine initialization data into getSpineInitData method * [ts][pixi-v8] Renamed getSpineInitData to createOptions. Deprecated from in favor of constructor accepting both SpineOptions and SpineFromOptions. * [ts][pixi-v7] Renamed getSpineInitData to createOptions. Deprecated from in favor of constructor accepting both SpineOptions and SpineFromOptions. * [ts][pixi-v7][pixi-v8] Refactored constructor. --------- Co-authored-by: Eugene Avtukhov Co-authored-by: Davide Tantillo --- spine-ts/spine-pixi-v7/example/bounds.html | 10 +- spine-ts/spine-pixi-v7/example/bunnymark.html | 2 +- .../example/control-bones-example.html | 2 +- .../spine-pixi-v7/example/events-example.html | 2 +- spine-ts/spine-pixi-v7/example/index.html | 2 +- .../spine-pixi-v7/example/inline-loading.html | 4 +- .../example/mix-and-match-example.html | 2 +- .../example/mouse-following.html | 2 +- spine-ts/spine-pixi-v7/example/physics.html | 2 +- spine-ts/spine-pixi-v7/example/physics2.html | 2 +- spine-ts/spine-pixi-v7/example/physics3.html | 2 +- spine-ts/spine-pixi-v7/example/physics4.html | 2 +- .../spine-pixi-v7/example/simple-input.html | 2 +- .../example/slot-objects-scale-rotation.html | 2 +- .../spine-pixi-v7/example/slot-objects.html | 2 +- .../spine-pixi-v7/example/typescript/index.ts | 5 +- spine-ts/spine-pixi-v7/src/Spine.ts | 55 +++++---- spine-ts/spine-pixi-v8/example/bounds.html | 10 +- spine-ts/spine-pixi-v8/example/bunnymark.html | 2 +- spine-ts/spine-pixi-v8/example/coin.html | 2 +- .../example/control-bones-example.html | 2 +- spine-ts/spine-pixi-v8/example/dragon.html | 4 +- .../spine-pixi-v8/example/events-example.html | 2 +- spine-ts/spine-pixi-v8/example/index.html | 2 +- .../spine-pixi-v8/example/inline-loading.html | 4 +- .../example/mix-and-match-example.html | 2 +- .../example/mouse-following.html | 2 +- spine-ts/spine-pixi-v8/example/physics.html | 2 +- spine-ts/spine-pixi-v8/example/physics2.html | 2 +- spine-ts/spine-pixi-v8/example/physics3.html | 2 +- spine-ts/spine-pixi-v8/example/physics4.html | 2 +- .../spine-pixi-v8/example/simple-input.html | 2 +- .../example/slot-objects-scale-rotation.html | 2 +- .../spine-pixi-v8/example/slot-objects.html | 2 +- .../spine-pixi-v8/example/typescript/index.ts | 4 +- spine-ts/spine-pixi-v8/src/Spine.ts | 114 ++++++++++-------- 36 files changed, 146 insertions(+), 118 deletions(-) diff --git a/spine-ts/spine-pixi-v7/example/bounds.html b/spine-ts/spine-pixi-v7/example/bounds.html index e86b711c7..202a7a668 100644 --- a/spine-ts/spine-pixi-v7/example/bounds.html +++ b/spine-ts/spine-pixi-v7/example/bounds.html @@ -29,21 +29,21 @@ // Create the spine display object - const spineboy1 = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: .2 }); + const spineboy1 = new spine.Spine({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: .2 }); - const spineboy2 = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: .2, + const spineboy2 = new spine.Spine({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: .2, boundsProvider: new spine.SetupPoseBoundsProvider(), }); - const spineboy3 = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: .2, + const spineboy3 = new spine.Spine({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, + const spineboy4 = new spine.Spine({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, + const spineboy5 = new spine.Spine({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: .2, boundsProvider: new spine.AABBRectangleBoundsProvider(-100, -100, 100, 100), }); diff --git a/spine-ts/spine-pixi-v7/example/bunnymark.html b/spine-ts/spine-pixi-v7/example/bunnymark.html index fa8cccf42..eb97485d0 100644 --- a/spine-ts/spine-pixi-v7/example/bunnymark.html +++ b/spine-ts/spine-pixi-v7/example/bunnymark.html @@ -88,7 +88,7 @@ bounds; constructor(bounds) { - const spineboy = spine.Spine.from({ + const spineboy = new spine.Spine({ atlas: "spineboyAtlas", skeleton: "spineboyData", scale: 0.125, diff --git a/spine-ts/spine-pixi-v7/example/control-bones-example.html b/spine-ts/spine-pixi-v7/example/control-bones-example.html index 821aa4609..141903f99 100644 --- a/spine-ts/spine-pixi-v7/example/control-bones-example.html +++ b/spine-ts/spine-pixi-v7/example/control-bones-example.html @@ -45,7 +45,7 @@ await PIXI.Assets.load(["stretchymanData", "stretchymanAtlas"]); // Create the spine display object - const stretchyman = spine.Spine.from({skeleton: "stretchymanData", atlas: "stretchymanAtlas", + const stretchyman = new spine.Spine({skeleton: "stretchymanData", atlas: "stretchymanAtlas", scale: 0.75, }); diff --git a/spine-ts/spine-pixi-v7/example/events-example.html b/spine-ts/spine-pixi-v7/example/events-example.html index 4501d3728..0d495ee5c 100644 --- a/spine-ts/spine-pixi-v7/example/events-example.html +++ b/spine-ts/spine-pixi-v7/example/events-example.html @@ -39,7 +39,7 @@ await PIXI.Assets.load(["spineboyData", "spineboyAtlas"]); // Create the Spine display object - const spineboy = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", + const spineboy = new spine.Spine({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: 0.5, }); diff --git a/spine-ts/spine-pixi-v7/example/index.html b/spine-ts/spine-pixi-v7/example/index.html index 6f182d0cf..e1cb2af0d 100644 --- a/spine-ts/spine-pixi-v7/example/index.html +++ b/spine-ts/spine-pixi-v7/example/index.html @@ -28,7 +28,7 @@ await PIXI.Assets.load(["spineboyData", "spineboyAtlas"]); // Create the spine display object - const spineboy = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", + const spineboy = new spine.Spine({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: 0.5, }); diff --git a/spine-ts/spine-pixi-v7/example/inline-loading.html b/spine-ts/spine-pixi-v7/example/inline-loading.html index 1d7fd6554..390014fad 100644 --- a/spine-ts/spine-pixi-v7/example/inline-loading.html +++ b/spine-ts/spine-pixi-v7/example/inline-loading.html @@ -53,7 +53,7 @@ await PIXI.Assets.load(["skelBase64", "atlasBase64"]); // creating spine game object with base64 data - const base64GameObject = spine.Spine.from({skeleton: "skelBase64", atlas: "atlasBase64" }); + const base64GameObject = new spine.Spine({skeleton: "skelBase64", atlas: "atlasBase64" }); app.stage.addChild(base64GameObject); base64GameObject.state.setAnimation(0, "animation", true); base64GameObject.position.set(window.innerWidth / 3, window.innerHeight / 2); @@ -91,7 +91,7 @@ await PIXI.Assets.load(["skelBlob", "atlasBlob"]); // creating spine game object - const blobGameObject = spine.Spine.from({skeleton: "skelBase64", atlas: "atlasBase64" }); + const blobGameObject = new spine.Spine({skeleton: "skelBase64", atlas: "atlasBase64" }); app.stage.addChild(blobGameObject); blobGameObject.state.setAnimation(0, "animation", true); blobGameObject.position.set(window.innerWidth / 3 * 2, window.innerHeight / 2); diff --git a/spine-ts/spine-pixi-v7/example/mix-and-match-example.html b/spine-ts/spine-pixi-v7/example/mix-and-match-example.html index b60111088..ca1261f0b 100644 --- a/spine-ts/spine-pixi-v7/example/mix-and-match-example.html +++ b/spine-ts/spine-pixi-v7/example/mix-and-match-example.html @@ -28,7 +28,7 @@ await PIXI.Assets.load(["mixAndMatchData", "mixAndMatchAtlas"]); // Create the Spine display object - const mixAndMatch = spine.Spine.from({skeleton: "mixAndMatchData", atlas: "mixAndMatchAtlas", + const mixAndMatch = new spine.Spine({skeleton: "mixAndMatchData", atlas: "mixAndMatchAtlas", scale: 0.5, }); diff --git a/spine-ts/spine-pixi-v7/example/mouse-following.html b/spine-ts/spine-pixi-v7/example/mouse-following.html index 4d1f91daf..f141d4131 100644 --- a/spine-ts/spine-pixi-v7/example/mouse-following.html +++ b/spine-ts/spine-pixi-v7/example/mouse-following.html @@ -28,7 +28,7 @@ await PIXI.Assets.load(["spineboyData", "spineboyAtlas"]); // Create the spine display object - const spineboy = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", + const spineboy = new spine.Spine({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: 0.5, }); diff --git a/spine-ts/spine-pixi-v7/example/physics.html b/spine-ts/spine-pixi-v7/example/physics.html index 4f69d77e1..791ebb64f 100644 --- a/spine-ts/spine-pixi-v7/example/physics.html +++ b/spine-ts/spine-pixi-v7/example/physics.html @@ -28,7 +28,7 @@ await PIXI.Assets.load(["sackData", "sackAtlas"]); // Create the spine display object - const sack = spine.Spine.from({skeleton: "sackData", atlas: "sackAtlas", + const sack = new spine.Spine({skeleton: "sackData", atlas: "sackAtlas", scale: 0.2, }); diff --git a/spine-ts/spine-pixi-v7/example/physics2.html b/spine-ts/spine-pixi-v7/example/physics2.html index 241fcef2b..b3b71a72f 100644 --- a/spine-ts/spine-pixi-v7/example/physics2.html +++ b/spine-ts/spine-pixi-v7/example/physics2.html @@ -28,7 +28,7 @@ await PIXI.Assets.load(["girlData", "girlAtlas"]); // Create the spine display object - const girl = spine.Spine.from({skeleton: "girlData", atlas: "girlAtlas", + const girl = new spine.Spine({skeleton: "girlData", atlas: "girlAtlas", scale: 0.2, }); diff --git a/spine-ts/spine-pixi-v7/example/physics3.html b/spine-ts/spine-pixi-v7/example/physics3.html index 0df300c29..5409851a3 100644 --- a/spine-ts/spine-pixi-v7/example/physics3.html +++ b/spine-ts/spine-pixi-v7/example/physics3.html @@ -28,7 +28,7 @@ await PIXI.Assets.load(["snowglobeData", "snowglobeAtlas"]); // Create the spine display object - const snowglobe = spine.Spine.from({skeleton: "snowglobeData", atlas: "snowglobeAtlas", + const snowglobe = new spine.Spine({skeleton: "snowglobeData", atlas: "snowglobeAtlas", scale: 0.25, }); diff --git a/spine-ts/spine-pixi-v7/example/physics4.html b/spine-ts/spine-pixi-v7/example/physics4.html index 980a85e60..53667f0ee 100644 --- a/spine-ts/spine-pixi-v7/example/physics4.html +++ b/spine-ts/spine-pixi-v7/example/physics4.html @@ -28,7 +28,7 @@ await PIXI.Assets.load(["cloudPotData", "cloudPotAtlas"]); // Create the spine display object - const cloudPot = spine.Spine.from({skeleton: "cloudPotData", atlas: "cloudPotAtlas", + const cloudPot = new spine.Spine({skeleton: "cloudPotData", atlas: "cloudPotAtlas", scale: 0.25, }); diff --git a/spine-ts/spine-pixi-v7/example/simple-input.html b/spine-ts/spine-pixi-v7/example/simple-input.html index 44d77182d..800b4a14c 100644 --- a/spine-ts/spine-pixi-v7/example/simple-input.html +++ b/spine-ts/spine-pixi-v7/example/simple-input.html @@ -28,7 +28,7 @@ await PIXI.Assets.load(["spineboyData", "spineboyAtlas"]); // Create the spine display object - const spineboy = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", + const spineboy = new spine.Spine({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: 0.5, }); diff --git a/spine-ts/spine-pixi-v7/example/slot-objects-scale-rotation.html b/spine-ts/spine-pixi-v7/example/slot-objects-scale-rotation.html index c4b7da21d..b7a4ee360 100644 --- a/spine-ts/spine-pixi-v7/example/slot-objects-scale-rotation.html +++ b/spine-ts/spine-pixi-v7/example/slot-objects-scale-rotation.html @@ -36,7 +36,7 @@ PIXI.Assets.cache.set("spineboyAtlas", textureAtlas); // creating spine game object - const spineGO = spine.Spine.from({skeleton: "jsonSkel", atlas: "spineboyAtlas" }); + const spineGO = new spine.Spine({skeleton: "jsonSkel", atlas: "spineboyAtlas" }); spineGO.position.set(300, 300); app.stage.addChild(spineGO); diff --git a/spine-ts/spine-pixi-v7/example/slot-objects.html b/spine-ts/spine-pixi-v7/example/slot-objects.html index 4163b1d5a..4ba92d702 100644 --- a/spine-ts/spine-pixi-v7/example/slot-objects.html +++ b/spine-ts/spine-pixi-v7/example/slot-objects.html @@ -28,7 +28,7 @@ await PIXI.Assets.load(["spineboyData", "spineboyAtlas"]); // Create the spine display object - const spineboy = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", + const spineboy = new spine.Spine({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: 0.25, }); diff --git a/spine-ts/spine-pixi-v7/example/typescript/index.ts b/spine-ts/spine-pixi-v7/example/typescript/index.ts index df3d8237b..e939fa0eb 100644 --- a/spine-ts/spine-pixi-v7/example/typescript/index.ts +++ b/spine-ts/spine-pixi-v7/example/typescript/index.ts @@ -1,5 +1,5 @@ -import { Application, Assets } from 'pixi.js'; import { Spine } from '@esotericsoftware/spine-pixi-v7'; +import { Application, Assets } from 'pixi.js'; /** The PixiJS app Application instance, shared across the project */ export const app = new Application({ @@ -23,7 +23,8 @@ async function init () { await Assets.load(["spineboyData", "spineboyAtlas"]); // Create the spine display object - const spineboy = Spine.from("spineboyData", "spineboyAtlas", { + const spineboy = new Spine({ + skeleton: "spineboyData", atlas: "spineboyAtlas", scale: 0.5, }); diff --git a/spine-ts/spine-pixi-v7/src/Spine.ts b/spine-ts/spine-pixi-v7/src/Spine.ts index 4bf86c032..68f446c98 100644 --- a/spine-ts/spine-pixi-v7/src/Spine.ts +++ b/spine-ts/spine-pixi-v7/src/Spine.ts @@ -40,7 +40,7 @@ import { Skeleton, SkeletonBinary, SkeletonClipping, - SkeletonData, + type SkeletonData, SkeletonJson, Skin, Utils, @@ -247,7 +247,8 @@ export class SkinsAndAnimationBoundsProvider /** * 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. + * Create and customize the default configuration using the static method {@link Spine.createOptions}, + * then pass it to the constructor. */ export class Spine extends Container { /** The skeleton for this Spine game object. */ @@ -328,29 +329,23 @@ export class Spine extends Container { private _boundsSpineID = -1; private _boundsSpineDirty = true; - constructor (options: SpineOptions | SkeletonData) { - if (options instanceof SkeletonData) { - options = { - skeletonData: options, - }; - } - + constructor (options: SpineOptions | SpineFromOptions) { super(); - const skeletonData = options instanceof SkeletonData ? options : options.skeletonData; + if ("skeleton" in options) + options = new.target.createOptions(options); + const { autoUpdate = true, boundsProvider, darkTint, skeletonData } = options; this.skeleton = new Skeleton(skeletonData); - const animData = new AnimationStateData(skeletonData); - this.state = new AnimationState(animData); - this.autoUpdate = options?.autoUpdate ?? true; + this.state = new AnimationState(new AnimationStateData(skeletonData)); + this.autoUpdate = autoUpdate; + this.boundsProvider = boundsProvider; // dark tint can be enabled by options, otherwise is enable if at least one slot has tint black - this.darkTint = options?.darkTint === undefined + this.darkTint = darkTint === undefined ? this.skeleton.slots.some(slot => !!slot.data.setup.darkColor) - : options?.darkTint; + : darkTint; if (this.darkTint) this.slotMeshFactory = () => new DarkSlotMesh(); - - this.boundsProvider = options.boundsProvider; } /** If {@link Spine.autoUpdate} is `false`, this method allows to update the AnimationState and the Skeleton with the given delta. */ @@ -861,7 +856,7 @@ export class Spine extends Container { public static readonly skeletonCache: Record = Object.create(null); /** - * Use this method to instantiate a Spine game object. + * Get a convenient initialization configuration for your Spine game object. * Before instantiating a Spine game object, the skeleton (`.skel` or `.json`) and the atlas text files must be loaded into the Assets. For example: * ``` * PIXI.Assets.add("sackData", "/assets/sack-pro.skel"); @@ -872,9 +867,9 @@ export class Spine extends Container { * `${skeletonAssetName}-${atlasAssetName}-${options?.scale ?? 1}` * * @param options - Options to configure the Spine game object. See {@link SpineFromOptions} - * @returns {Spine} The Spine game object instantiated + * @returns {SpineOptions} The configuration ready to be passed to the Spine constructor */ - public static from ({ skeleton, atlas, scale = 1, darkTint, autoUpdate = true, boundsProvider, allowMissingRegions }: SpineFromOptions) { + public static createOptions ({ skeleton, atlas, scale = 1, darkTint, autoUpdate = true, boundsProvider, allowMissingRegions }: SpineFromOptions): SpineOptions { const cacheKey = `${skeleton}-${atlas}-${scale}`; let skeletonData = Spine.skeletonCache[cacheKey]; @@ -888,7 +883,25 @@ export class Spine extends Container { skeletonData = parser.readSkeletonData(skeletonAsset); Spine.skeletonCache[cacheKey] = skeletonData; } - return new Spine({ skeletonData, darkTint, autoUpdate, boundsProvider }); + return { skeletonData, darkTint, autoUpdate, boundsProvider }; + } + + /** + * @deprecated Use directly the Spine constructor or {@link createOptions} to make options and customize it to pass to the constructor + * Before instantiating a Spine game object, the skeleton (`.skel` or `.json`) and the atlas text files must be loaded into the Assets. For example: + * ``` + * PIXI.Assets.add("sackData", "/assets/sack-pro.skel"); + * PIXI.Assets.add("sackAtlas", "/assets/sack-pma.atlas"); + * await PIXI.Assets.load(["sackData", "sackAtlas"]); + * ``` + * Once a Spine game object is created, its skeleton data is cached into {@link Spine.skeletonCache} using the key: + * `${skeletonAssetName}-${atlasAssetName}-${options?.scale ?? 1}` + * + * @param options - Options to configure the Spine game object. See {@link SpineFromOptions} + * @returns {Spine} The Spine game object instantiated + */ + public static from (options: SpineFromOptions) { + return new Spine(Spine.createOptions(options)); } public get tint (): number { diff --git a/spine-ts/spine-pixi-v8/example/bounds.html b/spine-ts/spine-pixi-v8/example/bounds.html index 518e74ee4..749c750bd 100644 --- a/spine-ts/spine-pixi-v8/example/bounds.html +++ b/spine-ts/spine-pixi-v8/example/bounds.html @@ -29,21 +29,21 @@ await PIXI.Assets.load(["spineboyData", "spineboyAtlas"]); // Create the spine display object - const spineboy1 = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: .2 }); + const spineboy1 = new spine.Spine({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: .2 }); - const spineboy2 = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: .2, + const spineboy2 = new spine.Spine({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: .2, boundsProvider: new spine.SetupPoseBoundsProvider(), }); - const spineboy3 = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: .2, + const spineboy3 = new spine.Spine({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, + const spineboy4 = new spine.Spine({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, + const spineboy5 = new spine.Spine({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: .2, boundsProvider: new spine.AABBRectangleBoundsProvider(-100, -100, 100, 100), }); diff --git a/spine-ts/spine-pixi-v8/example/bunnymark.html b/spine-ts/spine-pixi-v8/example/bunnymark.html index 26b16f5a4..e11b106c3 100644 --- a/spine-ts/spine-pixi-v8/example/bunnymark.html +++ b/spine-ts/spine-pixi-v8/example/bunnymark.html @@ -98,7 +98,7 @@ bounds; constructor(bounds) { - const spineboy = spine.Spine.from({ + const spineboy = new spine.Spine({ atlas: "spineboyAtlas", skeleton: "spineboyData", scale: 0.125, diff --git a/spine-ts/spine-pixi-v8/example/coin.html b/spine-ts/spine-pixi-v8/example/coin.html index 631de00cb..e12e2244d 100644 --- a/spine-ts/spine-pixi-v8/example/coin.html +++ b/spine-ts/spine-pixi-v8/example/coin.html @@ -30,7 +30,7 @@ await PIXI.Assets.load(["spineboyData", "spineboyAtlas"]); // Create the spine display object - const spineboy = spine.Spine.from({ + const spineboy = new spine.Spine({ skeleton: "spineboyData", atlas: "spineboyAtlas", scale: 1, diff --git a/spine-ts/spine-pixi-v8/example/control-bones-example.html b/spine-ts/spine-pixi-v8/example/control-bones-example.html index e52318b0c..599f93161 100644 --- a/spine-ts/spine-pixi-v8/example/control-bones-example.html +++ b/spine-ts/spine-pixi-v8/example/control-bones-example.html @@ -45,7 +45,7 @@ await PIXI.Assets.load(["stretchymanData", "stretchymanAtlas"]); // Create the spine display object - const stretchyman = spine.Spine.from({skeleton: "stretchymanData", atlas: "stretchymanAtlas", + const stretchyman = new spine.Spine({skeleton: "stretchymanData", atlas: "stretchymanAtlas", scale: 0.75, }); diff --git a/spine-ts/spine-pixi-v8/example/dragon.html b/spine-ts/spine-pixi-v8/example/dragon.html index 6caea180e..5c711879e 100644 --- a/spine-ts/spine-pixi-v8/example/dragon.html +++ b/spine-ts/spine-pixi-v8/example/dragon.html @@ -32,8 +32,8 @@ await PIXI.Assets.load(["spineboyData", "spineboyAtlas", "spineboyData", "spineboyAtlas2"]); // Create the spine display object - const spineboy = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: 0.5 }); - const spineboy2 = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: 0.5 }); + const spineboy = new spine.Spine({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: 0.5 }); + const spineboy2 = new spine.Spine({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: 0.5 }); spineboy.autoUpdate = false; spineboy2.autoUpdate = false; diff --git a/spine-ts/spine-pixi-v8/example/events-example.html b/spine-ts/spine-pixi-v8/example/events-example.html index 60e48b06d..d9ad0b09b 100644 --- a/spine-ts/spine-pixi-v8/example/events-example.html +++ b/spine-ts/spine-pixi-v8/example/events-example.html @@ -39,7 +39,7 @@ await PIXI.Assets.load(["spineboyData", "spineboyAtlas"]); // Create the Spine display object - const spineboy = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", + const spineboy = new spine.Spine({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: 0.5, }); diff --git a/spine-ts/spine-pixi-v8/example/index.html b/spine-ts/spine-pixi-v8/example/index.html index 4d9a4ef61..990c0a868 100644 --- a/spine-ts/spine-pixi-v8/example/index.html +++ b/spine-ts/spine-pixi-v8/example/index.html @@ -28,7 +28,7 @@ await PIXI.Assets.load(["spineboyData", "spineboyAtlas"]); // Create the spine display object - const spineboy = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", + const spineboy = new spine.Spine({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: 0.5, }); diff --git a/spine-ts/spine-pixi-v8/example/inline-loading.html b/spine-ts/spine-pixi-v8/example/inline-loading.html index 0319f80aa..38b4e28c3 100644 --- a/spine-ts/spine-pixi-v8/example/inline-loading.html +++ b/spine-ts/spine-pixi-v8/example/inline-loading.html @@ -52,7 +52,7 @@ await PIXI.Assets.load(["skelBase64", "atlasBase64"]); // creating spine game object with base64 data - const base64GameObject = spine.Spine.from({skeleton: "skelBase64", atlas: "atlasBase64" }); + const base64GameObject = new spine.Spine({skeleton: "skelBase64", atlas: "atlasBase64" }); app.stage.addChild(base64GameObject); base64GameObject.state.setAnimation(0, "animation", true); base64GameObject.position.set(window.innerWidth / 3, window.innerHeight / 2); @@ -90,7 +90,7 @@ await PIXI.Assets.load(["skelBlob", "atlasBlob"]); // creating spine game object - const blobGameObject = spine.Spine.from({skeleton: "skelBase64", atlas: "atlasBase64" }); + const blobGameObject = new spine.Spine({skeleton: "skelBase64", atlas: "atlasBase64" }); app.stage.addChild(blobGameObject); blobGameObject.state.setAnimation(0, "animation", true); blobGameObject.position.set(window.innerWidth / 3 * 2, window.innerHeight / 2); diff --git a/spine-ts/spine-pixi-v8/example/mix-and-match-example.html b/spine-ts/spine-pixi-v8/example/mix-and-match-example.html index afd8d4f96..26b9bebe4 100644 --- a/spine-ts/spine-pixi-v8/example/mix-and-match-example.html +++ b/spine-ts/spine-pixi-v8/example/mix-and-match-example.html @@ -28,7 +28,7 @@ await PIXI.Assets.load(["mixAndMatchData", "mixAndMatchAtlas"]); // Create the Spine display object - const mixAndMatch = spine.Spine.from({skeleton: "mixAndMatchData", atlas: "mixAndMatchAtlas", + const mixAndMatch = new spine.Spine({skeleton: "mixAndMatchData", atlas: "mixAndMatchAtlas", scale: 0.5, }); diff --git a/spine-ts/spine-pixi-v8/example/mouse-following.html b/spine-ts/spine-pixi-v8/example/mouse-following.html index 9e94efe12..0fad584c7 100644 --- a/spine-ts/spine-pixi-v8/example/mouse-following.html +++ b/spine-ts/spine-pixi-v8/example/mouse-following.html @@ -28,7 +28,7 @@ await PIXI.Assets.load(["spineboyData", "spineboyAtlas"]); // Create the spine display object - const spineboy = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", + const spineboy = new spine.Spine({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: 0.5, }); diff --git a/spine-ts/spine-pixi-v8/example/physics.html b/spine-ts/spine-pixi-v8/example/physics.html index c393bd16c..81391dc86 100644 --- a/spine-ts/spine-pixi-v8/example/physics.html +++ b/spine-ts/spine-pixi-v8/example/physics.html @@ -28,7 +28,7 @@ await PIXI.Assets.load(["sackData", "sackAtlas"]); // Create the spine display object - const sack = spine.Spine.from({skeleton: "sackData", atlas: "sackAtlas", + const sack = new spine.Spine({skeleton: "sackData", atlas: "sackAtlas", scale: 0.2, }); diff --git a/spine-ts/spine-pixi-v8/example/physics2.html b/spine-ts/spine-pixi-v8/example/physics2.html index ccb5f305d..b34ee0018 100644 --- a/spine-ts/spine-pixi-v8/example/physics2.html +++ b/spine-ts/spine-pixi-v8/example/physics2.html @@ -28,7 +28,7 @@ await PIXI.Assets.load(["girlData", "girlAtlas"]); // Create the spine display object - const girl = spine.Spine.from({skeleton: "girlData", atlas: "girlAtlas", + const girl = new spine.Spine({skeleton: "girlData", atlas: "girlAtlas", scale: 0.2, }); diff --git a/spine-ts/spine-pixi-v8/example/physics3.html b/spine-ts/spine-pixi-v8/example/physics3.html index f3e5d4941..984551b9a 100644 --- a/spine-ts/spine-pixi-v8/example/physics3.html +++ b/spine-ts/spine-pixi-v8/example/physics3.html @@ -28,7 +28,7 @@ await PIXI.Assets.load(["snowglobeData", "snowglobeAtlas"]); // Create the spine display object - const snowglobe = spine.Spine.from({skeleton: "snowglobeData", atlas: "snowglobeAtlas", + const snowglobe = new spine.Spine({skeleton: "snowglobeData", atlas: "snowglobeAtlas", scale: 0.25, }); diff --git a/spine-ts/spine-pixi-v8/example/physics4.html b/spine-ts/spine-pixi-v8/example/physics4.html index 371262f9f..632ff2fe8 100644 --- a/spine-ts/spine-pixi-v8/example/physics4.html +++ b/spine-ts/spine-pixi-v8/example/physics4.html @@ -28,7 +28,7 @@ await PIXI.Assets.load(["cloudPotData", "cloudPotAtlas"]); // Create the spine display object - const cloudPot = spine.Spine.from({skeleton: "cloudPotData", atlas: "cloudPotAtlas", + const cloudPot = new spine.Spine({skeleton: "cloudPotData", atlas: "cloudPotAtlas", scale: 0.25, }); diff --git a/spine-ts/spine-pixi-v8/example/simple-input.html b/spine-ts/spine-pixi-v8/example/simple-input.html index 871889999..fa248ccac 100644 --- a/spine-ts/spine-pixi-v8/example/simple-input.html +++ b/spine-ts/spine-pixi-v8/example/simple-input.html @@ -28,7 +28,7 @@ await PIXI.Assets.load(["spineboyData", "spineboyAtlas"]); // Create the spine display object - const spineboy = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", + const spineboy = new spine.Spine({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: 0.5, }); diff --git a/spine-ts/spine-pixi-v8/example/slot-objects-scale-rotation.html b/spine-ts/spine-pixi-v8/example/slot-objects-scale-rotation.html index 0abc5b199..0bca36e6e 100644 --- a/spine-ts/spine-pixi-v8/example/slot-objects-scale-rotation.html +++ b/spine-ts/spine-pixi-v8/example/slot-objects-scale-rotation.html @@ -35,7 +35,7 @@ PIXI.Assets.cache.set("spineboyAtlas", textureAtlas); // creating spine game object - const spineGO = spine.Spine.from({skeleton: "jsonSkel", atlas: "spineboyAtlas" }); + const spineGO = new spine.Spine({ skeleton: "jsonSkel", atlas: "spineboyAtlas" }); spineGO.position.set(300, 300); app.stage.addChild(spineGO); diff --git a/spine-ts/spine-pixi-v8/example/slot-objects.html b/spine-ts/spine-pixi-v8/example/slot-objects.html index 807f2de25..90f857e24 100644 --- a/spine-ts/spine-pixi-v8/example/slot-objects.html +++ b/spine-ts/spine-pixi-v8/example/slot-objects.html @@ -29,7 +29,7 @@ await PIXI.Assets.load(["spineboyData", "spineboyAtlas", "raptor_jaw"]); // Create the spine display object - const spineboy = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: 0.25 }); + const spineboy = new spine.Spine({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: 0.25 }); // Set the default mix time to use when transitioning // from one animation to the next. diff --git a/spine-ts/spine-pixi-v8/example/typescript/index.ts b/spine-ts/spine-pixi-v8/example/typescript/index.ts index 62ebaff78..9f21a9d7f 100644 --- a/spine-ts/spine-pixi-v8/example/typescript/index.ts +++ b/spine-ts/spine-pixi-v8/example/typescript/index.ts @@ -1,5 +1,5 @@ -import { Application, Assets } from 'pixi.js'; import { Spine } from '@esotericsoftware/spine-pixi-v8'; +import { Application, Assets } from 'pixi.js'; /** The PixiJS app Application instance, shared across the project */ export const app = new Application(); @@ -25,7 +25,7 @@ async function init () { await Assets.load(["spineboyData", "spineboyAtlas"]); // Create the spine display object - const spineboy = Spine.from({ + const spineboy = new Spine({ atlas: "spineboyAtlas", skeleton: "spineboyData", scale: 0.5, diff --git a/spine-ts/spine-pixi-v8/src/Spine.ts b/spine-ts/spine-pixi-v8/src/Spine.ts index b9dae2393..127da20e9 100644 --- a/spine-ts/spine-pixi-v8/src/Spine.ts +++ b/spine-ts/spine-pixi-v8/src/Spine.ts @@ -43,7 +43,7 @@ import { SkeletonBinary, SkeletonBounds, SkeletonClipping, - SkeletonData, + type SkeletonData, SkeletonJson, Skin, type Slot, @@ -292,7 +292,8 @@ const maskPool = new Pool(() => new Graphics); /** * 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. + * Create and customize the default configuration using the static method {@link Spine.createOptions}, + * then pass it to the constructor. */ export class Spine extends ViewContainer { // Pixi properties @@ -384,35 +385,28 @@ export class Spine extends ViewContainer { } private hasNeverUpdated = true; - constructor (options: SpineOptions | SkeletonData) { - if (options instanceof SkeletonData) { - options = { - skeletonData: options, - }; - } - + constructor (options: SpineOptions | SpineFromOptions) { super({}); + if ("skeleton" in options) + options = new.target.createOptions(options); + this.allowChildren = true; - const skeletonData = options instanceof SkeletonData ? options : options.skeletonData; - + const { autoUpdate, boundsProvider, darkTint, skeletonData } = options; this.skeleton = new Skeleton(skeletonData); this.state = new AnimationState(new AnimationStateData(skeletonData)); - this.autoUpdate = options?.autoUpdate ?? true; + this.autoUpdate = autoUpdate ?? true; + this._boundsProvider = boundsProvider; // dark tint can be enabled by options, otherwise is enable if at least one slot has tint black - this.darkTint = options?.darkTint === undefined + this.darkTint = darkTint === undefined ? this.skeleton.slots.some(slot => !!slot.data.setup.darkColor) - : options?.darkTint; + : darkTint; const slots = this.skeleton.slots; - - for (let i = 0; i < slots.length; i++) { + for (let i = 0; i < slots.length; i++) this.attachmentCacheData[i] = Object.create(null); - } - - this._boundsProvider = options.boundsProvider; } /** If {@link Spine.autoUpdate} is `false`, this method allows to update the AnimationState and the Skeleton with the given delta. */ @@ -1065,6 +1059,55 @@ export class Spine extends ViewContainer { } /** + * Get a convenient initialization configuration for your Spine game object. + * Before instantiating a Spine game object, the skeleton (`.skel` or `.json`) and the atlas text files must be loaded into the {@link Assets}. For example: + * ``` + * PIXI.Assets.add("sackData", "/assets/sack-pro.skel"); + * PIXI.Assets.add("sackAtlas", "/assets/sack-pma.atlas"); + * await PIXI.Assets.load(["sackData", "sackAtlas"]); + * ``` + * Once a Spine game object is created, its skeleton data is cached into {@link Cache} using the key: + * `${skeletonAssetName}-${atlasAssetName}-${options?.scale ?? 1}` + * + * @param options - Options to configure the Spine game object. See {@link SpineFromOptions} + * @returns {SpineOptions} The configuration ready to be passed to the Spine constructor + */ + static createOptions ({ skeleton, atlas, scale = 1, darkTint, autoUpdate = true, boundsProvider, allowMissingRegions = false }: SpineFromOptions): SpineOptions { + const cacheKey = `${skeleton}-${atlas}-${scale}`; + + if (Cache.has(cacheKey)) { + return { + skeletonData: Cache.get(cacheKey), + darkTint, + autoUpdate, + boundsProvider, + }; + } + + const atlasAsset = Assets.get(atlas); + const attachmentLoader = new AtlasAttachmentLoader(atlasAsset, allowMissingRegions); + + // biome-ignore lint/suspicious/noExplicitAny: json skeleton data is any + const skeletonAsset = Assets.get(skeleton); + const parser = skeletonAsset instanceof Uint8Array + ? new SkeletonBinary(attachmentLoader) + : new SkeletonJson(attachmentLoader); + + parser.scale = scale; + const skeletonData = parser.readSkeletonData(skeletonAsset); + + Cache.set(cacheKey, skeletonData); + + return { + skeletonData, + darkTint, + autoUpdate, + boundsProvider, + }; + } + + /** + * @deprecated Use directly the Spine constructor or {@link createOptions} to make options and customize it to pass to the constructor * Use this method to instantiate a Spine game object. * Before instantiating a Spine game object, the skeleton (`.skel` or `.json`) and the atlas text files must be loaded into the Assets. For example: * ``` @@ -1078,36 +1121,7 @@ export class Spine extends ViewContainer { * @param options - Options to configure the Spine game object. See {@link SpineFromOptions} * @returns {Spine} The Spine game object instantiated */ - static from ({ skeleton, atlas, scale = 1, darkTint, autoUpdate = true, boundsProvider, allowMissingRegions = false }: SpineFromOptions) { - const cacheKey = `${skeleton}-${atlas}-${scale}`; - - if (Cache.has(cacheKey)) { - return new Spine({ - skeletonData: Cache.get(cacheKey), - darkTint, - autoUpdate, - boundsProvider, - }); - } - - const skeletonAsset = Assets.get(skeleton); - - const atlasAsset = Assets.get(atlas); - const attachmentLoader = new AtlasAttachmentLoader(atlasAsset, allowMissingRegions); - const parser = skeletonAsset instanceof Uint8Array - ? new SkeletonBinary(attachmentLoader) - : new SkeletonJson(attachmentLoader); - - parser.scale = scale; - const skeletonData = parser.readSkeletonData(skeletonAsset); - - Cache.set(cacheKey, skeletonData); - - return new Spine({ - skeletonData, - darkTint, - autoUpdate, - boundsProvider, - }); + static from (options: SpineFromOptions) { + return new Spine(Spine.createOptions(options)); } }