[ts][pixi-v7][pixi-v8] Add ticker properties to provide custom ticker. See #3031.

This commit is contained in:
Davide Tantillo 2026-02-24 10:55:33 +01:00
parent cbb49edd2a
commit 7396c1cb1d
2 changed files with 63 additions and 14 deletions

View File

@ -102,6 +102,9 @@ export interface SpineFromOptions {
/** 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. */
boundsProvider?: SpineBoundsProvider, boundsProvider?: SpineBoundsProvider,
/** The ticker to use when {@link autoUpdate} is `true`. Defaults to {@link Ticker.shared}. */
ticker?: Ticker,
}; };
export interface SpineOptions { export interface SpineOptions {
@ -116,6 +119,9 @@ export interface SpineOptions {
/** See {@link SpineFromOptions.boundsProvider}. */ /** See {@link SpineFromOptions.boundsProvider}. */
boundsProvider?: SpineBoundsProvider, boundsProvider?: SpineBoundsProvider,
/** See {@link SpineFromOptions.ticker}. */
ticker?: Ticker,
} }
/** /**
@ -298,19 +304,37 @@ export class Spine extends Container {
afterUpdateWorldTransforms: (object: Spine) => void = () => { }; afterUpdateWorldTransforms: (object: Spine) => void = () => { };
private _autoUpdate: boolean = false; private _autoUpdate: boolean = false;
private _ticker: Ticker = Ticker.shared;
public get autoUpdate (): boolean { public get autoUpdate (): boolean {
return this._autoUpdate; return this._autoUpdate;
} }
/** When `true`, the Spine AnimationState and the Skeleton will be automatically updated using the {@link Ticker.shared} instance. */ /** When `true`, the Spine AnimationState and the Skeleton will be automatically updated using the {@link ticker}. */
public set autoUpdate (value: boolean) { public set autoUpdate (value: boolean) {
if (value && !this._autoUpdate) { if (value && !this._autoUpdate) {
Ticker.shared.add(this.internalUpdate, this); this._ticker.add(this.internalUpdate, this);
} else if (!value && this._autoUpdate) { } else if (!value && this._autoUpdate) {
Ticker.shared.remove(this.internalUpdate, this); this._ticker.remove(this.internalUpdate, this);
} }
this._autoUpdate = value; this._autoUpdate = value;
} }
/** The ticker to use when {@link autoUpdate} is `true`. Defaults to {@link Ticker.shared}. */
public get ticker (): Ticker {
return this._ticker;
}
/** Sets the ticker to use when {@link autoUpdate} is `true`. If `autoUpdate` is already `true`, the update callback will be moved from the old ticker to the new one. */
public set ticker (value: Ticker) {
if (this._ticker === value) return;
if (this._autoUpdate) {
this._ticker.remove(this.internalUpdate, this);
value.add(this.internalUpdate, this);
}
this._ticker = value;
}
private meshesCache = new Map<Slot, ISlotMesh>(); private meshesCache = new Map<Slot, ISlotMesh>();
private static vectorAux: Vector2 = new Vector2(); private static vectorAux: Vector2 = new Vector2();
@ -374,6 +398,7 @@ export class Spine extends Container {
this.initializeMeshFactory(oldOptions?.slotMeshFactory); this.initializeMeshFactory(oldOptions?.slotMeshFactory);
} }
if (options?.ticker) this._ticker = options.ticker;
this.autoUpdate = options?.autoUpdate ?? true; this.autoUpdate = options?.autoUpdate ?? true;
this.boundsProvider = options.boundsProvider; this.boundsProvider = options.boundsProvider;
@ -408,7 +433,7 @@ export class Spine extends Container {
this.hasNeverUpdated = false; this.hasNeverUpdated = false;
// Because reasons, pixi uses deltaFrames at 60fps. We ignore the default deltaFrames and use the deltaSeconds from pixi ticker. // Because reasons, pixi uses deltaFrames at 60fps. We ignore the default deltaFrames and use the deltaSeconds from pixi ticker.
const delta = deltaSeconds ?? Ticker.shared.deltaMS / 1000; const delta = deltaSeconds ?? this._ticker.deltaMS / 1000;
this.state.update(delta); this.state.update(delta);
this.state.apply(this.skeleton); this.state.apply(this.skeleton);
this.beforeUpdateWorldTransforms(this); this.beforeUpdateWorldTransforms(this);
@ -966,7 +991,7 @@ export class Spine extends Container {
return Spine.oldFrom(paramOne, atlasAssetName!, options); return Spine.oldFrom(paramOne, atlasAssetName!, options);
} }
const { skeleton, atlas, scale = 1, darkTint, autoUpdate, boundsProvider } = paramOne; const { skeleton, atlas, scale = 1, darkTint, autoUpdate, boundsProvider, ticker } = 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) {
@ -978,7 +1003,7 @@ export class Spine extends Container {
skeletonData = parser.readSkeletonData(skeletonAsset); skeletonData = parser.readSkeletonData(skeletonAsset);
Spine.skeletonCache[cacheKey] = skeletonData; Spine.skeletonCache[cacheKey] = skeletonData;
} }
return new Spine({ skeletonData, darkTint, autoUpdate, boundsProvider }); return new Spine({ skeletonData, darkTint, autoUpdate, boundsProvider, ticker });
} }

View File

