From 90cd61e505344931881250b5fbb00d9d892260d7 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 22 Dec 2022 08:54:30 +0100 Subject: [PATCH] [phaser] Bounds providers. --- .../spine-phaser/example/bounds-test.html | 2 +- spine-ts/spine-phaser/src/SpineGameObject.ts | 65 +++++++++++++++---- spine-ts/spine-phaser/src/SpinePlugin.ts | 6 +- spine-ts/spine-phaser/src/index.ts | 1 + 4 files changed, 59 insertions(+), 15 deletions(-) diff --git a/spine-ts/spine-phaser/example/bounds-test.html b/spine-ts/spine-phaser/example/bounds-test.html index 6a62b8fae..9989adab0 100644 --- a/spine-ts/spine-phaser/example/bounds-test.html +++ b/spine-ts/spine-phaser/example/bounds-test.html @@ -38,7 +38,7 @@ function preload () { } function create () { - spineboy = this.add.spine(400, 300, 'spineboy-data', "spineboy-atlas"); + spineboy = this.add.spine(400, 300, 'spineboy-data', "spineboy-atlas", new spine.SkinsAndAnimationBoundsProvider("run")); spineboy.scale = 0.4 spineboy.setInteractive(); this.input.enableDebug(spineboy, 0xff00ff); diff --git a/spine-ts/spine-phaser/src/SpineGameObject.ts b/spine-ts/spine-phaser/src/SpineGameObject.ts index 512d07006..a0d03d756 100644 --- a/spine-ts/spine-phaser/src/SpineGameObject.ts +++ b/spine-ts/spine-phaser/src/SpineGameObject.ts @@ -1,7 +1,7 @@ import { SPINE_GAME_OBJECT_TYPE } from "./keys"; import { SpinePlugin } from "./SpinePlugin"; import { ComputedSizeMixin, DepthMixin, FlipMixin, ScrollFactorMixin, TransformMixin, VisibleMixin } from "./mixins"; -import { AnimationState, AnimationStateData, MathUtils, Skeleton } from "@esotericsoftware/spine-core"; +import { AnimationState, AnimationStateData, MathUtils, Skeleton, Skin } from "@esotericsoftware/spine-core"; class BaseSpineGameObject extends Phaser.GameObjects.GameObject { constructor(scene: Phaser.Scene, type: string) { @@ -16,23 +16,66 @@ export interface SpineGameObjectBoundsProvider { export class SetupPoseBoundsProvider implements SpineGameObjectBoundsProvider { calculateBounds(gameObject: SpineGameObject) { if (!gameObject.skeleton) return { x: 0, y: 0, width: 0, height: 0 }; - gameObject.skeleton.setToSetupPose(); - gameObject.skeleton.updateWorldTransform(); - return gameObject.skeleton.getBoundsRect(); + // 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(); + return skeleton.getBoundsRect(); } } export class SkinsAndAnimationBoundsProvider implements SpineGameObjectBoundsProvider { - constructor(private animation: string, private skins: string[] = []) { + constructor(private animation: string, private skins: string[] = [], private timeStep: number = 0.05) { } calculateBounds(gameObject: SpineGameObject): { x: number; y: number; width: number; height: number; } { - if (!gameObject.skeleton) return { x: 0, y: 0, width: 0, height: 0 }; - // FIXME - gameObject.skeleton.setToSetupPose(); - gameObject.skeleton.updateWorldTransform(); - return gameObject.skeleton.getBoundsRect(); + if (!gameObject.skeleton || !gameObject.animationState) 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.animationState.data); + const skeleton = new Skeleton(gameObject.skeleton.data); + const data = skeleton.data; + let customSkin = new Skin("custom-skin"); + if (this.skins.length > 0) { + 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; + let minX = Number.POSITIVE_INFINITY, minY = Number.POSITIVE_INFINITY, maxX = Number.NEGATIVE_INFINITY, maxY = Number.NEGATIVE_INFINITY; + if (animation == null) { + skeleton.updateWorldTransform(); + const bounds = skeleton.getBoundsRect(); + minX = bounds.x; + minY = bounds.y; + maxX = minX + bounds.width; + maxY = minY + bounds.height; + } else { + animationState.clearTracks(); + animationState.setAnimationWith(0, animation, false); + const steps = Math.max(animation.duration / this.timeStep, 1.0); + for (let i = 0; i < steps; i++) { + animationState.update(i > 0 ? this.timeStep : 0); + animationState.apply(skeleton); + skeleton.updateWorldTransform(); + + const bounds = skeleton.getBoundsRect(); + minX = Math.min(minX, bounds.x); + minY = Math.min(minY, bounds.y); + maxX = Math.max(maxX, minX + bounds.width); + maxY = Math.max(maxY, minY + bounds.height); + } + } + return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }; } } @@ -93,7 +136,7 @@ export class SpineGameObject extends ComputedSizeMixin(DepthMixin(FlipMixin(Scro } public get scaleY() { - return this._scaleX; + return this._scaleY; } public set scaleY(value: number) { diff --git a/spine-ts/spine-phaser/src/SpinePlugin.ts b/spine-ts/spine-phaser/src/SpinePlugin.ts index d0b94e77a..45f240bb4 100644 --- a/spine-ts/spine-phaser/src/SpinePlugin.ts +++ b/spine-ts/spine-phaser/src/SpinePlugin.ts @@ -30,7 +30,7 @@ import Phaser from "phaser"; import { SPINE_ATLAS_CACHE_KEY, SPINE_CONTAINER_TYPE, SPINE_GAME_OBJECT_TYPE, SPINE_ATLAS_TEXTURE_CACHE_KEY, SPINE_SKELETON_DATA_FILE_TYPE, SPINE_ATLAS_FILE_TYPE, SPINE_SKELETON_FILE_CACHE_KEY as SPINE_SKELETON_DATA_CACHE_KEY } from "./keys"; import { AtlasAttachmentLoader, Bone, GLTexture, SceneRenderer, Skeleton, SkeletonBinary, SkeletonData, SkeletonJson, TextureAtlas } from "@esotericsoftware/spine-webgl" -import { SpineGameObject } from "./SpineGameObject"; +import { SpineGameObject, SpineGameObjectBoundsProvider } from "./SpineGameObject"; import { CanvasTexture, SkeletonRenderer } from "@esotericsoftware/spine-canvas"; export class SpinePlugin extends Phaser.Plugins.ScenePlugin { @@ -98,8 +98,8 @@ export class SpinePlugin extends Phaser.Plugins.ScenePlugin { pluginManager.registerFileType("spineAtlas", atlasFileCallback, scene); let self = this; - let addSpineGameObject = function (this: Phaser.GameObjects.GameObjectFactory, x: number, y: number, dataKey: string, atlasKey: string) { - let gameObject = new SpineGameObject(scene, self, x, y, dataKey, atlasKey); + let addSpineGameObject = function (this: Phaser.GameObjects.GameObjectFactory, x: number, y: number, dataKey: string, atlasKey: string, boundsProvider: SpineGameObjectBoundsProvider) { + let gameObject = new SpineGameObject(scene, self, x, y, dataKey, atlasKey, boundsProvider); this.displayList.add(gameObject); this.updateList.add(gameObject); return gameObject; diff --git a/spine-ts/spine-phaser/src/index.ts b/spine-ts/spine-phaser/src/index.ts index 37eb557e8..e5d94836e 100644 --- a/spine-ts/spine-phaser/src/index.ts +++ b/spine-ts/spine-phaser/src/index.ts @@ -1,5 +1,6 @@ export * from "./require-shim" export * from "./SpinePlugin" +export * from "./SpineGameObject" export * from "./mixins" export * from "@esotericsoftware/spine-core"; export * from "@esotericsoftware/spine-webgl";