Prechádzať zdrojové kódy

[ts][phaser-v3][phaser-v4] Aligned bounds and skeleton when gameobject origin is moved. Add AABBRectangleBoundsProvider.

Davide Tantillo 4 mesiacov pred
rodič
commit
62b5fc9e86

+ 3 - 0
spine-ts/index.html

@@ -72,6 +72,9 @@
         <li>
           <a href="/spine-phaser-v4/example/bounds-test.html">Bounds test</a> - (<a href="/spine-phaser-v3/example/bounds-test.html">v3</a>)
         </li>
+        <li>
+          <a href="/spine-phaser-v4/example/move-origin.html">Move origin</a> - (<a href="/spine-phaser-v3/example/move-origin.html">v3</a>)
+        </li>
         <li>
           <a href="/spine-phaser-v4/example/visibility-test.html">Visibility test</a> - (<a href="/spine-phaser-v3/example/visibility-test.html">v3</a>)
         </li>

BIN
spine-ts/spine-phaser-v3/example/block.png


+ 147 - 0
spine-ts/spine-phaser-v3/example/move-origin.html

@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <script src="//cdn.jsdelivr.net/npm/[email protected]/dist/phaser.js"></script>
+    <script src="../dist/iife/spine-phaser-v3.js"></script>
+    <link rel="stylesheet" href="../../index.css" />
+    <title>Spine Phaser Example</title>
+  </head>
+
+  <body class="p-4 flex flex-col items-center">
+    <h1>Move origin</h1>
+    <h2>Demonstrate moving origin of Spine Gameobject, behaves like a sprite</h2>
+  </body>
+  <script>
+    let cursors;
+    let spineboy;
+    const levelWidth = 800;
+    const levelHeight = 600;
+
+    class BasicExample extends Phaser.Scene {
+      preload() {
+        this.load.spineBinary("spineboy-data", "assets/spineboy-pro.skel");
+        this.load.spineAtlas("spineboy-atlas", "assets/spineboy-pma.atlas");
+        this.load.image("block", "block.png");
+      }
+
+      create() {
+        const graphics = this.add.graphics();
+        const width = levelWidth / 5;
+        const height = levelHeight / 4;
+        let cells = [];
+        for (let i = 0; i < levelWidth / width; i++) {
+          for (let j = 0; j < levelHeight / height; j++) {
+            const x = i * width;
+            const y = j * height;
+            cells.push({ x, y });
+            graphics.lineStyle(1, 0x00ff00, .5);
+            graphics.strokeRect(x, y, width, height);
+
+            graphics.beginPath();
+            graphics.moveTo(x, y);
+            graphics.lineTo(x + width, y + height);
+            graphics.moveTo(x + width, y);
+            graphics.lineTo(x, y + height);
+            graphics.closePath();
+
+            graphics.strokePath();
+          }
+        }
+
+
+        let animations = undefined;
+        let prevWidth = 0;
+        let prevHeight = 0;
+        cells.forEach(({ x, y }, i) => {
+          let gameobject;
+          let animation;
+          if (i % 2 === 1) {
+            gameobject = this.add.image(x, y, "block");
+            gameobject.displayWidth = prevWidth;
+            gameobject.displayHeight = prevHeight;
+          } else {
+            animation = animations ? animations[i % animations.length].name : "aim";
+            gameobject = this.add.spine(x, y, "spineboy-data", "spineboy-atlas", new spine.SkinsAndAnimationBoundsProvider(animation, undefined, undefined, true));
+            if (!animations) animations = gameobject.skeleton.data.animations;
+            gameobject.animationState.setAnimation(0, animation, true);
+
+            let ratio = gameobject.width / gameobject.height;
+            if (ratio > 1) {
+              gameobject.displayHeight = height / ratio;
+              gameobject.displayWidth = width;
+            } else {
+              gameobject.displayWidth = ratio * height;
+              gameobject.displayHeight = height;
+            }
+            prevWidth = gameobject.displayWidth;
+            prevHeight = gameobject.displayHeight;
+          }
+
+          // moving origin at the center of the cell, we're still able to align spineboy and keep gameobject bounds align with the drawn skeleton
+          const origin = 1 / (cells.length / 2 - 1) * Math.floor(i / 2)
+
+          gameobject.setOrigin(origin)
+          gameobject.x += width / 2 - gameobject.displayWidth * (0.5 - origin);
+          gameobject.y += height / 2 - gameobject.displayHeight * (0.5 - origin);
+
+          const graphics = this.add.graphics();
+          const rect = new Phaser.Geom.Rectangle(
+            gameobject.x - gameobject.displayOriginX * gameobject.scaleX,
+            gameobject.y - gameobject.displayOriginY * gameobject.scaleY,
+            gameobject.width * gameobject.scaleX,
+            gameobject.height * gameobject.scaleY,
+          );
+
+          const defaultGraphicsElements = () => {
+            graphics.clear();
+            graphics.lineStyle(7, 0x0000ff, .75);
+            graphics.strokeRectShape(rect);
+            graphics.fillStyle(0xffff00, 10);
+            graphics.fillCircle(gameobject.x, gameobject.y, 10);
+
+            graphics.fillStyle(0xff5500, 1);
+            graphics.fillCircle(gameobject.x - gameobject.displayOriginX * gameobject.scaleX, gameobject.y - gameobject.displayOriginY * gameobject.scaleY, 5);
+
+            graphics.lineStyle(3, 0xffffff, 1);
+            graphics.lineBetween(gameobject.x, gameobject.y, gameobject.x + gameobject.displayOriginX * gameobject.scaleX, gameobject.y + gameobject.displayOriginY * gameobject.scaleY);
+          }
+          defaultGraphicsElements();
+
+          // interactions bounds are aligned with gameobject bounds
+          gameobject.setInteractive();
+          this.input.enableDebug(gameobject, 0xff00ff);
+          gameobject.on("pointerover", () => {
+            defaultGraphicsElements();
+            graphics.fillStyle(0x00ff00, .25);
+            graphics.fillRectShape(rect);
+            graphics.fillStyle(0xff5500, 1);
+          });
+          gameobject.on("pointerout", () => defaultGraphicsElements());
+        })
+
+      }
+
+    }
+
+
+    new Phaser.Game({
+      type: Phaser.AUTO,
+      width: levelWidth,
+      height: levelHeight,
+      type: Phaser.WEBGL,
+      scene: [BasicExample],
+      plugins: {
+        scene: [
+          {
+            key: "spine.SpinePlugin",
+            plugin: spine.SpinePlugin,
+            mapping: "spine",
+          },
+        ],
+      },
+    });
+  </script>
+</html>