@ -92,6 +92,9 @@ export interface SpineFromOptions {
/** 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. */
boundsProvider?: SpineBoundsProvider, boundsProvider?: SpineBoundsProvider,
/** The ticker to use when {@link autoUpdate} is `true`. Defaults to {@link Ticker.shared}. */
ticker?: Ticker,
}; };
const vectorAux = new Vector2(); const vectorAux = new Vector2();
@ -244,6 +247,9 @@ export interface SpineOptions extends ContainerOptions {
/** See {@link SpineFromOptions.boundsProvider}. */ /** See {@link SpineFromOptions.boundsProvider}. */
boundsProvider?: SpineBoundsProvider, boundsProvider?: SpineBoundsProvider,
/** See {@link SpineFromOptions.ticker}. */
ticker?: Ticker,
} }
/** /**
@ -352,21 +358,38 @@ export class Spine extends ViewContainer {
} }
private _autoUpdate = false; private _autoUpdate = false;
private _ticker: Ticker = Ticker.shared;
public get autoUpdate (): boolean { public get autoUpdate (): boolean {
return this._autoUpdate; return this._autoUpdate;
} }
/** When `true`, the Spine AnimationState and the Skeleton will be automatically updated using the {@link Ticker.shared} instance. */ /** When `true`, the Spine AnimationState and the Skeleton will be automatically updated using the {@link ticker}. */
public set autoUpdate (value: boolean) { public set autoUpdate (value: boolean) {
if (value && !this._autoUpdate) { if (value && !this._autoUpdate) {
Ticker.shared.add(this.internalUpdate, this); this._ticker.add(this.internalUpdate, this);
} else if (!value && this._autoUpdate) { } else if (!value && this._autoUpdate) {
Ticker.shared.remove(this.internalUpdate, this); this._ticker.remove(this.internalUpdate, this);
} }
this._autoUpdate = value; this._autoUpdate = value;
} }
/** The ticker to use when {@link autoUpdate} is `true`. Defaults to {@link Ticker.shared}. */
public get ticker (): Ticker {
return this._ticker;
}
/** Sets the ticker to use when {@link autoUpdate} is `true`. If `autoUpdate` is already `true`, the update callback will be moved from the old ticker to the new one. */
public set ticker (value: Ticker) {
if (this._ticker === value) return;
if (this._autoUpdate) {
this._ticker.remove(this.internalUpdate, this);
value.add(this.internalUpdate, this);
}
this._ticker = value;
}
private _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 {
@ -396,6 +419,7 @@ export class Spine extends ViewContainer {
this.skeleton = new Skeleton(skeletonData); this.skeleton = new Skeleton(skeletonData);
this.state = new AnimationState(new AnimationStateData(skeletonData)); this.state = new AnimationState(new AnimationStateData(skeletonData));
if (options?.ticker) this._ticker = options.ticker;
this.autoUpdate = options?.autoUpdate ?? true; this.autoUpdate = options?.autoUpdate ?? true;
// dark tint can be enabled by options, otherwise is enable if at least one slot has tint black // dark tint can be enabled by options, otherwise is enable if at least one slot has tint black
@ -418,9 +442,7 @@ export class Spine extends ViewContainer {
} }
protected internalUpdate (_deltaFrame: any, deltaSeconds?: number): void { protected internalUpdate (_deltaFrame: any, deltaSeconds?: number): void {
// Because reasons, pixi uses deltaFrames at 60fps. this._updateAndApplyState(deltaSeconds ?? this._ticker.deltaMS / 1000);
// We ignore the default deltaFrames and use the deltaSeconds from pixi ticker.
this._updateAndApplyState(deltaSeconds ?? Ticker.shared.deltaMS / 1000);
} }
override get bounds () { override get bounds () {
@ -1027,7 +1049,7 @@ export class Spine extends ViewContainer {
public override destroy (options: DestroyOptions = false) { public override destroy (options: DestroyOptions = false) {
super.destroy(options); super.destroy(options);
Ticker.shared.remove(this.internalUpdate, this); this._ticker.remove(this.internalUpdate, this);
this.state.clearListeners(); this.state.clearListeners();
this.debug = undefined; this.debug = undefined;
this.skeleton = null as any; this.skeleton = null as any;
@ -1072,7 +1094,7 @@ export class Spine extends ViewContainer {
* @param options - Options to configure the Spine game object. See {@link SpineFromOptions} * @param options - Options to configure the Spine game object. See {@link SpineFromOptions}
* @returns {Spine} The Spine game object instantiated * @returns {Spine} The Spine game object instantiated
*/ */
static from ({ skeleton, atlas, scale = 1, darkTint, autoUpdate = true, boundsProvider }: SpineFromOptions) { static from ({ skeleton, atlas, scale = 1, darkTint, autoUpdate = true, boundsProvider, ticker }: SpineFromOptions) {
const cacheKey = `${skeleton}-${atlas}-${scale}`; const cacheKey = `${skeleton}-${atlas}-${scale}`;
if (Cache.has(cacheKey)) { if (Cache.has(cacheKey)) {
@ -1081,6 +1103,7 @@ export class Spine extends ViewContainer {
darkTint, darkTint,
autoUpdate, autoUpdate,
boundsProvider, boundsProvider,
ticker,
}); });
} }
@ -1102,6 +1125,7 @@ export class Spine extends ViewContainer {
darkTint, darkTint,
autoUpdate, autoUpdate,
boundsProvider, boundsProvider,
ticker,
}); });
} }
} }