+ 28 - 11
spine-ts/spine-phaser-v3/src/SpineGameObject.ts

@@ -68,6 +68,19 @@ export interface SpineGameObjectBoundsProvider {
 	};
 }
 
+/** A bounds provider that provides a fixed size given by the user. */
+export class AABBRectangleBoundsProvider implements SpineGameObjectBoundsProvider {
+	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 SpineGameObjectBoundsProvider {
 	/**
@@ -215,6 +228,8 @@ export class SpineGameObject extends DepthMixin(
 	beforeUpdateWorldTransforms: (object: SpineGameObject) => void = () => { };
 	afterUpdateWorldTransforms: (object: SpineGameObject) => void = () => { };
 	private premultipliedAlpha = false;
+	private offsetX = 0;
+	private offsetY = 0;
 
 	constructor (
 		scene: Phaser.Scene,
@@ -239,13 +254,11 @@ export class SpineGameObject extends DepthMixin(
 	updateSize () {
 		if (!this.skeleton) return;
 		let bounds = this.boundsProvider.calculateBounds(this);
-		// For some reason the TS compiler and the ComputedSize mixin don't work well together and we have
-		// to cast to any.
-		let self = this as any;
-		self.width = bounds.width;
-		self.height = bounds.height;
-		this.displayOriginX = -bounds.x;
-		this.displayOriginY = -bounds.y;
+		this.width = bounds.width;
+		this.height = bounds.height;
+		this.setDisplayOrigin(-bounds.x, -bounds.y);
+		this.offsetX = -bounds.x;
+		this.offsetY = -bounds.y;
 	}
 
 	/** Converts a point from the skeleton coordinate system to the Phaser world coordinate system. */
@@ -355,17 +368,21 @@ export class SpineGameObject extends DepthMixin(
 			d = transform.d,
 			tx = transform.tx,
 			ty = transform.ty;
+
+		let offsetX = (src.offsetX - src.displayOriginX) * src.scaleX;
+		let offsetY = (src.offsetY - src.displayOriginY) * src.scaleY;
+
 		sceneRenderer.drawSkeleton(
-			this.skeleton,
-			this.premultipliedAlpha,
+			src.skeleton,
+			src.premultipliedAlpha,
 			-1,
 			-1,
 			(vertices, numVertices, stride) => {
 				for (let i = 0; i < numVertices; i += stride) {
 					let vx = vertices[i];
 					let vy = vertices[i + 1];
-					vertices[i] = vx * a + vy * c + tx;
-					vertices[i + 1] = vx * b + vy * d + ty;
+					vertices[i] = vx * a + vy * c + tx + offsetX;
+					vertices[i + 1] = vx * b + vy * d + ty + offsetY;
 				}
 			}
 		);

BIN
spine-ts/spine-phaser-v4/example/block.png


+ 147 - 0
spine-ts/spine-phaser-v4/example/move-origin.html

@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <script src="//cdn.jsdelivr.net/npm/[email protected]/dist/phaser.js"></script>
+    <script src="../dist/iife/spine-phaser-v4.js"></script>
+    <link rel="stylesheet" href="../../index.css" />
+    <title>Spine Phaser Example</title>
+  </head>
+
+  <body class="p-4 flex flex-col items-center">
+    <h1>Move origin</h1>
+    <h2>Demonstrate moving origin of Spine Gameobject, behaves like a sprite</h2>
+  </body>
+  <script>
+    let cursors;
+    let spineboy;
+    const levelWidth = 800;
+    const levelHeight = 600;
+
+    class BasicExample extends Phaser.Scene {
+      preload() {
+        this.load.spineBinary("spineboy-data", "assets/spineboy-pro.skel");
+        this.load.spineAtlas("spineboy-atlas", "assets/spineboy-pma.atlas");
+        this.load.image("block", "block.png");
+      }
+
+      create() {
+        const graphics = this.add.graphics();
+        const width = levelWidth / 5;
+        const height = levelHeight / 4;
+        let cells = [];
+        for (let i = 0; i < levelWidth / width; i++) {
+          for (let j = 0; j < levelHeight / height; j++) {
+            const x = i * width;
+            const y = j * height;
+            cells.push({ x, y });
+            graphics.lineStyle(1, 0x00ff00, .5);
+            graphics.strokeRect(x, y, width, height);
+
+            graphics.beginPath();
+            graphics.moveTo(x, y);
+            graphics.lineTo(x + width, y + height);
+            graphics.moveTo(x + width, y);
+            graphics.lineTo(x, y + height);
+            graphics.closePath();
+
+            graphics.strokePath();
+          }
+        }
+
+
+        let animations = undefined;
+        let prevWidth = 0;
+        let prevHeight = 0;
+        cells.forEach(({ x, y }, i) => {
+          let gameobject;
+          let animation;
+          if (i % 2 === 1) {
+            gameobject = this.add.image(x, y, "block");
+            gameobject.displayWidth = prevWidth;
+            gameobject.displayHeight = prevHeight;
+          } else {
+            animation = animations ? animations[i % animations.length].name : "aim";
+            gameobject = this.add.spine(x, y, "spineboy-data", "spineboy-atlas", new spine.SkinsAndAnimationBoundsProvider(animation, undefined, undefined, true));
+            if (!animations) animations = gameobject.skeleton.data.animations;
+            gameobject.animationState.setAnimation(0, animation, true);
+
+            let ratio = gameobject.width / gameobject.height;
+            if (ratio > 1) {
+              gameobject.displayHeight = height / ratio;
+              gameobject.displayWidth = width;
+            } else {
+              gameobject.displayWidth = ratio * height;
+              gameobject.displayHeight = height;
+            }
+            prevWidth = gameobject.displayWidth;
+            prevHeight = gameobject.displayHeight;
+          }
+
+          // moving origin at the center of the cell, we're still able to align spineboy and keep gameobject bounds align with the drawn skeleton
+          const origin = 1 / (cells.length / 2 - 1) * Math.floor(i / 2)
+
+          gameobject.setOrigin(origin)
+          gameobject.x += width / 2 - gameobject.displayWidth * (0.5 - origin);
+          gameobject.y += height / 2 - gameobject.displayHeight * (0.5 - origin);
+
+          const graphics = this.add.graphics();
+          const rect = new Phaser.Geom.Rectangle(
+            gameobject.x - gameobject.displayOriginX * gameobject.scaleX,
+            gameobject.y - gameobject.displayOriginY * gameobject.scaleY,
+            gameobject.width * gameobject.scaleX,
+            gameobject.height * gameobject.scaleY,
+          );
+
+          const defaultGraphicsElements = () => {
+            graphics.clear();
+            graphics.lineStyle(7, 0x0000ff, .75);
+            graphics.strokeRectShape(rect);
+            graphics.fillStyle(0xffff00, 10);
+            graphics.fillCircle(gameobject.x, gameobject.y, 10);
+
+            graphics.fillStyle(0xff5500, 1);
+            graphics.fillCircle(gameobject.x - gameobject.displayOriginX * gameobject.scaleX, gameobject.y - gameobject.displayOriginY * gameobject.scaleY, 5);
+
+            graphics.lineStyle(3, 0xffffff, 1);
+            graphics.lineBetween(gameobject.x, gameobject.y, gameobject.x + gameobject.displayOriginX * gameobject.scaleX, gameobject.y + gameobject.displayOriginY * gameobject.scaleY);
+          }
+          defaultGraphicsElements();
+
+          // interactions bounds are aligned with gameobject bounds
+          gameobject.setInteractive();
+          this.input.enableDebug(gameobject, 0xff00ff);
+          gameobject.on("pointerover", () => {
+            defaultGraphicsElements();
+            graphics.fillStyle(0x00ff00, .25);
+            graphics.fillRectShape(rect);
+            graphics.fillStyle(0xff5500, 1);
+          });
+          gameobject.on("pointerout", () => defaultGraphicsElements());
+        })
+
+      }
+
+    }
+
+
+    new Phaser.Game({
+      type: Phaser.AUTO,
+      width: levelWidth,
+      height: levelHeight,
+      type: Phaser.WEBGL,
+      scene: [BasicExample],
+      plugins: {
+        scene: [
+          {
+            key: "spine.SpinePlugin",
+            plugin: spine.SpinePlugin,
+            mapping: "spine",
+          },
+        ],
+      },
+    });
+  </script>
+</html>

+ 26 - 9
spine-ts/spine-phaser-v4/src/SpineGameObject.ts

@@ -68,6 +68,19 @@ export interface SpineGameObjectBoundsProvider {
 	};
 }
 
+/** A bounds provider that provides a fixed size given by the user. */
+export class AABBRectangleBoundsProvider implements SpineGameObjectBoundsProvider {
+	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 SpineGameObjectBoundsProvider {
 	/**
@@ -215,6 +228,8 @@ export class SpineGameObject extends DepthMixin(
 	beforeUpdateWorldTransforms: (object: SpineGameObject) => void = () => { };
 	afterUpdateWorldTransforms: (object: SpineGameObject) => void = () => { };
 	private premultipliedAlpha = false;
+	private offsetX = 0;
+	private offsetY = 0;
 
 	constructor (
 		scene: Phaser.Scene,
@@ -239,13 +254,11 @@ export class SpineGameObject extends DepthMixin(
 	updateSize () {
 		if (!this.skeleton) return;
 		let bounds = this.boundsProvider.calculateBounds(this);
-		// For some reason the TS compiler and the ComputedSize mixin don't work well together and we have
-		// to cast to any.
-		let self = this as any;
-		self.width = bounds.width;
-		self.height = bounds.height;
-		this.displayOriginX = -bounds.x;
-		this.displayOriginY = -bounds.y;
+		this.width = bounds.width;
+		this.height = bounds.height;
+		this.setDisplayOrigin(-bounds.x, -bounds.y);
+		this.offsetX = -bounds.x;
+		this.offsetY = -bounds.y;
 	}
 
 	/** Converts a point from the skeleton coordinate system to the Phaser world coordinate system. */
@@ -374,6 +387,10 @@ export class SpineGameObject extends DepthMixin(
 			d = transform.d,
 			tx = transform.tx,
 			ty = transform.ty;
+
+		let offsetX = (src.offsetX - src.displayOriginX) * src.scaleX;
+		let offsetY = (src.offsetY - src.displayOriginY) * src.scaleY;
+
 		sceneRenderer.drawSkeleton(
 			src.skeleton,
 			src.premultipliedAlpha,
@@ -383,8 +400,8 @@ export class SpineGameObject extends DepthMixin(
 				for (let i = 0; i < numVertices; i += stride) {
 					let vx = vertices[i];
 					let vy = vertices[i + 1];
-					vertices[i] = vx * a + vy * c + tx;
-					vertices[i + 1] = vx * b + vy * d + ty;
+					vertices[i] = vx * a + vy * c + tx + offsetX;
+					vertices[i + 1] = vx * b + vy * d + ty + offsetY;
 				}
 			}
 		);