소스 검색

Merge branch '4.1' into 4.2-beta

# Conflicts:
#	.gitignore
#	spine-cpp/spine-cpp/src/spine/SkeletonBinary.cpp
#	spine-ts/package-lock.json
#	spine-ts/package.json
Mario Zechner 2 년 전
부모
커밋
68a9096049
52개의 변경된 파일4599개의 추가작업 그리고 52개의 파일을 삭제
  1. 1 0
      .gitignore
  2. 23 0
      examples/export/runtimes.sh
  3. 34 34
      spine-sfml/cpp/example/testbed.cpp
  4. 8 0
      spine-ts/.vscode/launch.json
  5. 4 0
      spine-ts/index.html
  6. 12 10
      spine-ts/package.json
  7. 2 1
      spine-ts/publish.sh
  8. 11 1
      spine-ts/spine-core/src/Skeleton.ts
  9. 26 0
      spine-ts/spine-phaser/LICENSE
  10. 3 0
      spine-ts/spine-phaser/README.md
  11. 59 0
      spine-ts/spine-phaser/example/arcade-physics-test.html
  12. 19 0
      spine-ts/spine-phaser/example/assets/coin-pma.atlas
  13. BIN
      spine-ts/spine-phaser/example/assets/coin-pma.png
  14. BIN
      spine-ts/spine-phaser/example/assets/coin-pro.skel
  15. 363 0
      spine-ts/spine-phaser/example/assets/mix-and-match-pma.atlas
  16. BIN
      spine-ts/spine-phaser/example/assets/mix-and-match-pma.png
  17. 1285 0
      spine-ts/spine-phaser/example/assets/mix-and-match-pro.json
  18. BIN
      spine-ts/spine-phaser/example/assets/mix-and-match-pro.skel
  19. 101 0
      spine-ts/spine-phaser/example/assets/raptor-pma.atlas
  20. BIN
      spine-ts/spine-phaser/example/assets/raptor-pma.png
  21. 692 0
      spine-ts/spine-phaser/example/assets/raptor-pro.json
  22. 102 0
      spine-ts/spine-phaser/example/assets/spineboy-pma.atlas
  23. BIN
      spine-ts/spine-phaser/example/assets/spineboy-pma.png
  24. BIN
      spine-ts/spine-phaser/example/assets/spineboy-pro.skel
  25. 16 0
      spine-ts/spine-phaser/example/assets/stretchyman-pma.atlas
  26. BIN
      spine-ts/spine-phaser/example/assets/stretchyman-pma.png
  27. 512 0
      spine-ts/spine-phaser/example/assets/stretchyman-pro.json
  28. BIN
      spine-ts/spine-phaser/example/assets/stretchyman-pro.skel
  29. 44 0
      spine-ts/spine-phaser/example/basic-example.html
  30. 63 0
      spine-ts/spine-phaser/example/batching-test.html
  31. 70 0
      spine-ts/spine-phaser/example/blend-test.html
  32. 56 0
      spine-ts/spine-phaser/example/bounds-test.html
  33. 113 0
      spine-ts/spine-phaser/example/camera-pipeline-test.html
  34. 71 0
      spine-ts/spine-phaser/example/control-bones-test.html
  35. 24 0
      spine-ts/spine-phaser/example/index.html
  36. 65 0
      spine-ts/spine-phaser/example/multi-scene-test.html
  37. BIN
      spine-ts/spine-phaser/example/nyan.png
  38. 50 0
      spine-ts/spine-phaser/example/visibility-test.html
  39. 38 0
      spine-ts/spine-phaser/package.json
  40. 255 0
      spine-ts/spine-phaser/src/SpineGameObject.ts
  41. 315 0
      spine-ts/spine-phaser/src/SpinePlugin.ts
  42. 8 0
      spine-ts/spine-phaser/src/index.ts
  43. 8 0
      spine-ts/spine-phaser/src/keys.ts
  44. 77 0
      spine-ts/spine-phaser/src/mixins.ts
  45. 11 0
      spine-ts/spine-phaser/src/require-shim.ts
  46. 36 0
      spine-ts/spine-phaser/tsconfig.json
  47. 1 1
      spine-ts/spine-player/src/index.ts
  48. 8 0
      spine-ts/spine-webgl/src/PolygonBatcher.ts
  49. 3 3
      spine-ts/spine-webgl/src/SceneRenderer.ts
  50. 5 1
      spine-ts/spine-webgl/src/SkeletonRenderer.ts
  51. 2 1
      spine-ts/tsconfig.base.json
  52. 3 0
      spine-ts/tsconfig.json

+ 1 - 0
.gitignore

@@ -185,3 +185,4 @@ spine-flutter/src/spine-cpp
 
 spine-godot/.clang-format
 
+spine-ts/spine-phaser/dist

+ 23 - 0
examples/export/runtimes.sh

@@ -357,6 +357,29 @@ cp -f ../spineboy/export/spineboy-pro.skel "$ROOT/spine-ts/spine-player/example/
 cp -f ../spineboy/export/spineboy-pma.atlas "$ROOT/spine-ts/spine-player/example/assets/"
 cp -f ../spineboy/export/spineboy-pma.png "$ROOT/spine-ts/spine-player/example/assets/"
 
+rm "$ROOT/spine-ts/spine-phaser/example/assets/"*
+cp -f ../raptor/export/raptor-pro.json "$ROOT/spine-ts/spine-phaser/example/assets/"
+cp -f ../raptor/export/raptor-pma.atlas "$ROOT/spine-ts/spine-phaser/example/assets/"
+cp -f ../raptor/export/raptor-pma.png "$ROOT/spine-ts/spine-phaser/example/assets/"
+
+cp -f ../spineboy/export/spineboy-pro.skel "$ROOT/spine-ts/spine-phaser/example/assets/"
+cp -f ../spineboy/export/spineboy-pma.atlas "$ROOT/spine-ts/spine-phaser/example/assets/"
+cp -f ../spineboy/export/spineboy-pma.png "$ROOT/spine-ts/spine-phaser/example/assets/"
+
+cp -f ../coin/export/coin-pro.skel "$ROOT/spine-ts/spine-phaser/example/assets/"
+cp -f ../coin/export/coin-pma.atlas "$ROOT/spine-ts/spine-phaser/example/assets/"
+cp -f ../coin/export/coin-pma.png "$ROOT/spine-ts/spine-phaser/example/assets/"
+
+cp -f ../stretchyman/export/stretchyman-pro.json "$ROOT/spine-ts/spine-phaser/example/assets/"
+cp -f ../stretchyman/export/stretchyman-pro.skel "$ROOT/spine-ts/spine-phaser/example/assets/"
+cp -f ../stretchyman/export/stretchyman-pma.atlas "$ROOT/spine-ts/spine-phaser/example/assets/"
+cp -f ../stretchyman/export/stretchyman-pma.png "$ROOT/spine-ts/spine-phaser/example/assets/"
+
+cp -f ../mix-and-match/export/mix-and-match-pro.json "$ROOT/spine-ts/spine-phaser/example/assets/"
+cp -f ../mix-and-match/export/mix-and-match-pro.skel "$ROOT/spine-ts/spine-phaser/example/assets/"
+cp -f ../mix-and-match/export/mix-and-match-pma.atlas "$ROOT/spine-ts/spine-phaser/example/assets/"
+cp -f ../mix-and-match/export/mix-and-match-pma.png "$ROOT/spine-ts/spine-phaser/example/assets/"
+
 echo "spine-monogame"
 rm "$ROOT/spine-monogame/spine-monogame-example/data/"*
 cp -f ../coin/export/coin-pro.json "$ROOT/spine-monogame/spine-monogame-example/data/"

+ 34 - 34
spine-sfml/cpp/example/testbed.cpp

@@ -34,42 +34,42 @@
 using namespace spine;
 
 int main(void) {
-    String atlasFile("data/spineboy-pma.atlas");
-    String skeletonFile("data/spineboy-pro.skel");
-    float scale = 0.6f;
-    SFMLTextureLoader textureLoader;
-    Atlas *atlas = new Atlas(atlasFile, &textureLoader);
-    SkeletonData *skeletonData = nullptr;
-    if (strncmp(skeletonFile.buffer(), ".skel", skeletonFile.length()) > 0) {
-        SkeletonBinary binary(atlas);
-        binary.setScale(scale);
-        skeletonData = binary.readSkeletonDataFile(skeletonFile);
-    } else {
-        SkeletonJson json(atlas);
-        json.setScale(scale);
-        skeletonData = json.readSkeletonDataFile(skeletonFile);
-    }
+	String atlasFile("data/spineboy-pma.atlas");
+	String skeletonFile("data/spineboy-pro.skel");
+	float scale = 0.6f;
+	SFMLTextureLoader textureLoader;
+	Atlas *atlas = new Atlas(atlasFile, &textureLoader);
+	SkeletonData *skeletonData = nullptr;
+	if (strncmp(skeletonFile.buffer(), ".skel", skeletonFile.length()) > 0) {
+		SkeletonBinary binary(atlas);
+		binary.setScale(scale);
+		skeletonData = binary.readSkeletonDataFile(skeletonFile);
+	} else {
+		SkeletonJson json(atlas);
+		json.setScale(scale);
+		skeletonData = json.readSkeletonDataFile(skeletonFile);
+	}
 
-    AnimationStateData stateData(skeletonData);
-    SkeletonDrawable drawable(skeletonData, &stateData);
-    drawable.skeleton->setPosition(320, 590);
-    drawable.state->setAnimation(0, "walk", true);
+	AnimationStateData stateData(skeletonData);
+	SkeletonDrawable drawable(skeletonData, &stateData);
+	drawable.skeleton->setPosition(320, 590);
+	drawable.state->setAnimation(0, "walk", true);
 
-    sf::RenderWindow window(sf::VideoMode(640, 640), "Spine SFML - testbed");
-    window.setFramerateLimit(60);
-    sf::Event event;
-    sf::Clock deltaClock;
-    while (window.isOpen()) {
-        while (window.pollEvent(event))
-            if (event.type == sf::Event::Closed) window.close();
+	sf::RenderWindow window(sf::VideoMode(640, 640), "Spine SFML - testbed");
+	window.setFramerateLimit(60);
+	sf::Event event;
+	sf::Clock deltaClock;
+	while (window.isOpen()) {
+		while (window.pollEvent(event))
+			if (event.type == sf::Event::Closed) window.close();
 
-        float delta = deltaClock.getElapsedTime().asSeconds();
-        deltaClock.restart();
-        drawable.update(delta);
-        window.clear();
-        window.draw(drawable);
-        window.display();
-    }
+		float delta = deltaClock.getElapsedTime().asSeconds();
+		deltaClock.restart();
+		drawable.update(delta);
+		window.clear();
+		window.draw(drawable);
+		window.display();
+	}
 
-    return 0;
+	return 0;
 }

+ 8 - 0
spine-ts/.vscode/launch.json

@@ -31,6 +31,14 @@
 			"name": "threejs-example",
 			"url": "http://localhost:8080/spine-threejs/example/index.html",
 			"webRoot": "${workspaceFolder}"
+		},
+		{
+			"type": "pwa-chrome",
+			"request": "launch",
+			"name": "phaser-example",
+			"url": "http://localhost:8080/spine-phaser/example/index.html",
+			"webRoot": "${workspaceFolder}"
 		}
+
 	]
 }

+ 4 - 0
spine-ts/index.html

@@ -16,6 +16,10 @@
 			<li><a href="/spine-canvas/example">Example</a></li>
 			<li><a href="/spine-canvas/example/mouse-click.html">Mouse click</a></li>
 		</ul>
+		<li>Phaser</li>
+		<ul>
+			<li><a href="/spine-phaser/example/index.html">Example</a></li>
+		</ul>
 		<li>Player</li>
 		<ul>
 			<li><a href="/spine-player/example/example.html">Example</a></li>

+ 12 - 10
spine-ts/package.json

@@ -7,20 +7,22 @@
   ],
   "scripts": {
     "prepublish": "npm run clean && npm run build",
-    "clean": "npx rimraf spine-core/dist spine-canvas/dist spine-webgl/dist spine-player/dist spine-threejs/dist",
-    "build": "npm run clean && npm run build:modules && concurrently \"npm run build:core\" \"npm run build:canvas\" \"npm run build:webgl\" \"npm run build:player\" \"npm run build:threejs\"",
+    "clean": "npx rimraf spine-core/dist spine-canvas/dist spine-webgl/dist spine-phaser/dist spine-player/dist spine-threejs/dist",
+    "build": "npm run clean && npm run build:modules && concurrently \"npm run build:core\" \"npm run build:canvas\" \"npm run build:webgl\" \"npm run build:phaser\" \"npm run build:player\" \"npm run build:threejs\"",
     "postbuild": "npm run minify",
     "build:modules": "npx tsc -b -clean && npx tsc -b",
     "build:core": "npx esbuild --bundle spine-core/src/index.ts --tsconfig=spine-core/tsconfig.json  --sourcemap --outfile=spine-core/dist/iife/spine-core.js --format=iife --global-name=spine",
     "build:canvas": "npx esbuild --bundle spine-canvas/src/index.ts --tsconfig=spine-canvas/tsconfig.json  --sourcemap --outfile=spine-canvas/dist/iife/spine-canvas.js --format=iife --global-name=spine",
     "build:webgl": "npx esbuild --bundle spine-webgl/src/index.ts --tsconfig=spine-webgl/tsconfig.json  --sourcemap --outfile=spine-webgl/dist/iife/spine-webgl.js --format=iife --global-name=spine",
     "build:player": "npx copyfiles -f spine-player/css/spine-player.css spine-player/dist/ && npx npx esbuild --bundle spine-player/src/index.ts --tsconfig=spine-player/tsconfig.json  --sourcemap --outfile=spine-player/dist/iife/spine-player.js --format=iife --global-name=spine",
+    "build:phaser": "npx esbuild  --bundle spine-phaser/src/index.ts  --tsconfig=spine-phaser/tsconfig.json   --sourcemap --outfile=spine-phaser/dist/iife/spine-phaser.js   --external:Phaser --alias:phaser=Phaser --format=iife --global-name=spine",
     "build:threejs": "npx esbuild --bundle spine-threejs/src/index.ts --tsconfig=spine-threejs/tsconfig.json  --sourcemap --outfile=spine-threejs/dist/iife/spine-threejs.js --external:three --format=iife --global-name=spine",
-    "minify": "npx esbuild --minify spine-core/dist/iife/spine-core.js --outfile=spine-core/dist/iife/spine-core.min.js && npx esbuild --minify spine-canvas/dist/iife/spine-canvas.js --outfile=spine-canvas/dist/iife/spine-canvas.min.js && npx esbuild --minify spine-player/dist/iife/spine-player.js --outfile=spine-player/dist/iife/spine-player.min.js && npx esbuild --minify spine-webgl/dist/iife/spine-webgl.js --outfile=spine-webgl/dist/iife/spine-webgl.min.js && npx esbuild --minify spine-threejs/dist/iife/spine-threejs.js --outfile=spine-threejs/dist/iife/spine-threejs.min.js",
-    "dev": "concurrently \"npx live-server --no-browser\" \"npm run dev:modules\" \"npm run dev:canvas\" \"npm run dev:webgl\" \"npm run dev:player\" \"npm run dev:threejs\"",
+    "minify": "npx esbuild --minify spine-core/dist/iife/spine-core.js --outfile=spine-core/dist/iife/spine-core.min.js && npx esbuild --minify spine-canvas/dist/iife/spine-canvas.js --outfile=spine-canvas/dist/iife/spine-canvas.min.js && npx esbuild --minify spine-player/dist/iife/spine-player.js --outfile=spine-player/dist/iife/spine-player.min.js && npx esbuild --minify spine-phaser/dist/iife/spine-phaser.js --outfile=spine-phaser/dist/iife/spine-phaser.min.js && npx esbuild --minify spine-webgl/dist/iife/spine-webgl.js --outfile=spine-webgl/dist/iife/spine-webgl.min.js && npx esbuild --minify spine-threejs/dist/iife/spine-threejs.js --outfile=spine-threejs/dist/iife/spine-threejs.min.js",
+    "dev": "concurrently \"npx live-server --no-browser\" \"npm run dev:canvas\" \"npm run dev:webgl\" \"npm run dev:phaser\" \"npm run dev:player\" \"npm run dev:threejs\"",
     "dev:modules": "npm run build:modules -- --watch",
     "dev:canvas": "npm run build:canvas -- --watch",
     "dev:webgl": "npm run build:webgl -- --watch",
+    "dev:phaser": "npm run build:phaser -- --watch",
     "dev:player": "npm run build:player -- --watch",
     "dev:threejs": "npm run build:threejs -- --watch"
   },
@@ -46,18 +48,18 @@
   "workspaces": [
     "spine-core",
     "spine-canvas",
+    "spine-phaser",
     "spine-player",
     "spine-threejs",
     "spine-webgl"
   ],
   "devDependencies": {
-    "concurrently": "^6.2.1",
+    "@types/offscreencanvas": "^2019.6.4",
+    "concurrently": "^7.6.0",
     "copyfiles": "^2.4.1",
-    "esbuild": "^0.12.22",
-    "live-server": "^1.2.1",
-    "npx": "^10.2.2",
+    "esbuild": "^0.16.4",
+    "live-server": "^1.2.2",
     "rimraf": "^3.0.2",
-    "typescript": "^4.9.3",
-    "@types/offscreencanvas": "^2019.6.4"
+    "typescript": "^4.9.4"
   }
 }

+ 2 - 1
spine-ts/publish.sh

@@ -3,7 +3,7 @@ set -e
 
 if [ ! "$#" -eq 2 ]; then
 	echo "Usage: ./publish.sh <last-version> <new-version>"
-	exit	
+	exit
 else
 	lastVersion=${1%/}
 	newVersion=${2%/}
@@ -14,6 +14,7 @@ fi
 sed -i '' "s/$lastVersion/$newVersion/" package.json
 sed -i '' "s/$lastVersion/$newVersion/" spine-canvas/package.json
 sed -i '' "s/$lastVersion/$newVersion/" spine-core/package.json
+sed -i '' "s/$lastVersion/$newVersion/" spine-phaser/package.json
 sed -i '' "s/$lastVersion/$newVersion/" spine-player/package.json
 sed -i '' "s/$lastVersion/$newVersion/" spine-threejs/package.json
 sed -i '' "s/$lastVersion/$newVersion/" spine-webgl/package.json

+ 11 - 1
spine-ts/spine-core/src/Skeleton.ts

@@ -45,6 +45,8 @@ import { Color, Utils, MathUtils, Vector2, NumberArrayLike } from "./Utils";
  *
  * See [Instance objects](http://esotericsoftware.com/spine-runtime-architecture#Instance-objects) in the Spine Runtimes Guide. */
 export class Skeleton {
+	static yDown = false;;
+
 	/** The skeleton's setup pose data. */
 	data: SkeletonData;
 
@@ -81,7 +83,15 @@ export class Skeleton {
 
 	/** Scales the entire skeleton on the Y axis. This affects all bones, even if the bone's transform mode disallows scale
 	  * inheritance. */
-	scaleY = 1;
+	private _scaleY	= 1;
+
+	public get scaleY() {
+		return Skeleton.yDown ? -this._scaleY : this._scaleY;
+	}
+
+	public set scaleY(scaleY: number) {
+		this._scaleY = scaleY;
+	}
 
 	/** Sets the skeleton X position, which is added to the root bone worldX position. */
 	x = 0;

+ 26 - 0
spine-ts/spine-phaser/LICENSE

@@ -0,0 +1,26 @@
+Spine Runtimes License Agreement
+Last updated May 1, 2019. Replaces all prior versions.
+
+Copyright (c) 2013-2019, Esoteric Software LLC
+
+Integration of the Spine Runtimes into software or otherwise creating
+derivative works of the Spine Runtimes is permitted under the terms and
+conditions of Section 2 of the Spine Editor License Agreement:
+http://esotericsoftware.com/spine-editor-license
+
+Otherwise, it is permitted to integrate the Spine Runtimes into software
+or otherwise create derivative works of the Spine Runtimes (collectively,
+"Products"), provided that each user of the Products must obtain their own
+Spine Editor license and redistribution of the Products in any form must
+include this license and copyright notice.
+
+THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS
+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS
+INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 3 - 0
spine-ts/spine-phaser/README.md

@@ -0,0 +1,3 @@
+# spine-ts Phaser
+
+Please see https://github.com/EsotericSoftware/spine-runtimes/blob/4.0/spine-ts/README.md for more information.

+ 59 - 0
spine-ts/spine-phaser/example/arcade-physics-test.html

@@ -0,0 +1,59 @@
+<!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.js"></script>
+    <title>Spine Phaser Example</title>
+</head>
+<body>
+    <h1>Arcade Physics example</h1>
+</body>
+<script>
+var config = {
+    type: Phaser.AUTO,
+    width: 800,
+    height: 600,
+    type: Phaser.WEBGL,
+    physics: {
+        default: 'arcade',
+        arcade: {
+            debug: true,
+            gravity: { y: 200 }
+        }
+    },
+    scene: {
+        preload: preload,
+        create: create,
+    },
+    plugins: {
+        scene: [
+            { key: "spine.SpinePlugin", plugin: spine.SpinePlugin, mapping: "spine" }
+        ]
+    }
+};
+
+let game = new Phaser.Game(config);
+
+function preload () {
+    this.load.spineBinary("coin-data", "assets/coin-pro.skel");
+    this.load.spineAtlas("coin-atlas", "assets/coin-pma.atlas");
+}
+
+function create () {
+    let coin = this.add.spine(400, 200, 'coin-data', "coin-atlas");
+    coin.animationState.setAnimation(0, "animation", true);
+    coin.setScale(0.3);
+    coin.setSize(280, 280);
+
+    this.physics.add.existing(coin);
+
+    coin.body.setOffset(0, 50);
+    coin.body.setVelocity(100, 200);
+    coin.body.setBounce(1, 1);
+    coin.body.setCollideWorldBounds(true);
+}
+</script>
+</html>

+ 19 - 0
spine-ts/spine-phaser/example/assets/coin-pma.atlas

@@ -0,0 +1,19 @@
+coin-pma.png
+	size: 1024, 1024
+	filter: Linear, Linear
+	pma: true
+coin-front-logo
+	bounds: 2, 570, 305, 302
+coin-front-shine-logo
+	bounds: 2, 286, 282, 282
+coin-front-shine-spineboy
+	bounds: 305, 283, 282, 282
+coin-front-spineboy
+	bounds: 309, 567, 305, 302
+	rotate: 90
+coin-side-round
+	bounds: 2, 2, 144, 282
+coin-side-straight
+	bounds: 286, 286, 17, 282
+shine
+	bounds: 148, 39, 72, 245

BIN
spine-ts/spine-phaser/example/assets/coin-pma.png


BIN
spine-ts/spine-phaser/example/assets/coin-pro.skel


+ 363 - 0
spine-ts/spine-phaser/example/assets/mix-and-match-pma.atlas

@@ -0,0 +1,363 @@
+mix-and-match-pma.png
+	size: 1024, 512
+	filter: Linear, Linear
+	pma: true
+	scale: 0.5
+base-head
+	bounds: 587, 2, 95, 73
+boy/arm-front
+	bounds: 558, 271, 36, 115
+boy/backpack
+	bounds: 235, 109, 119, 153
+boy/backpack-pocket
+	bounds: 328, 73, 34, 62
+	rotate: 90
+boy/backpack-strap-front
+	bounds: 665, 79, 38, 88
+boy/backpack-up
+	bounds: 395, 364, 21, 70
+	rotate: 90
+boy/body
+	bounds: 251, 264, 97, 132
+	rotate: 90
+boy/boot-ribbon-front
+	bounds: 648, 131, 9, 11
+boy/collar
+	bounds: 744, 4, 73, 29
+	rotate: 90
+boy/ear
+	bounds: 383, 109, 19, 23
+	rotate: 90
+boy/eye-back-low-eyelid
+	bounds: 739, 284, 17, 6
+	rotate: 90
+boy/eye-back-pupil
+	bounds: 832, 443, 8, 9
+	rotate: 90
+boy/eye-back-up-eyelid
+	bounds: 558, 264, 23, 5
+boy/eye-back-up-eyelid-back
+	bounds: 802, 491, 19, 10
+	rotate: 90
+boy/eye-front-low-eyelid
+	bounds: 386, 363, 22, 7
+	rotate: 90
+boy/eye-front-pupil
+	bounds: 816, 389, 9, 9
+boy/eye-front-up-eyelid
+	bounds: 160, 71, 31, 6
+	rotate: 90
+boy/eye-front-up-eyelid-back
+	bounds: 801, 434, 26, 9
+	rotate: 90
+boy/eye-iris-back
+	bounds: 618, 264, 17, 17
+boy/eye-iris-front
+	bounds: 727, 264, 18, 18
+boy/eye-white-back
+	bounds: 580, 131, 20, 12
+boy/eye-white-front
+	bounds: 510, 130, 27, 13
+boy/eyebrow-back
+	bounds: 751, 88, 20, 11
+	rotate: 90
+boy/eyebrow-front
+	bounds: 483, 130, 25, 11
+boy/hair-back
+	bounds: 494, 388, 122, 81
+	rotate: 90
+boy/hair-bangs
+	bounds: 667, 284, 70, 37
+boy/hair-side
+	bounds: 789, 374, 25, 43
+boy/hand-backfingers
+	bounds: 467, 364, 19, 21
+boy/hand-front-fingers
+	bounds: 488, 364, 19, 21
+boy/hat
+	bounds: 615, 417, 93, 56
+	rotate: 90
+boy/leg-front
+	bounds: 138, 104, 31, 158
+boy/mouth-close
+	bounds: 551, 365, 21, 5
+	rotate: 90
+girl-blue-cape/mouth-close
+	bounds: 551, 365, 21, 5
+	rotate: 90
+girl-spring-dress/mouth-close
+	bounds: 551, 365, 21, 5
+	rotate: 90
+girl/mouth-close
+	bounds: 551, 365, 21, 5
+	rotate: 90
+boy/mouth-smile
+	bounds: 705, 79, 29, 7
+boy/nose
+	bounds: 836, 473, 17, 10
+	rotate: 90
+boy/pompom
+	bounds: 747, 273, 48, 43
+	rotate: 90
+boy/zip
+	bounds: 648, 144, 14, 23
+girl-blue-cape/back-eyebrow
+	bounds: 602, 131, 18, 12
+girl-blue-cape/body-dress
+	bounds: 2, 264, 109, 246
+girl-blue-cape/body-ribbon
+	bounds: 615, 283, 50, 38
+girl-blue-cape/cape-back
+	bounds: 2, 69, 134, 193
+girl-blue-cape/cape-back-up
+	bounds: 386, 387, 123, 106
+	rotate: 90
+girl-blue-cape/cape-ribbon
+	bounds: 675, 264, 50, 18
+girl-blue-cape/cape-shoulder-back
+	bounds: 751, 110, 49, 59
+girl-blue-cape/cape-shoulder-front
+	bounds: 113, 264, 62, 76
+	rotate: 90
+girl-blue-cape/cape-up-front
+	bounds: 399, 264, 98, 117
+	rotate: 90
+girl-blue-cape/ear
+	bounds: 775, 2, 19, 23
+girl-spring-dress/ear
+	bounds: 775, 2, 19, 23
+girl/ear
+	bounds: 775, 2, 19, 23
+girl-blue-cape/eye-back-low-eyelid
+	bounds: 802, 463, 17, 6
+girl-spring-dress/eye-back-low-eyelid
+	bounds: 802, 463, 17, 6
+girl/eye-back-low-eyelid
+	bounds: 802, 463, 17, 6
+girl-blue-cape/eye-back-pupil
+	bounds: 816, 367, 8, 9
+girl-spring-dress/eye-back-pupil
+	bounds: 816, 367, 8, 9
+girl/eye-back-pupil
+	bounds: 816, 367, 8, 9
+girl-blue-cape/eye-back-up-eyelid
+	bounds: 554, 131, 24, 12
+girl-spring-dress/eye-back-up-eyelid
+	bounds: 554, 131, 24, 12
+girl/eye-back-up-eyelid
+	bounds: 554, 131, 24, 12
+girl-blue-cape/eye-back-up-eyelid-back
+	bounds: 832, 453, 17, 11
+	rotate: 90
+girl-spring-dress/eye-back-up-eyelid-back
+	bounds: 832, 453, 17, 11
+	rotate: 90
+girl/eye-back-up-eyelid-back
+	bounds: 832, 453, 17, 11
+	rotate: 90
+girl-blue-cape/eye-front-low-eyelid
+	bounds: 739, 303, 18, 6
+	rotate: 90
+girl-spring-dress/eye-front-low-eyelid
+	bounds: 739, 303, 18, 6
+	rotate: 90
+girl/eye-front-low-eyelid
+	bounds: 739, 303, 18, 6
+	rotate: 90
+girl-blue-cape/eye-front-pupil
+	bounds: 816, 378, 9, 9
+girl-spring-dress/eye-front-pupil
+	bounds: 816, 378, 9, 9
+girl/eye-front-pupil
+	bounds: 816, 378, 9, 9
+girl-blue-cape/eye-front-up-eyelid
+	bounds: 392, 77, 30, 14
+	rotate: 90
+girl-spring-dress/eye-front-up-eyelid
+	bounds: 392, 77, 30, 14
+	rotate: 90
+girl/eye-front-up-eyelid
+	bounds: 392, 77, 30, 14
+	rotate: 90
+girl-blue-cape/eye-front-up-eyelid-back
+	bounds: 455, 130, 26, 11
+girl-spring-dress/eye-front-up-eyelid-back
+	bounds: 455, 130, 26, 11
+girl/eye-front-up-eyelid-back
+	bounds: 455, 130, 26, 11
+girl-blue-cape/eye-iris-back
+	bounds: 637, 264, 17, 17
+girl-blue-cape/eye-iris-front
+	bounds: 802, 471, 18, 18
+girl-blue-cape/eye-white-back
+	bounds: 596, 264, 20, 16
+girl-spring-dress/eye-white-back
+	bounds: 596, 264, 20, 16
+girl-blue-cape/eye-white-front
+	bounds: 796, 5, 20, 16
+	rotate: 90
+girl-spring-dress/eye-white-front
+	bounds: 796, 5, 20, 16
+	rotate: 90
+girl/eye-white-front
+	bounds: 796, 5, 20, 16
+	rotate: 90
+girl-blue-cape/front-eyebrow
+	bounds: 608, 149, 18, 12
+	rotate: 90
+girl-blue-cape/hair-back
+	bounds: 508, 145, 117, 98
+	rotate: 90
+girl-blue-cape/hair-bangs
+	bounds: 673, 419, 91, 40
+	rotate: 90
+girl-blue-cape/hair-head-side-back
+	bounds: 196, 331, 30, 52
+	rotate: 90
+girl-blue-cape/hair-head-side-front
+	bounds: 738, 323, 41, 42
+girl-blue-cape/hair-side
+	bounds: 473, 3, 36, 71
+girl-blue-cape/hand-front-fingers
+	bounds: 509, 365, 19, 21
+girl-spring-dress/hand-front-fingers
+	bounds: 509, 365, 19, 21
+girl-blue-cape/leg-front
+	bounds: 168, 72, 30, 158
+	rotate: 90
+girl-blue-cape/mouth-smile
+	bounds: 736, 79, 29, 7
+girl-spring-dress/mouth-smile
+	bounds: 736, 79, 29, 7
+girl/mouth-smile
+	bounds: 736, 79, 29, 7
+girl-blue-cape/nose
+	bounds: 747, 264, 11, 7
+girl-spring-dress/nose
+	bounds: 747, 264, 11, 7
+girl/nose
+	bounds: 747, 264, 11, 7
+girl-blue-cape/sleeve-back
+	bounds: 767, 79, 42, 29
+girl-blue-cape/sleeve-front
+	bounds: 408, 76, 52, 119
+	rotate: 90
+girl-spring-dress/arm-front
+	bounds: 596, 282, 17, 111
+girl-spring-dress/back-eyebrow
+	bounds: 801, 420, 18, 12
+girl-spring-dress/body-up
+	bounds: 179, 4, 64, 66
+girl-spring-dress/cloak-down
+	bounds: 775, 27, 50, 50
+girl-spring-dress/cloak-up
+	bounds: 360, 7, 64, 58
+	rotate: 90
+girl-spring-dress/eye-iris-back
+	bounds: 656, 264, 17, 17
+girl-spring-dress/eye-iris-front
+	bounds: 814, 492, 18, 18
+girl-spring-dress/front-eyebrow
+	bounds: 822, 472, 18, 12
+	rotate: 90
+girl-spring-dress/hair-back
+	bounds: 196, 363, 147, 93
+	rotate: 90
+girl-spring-dress/hair-bangs
+	bounds: 696, 326, 91, 40
+	rotate: 90
+girl-spring-dress/hair-head-side-back
+	bounds: 529, 76, 30, 52
+girl-spring-dress/hair-head-side-front
+	bounds: 781, 323, 41, 42
+girl-spring-dress/hair-side
+	bounds: 511, 3, 36, 71
+girl-spring-dress/leg-front
+	bounds: 171, 104, 30, 158
+girl-spring-dress/neck
+	bounds: 138, 70, 20, 32
+girl-spring-dress/shoulder-ribbon
+	bounds: 622, 131, 36, 24
+	rotate: 90
+girl-spring-dress/skirt
+	bounds: 113, 328, 182, 81
+	rotate: 90
+girl-spring-dress/underskirt
+	bounds: 2, 2, 175, 65
+girl/arm-front
+	bounds: 577, 395, 36, 115
+girl/back-eyebrow
+	bounds: 834, 492, 18, 12
+	rotate: 90
+girl/bag-base
+	bounds: 191, 264, 62, 58
+	rotate: 90
+girl/bag-strap-front
+	bounds: 385, 265, 12, 96
+girl/bag-top
+	bounds: 738, 367, 49, 50
+girl/body
+	bounds: 356, 130, 97, 132
+girl/boot-ribbon-front
+	bounds: 539, 130, 13, 13
+girl/eye-iris-back
+	bounds: 821, 424, 17, 17
+girl/eye-iris-front
+	bounds: 812, 443, 18, 18
+girl/eye-white-back
+	bounds: 814, 5, 20, 16
+	rotate: 90
+girl/front-eyebrow
+	bounds: 816, 400, 18, 12
+	rotate: 90
+girl/hair-back
+	bounds: 291, 363, 147, 93
+	rotate: 90
+girl/hair-bangs
+	bounds: 715, 419, 91, 40
+	rotate: 90
+girl/hair-flap-down-front
+	bounds: 288, 5, 70, 65
+girl/hair-head-side-back
+	bounds: 561, 77, 30, 52
+girl/hair-head-side-front
+	bounds: 757, 419, 41, 42
+	rotate: 90
+girl/hair-patch
+	bounds: 245, 4, 66, 41
+	rotate: 90
+girl/hair-side
+	bounds: 549, 3, 36, 71
+girl/hair-strand-back-1
+	bounds: 684, 3, 58, 74
+girl/hair-strand-back-2
+	bounds: 692, 171, 91, 58
+	rotate: 90
+girl/hair-strand-back-3
+	bounds: 615, 323, 92, 79
+	rotate: 90
+girl/hair-strand-front-1
+	bounds: 518, 269, 38, 94
+girl/hair-strand-front-2
+	bounds: 593, 79, 70, 50
+girl/hair-strand-front-3
+	bounds: 705, 88, 44, 81
+girl/hand-front-fingers
+	bounds: 530, 365, 19, 21
+girl/hat
+	bounds: 608, 169, 93, 82
+	rotate: 90
+girl/leg-front
+	bounds: 203, 104, 30, 158
+girl/pompom
+	bounds: 757, 462, 48, 43
+	rotate: 90
+girl/scarf
+	bounds: 455, 143, 119, 51
+	rotate: 90
+girl/scarf-back
+	bounds: 420, 2, 72, 51
+	rotate: 90
+girl/zip
+	bounds: 356, 109, 19, 25
+	rotate: 90

BIN
spine-ts/spine-phaser/example/assets/mix-and-match-pma.png


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1285 - 0
spine-ts/spine-phaser/example/assets/mix-and-match-pro.json


BIN
spine-ts/spine-phaser/example/assets/mix-and-match-pro.skel


+ 101 - 0
spine-ts/spine-phaser/example/assets/raptor-pma.atlas

@@ -0,0 +1,101 @@
+raptor-pma.png
+	size: 1024, 512
+	filter: Linear, Linear
+	pma: true
+	scale: 0.5
+back-arm
+	bounds: 829, 88, 46, 25
+	rotate: 90
+back-bracer
+	bounds: 195, 238, 39, 28
+	rotate: 90
+back-hand
+	bounds: 724, 140, 36, 34
+	rotate: 90
+back-knee
+	bounds: 760, 131, 49, 67
+	rotate: 90
+back-thigh
+	bounds: 225, 238, 39, 24
+	rotate: 90
+eyes-open
+	bounds: 975, 204, 47, 45
+front-arm
+	bounds: 969, 112, 48, 26
+front-bracer
+	bounds: 724, 97, 41, 29
+	rotate: 90
+front-hand
+	bounds: 251, 239, 41, 38
+front-open-hand
+	bounds: 856, 76, 43, 44
+	rotate: 90
+front-thigh
+	bounds: 729, 178, 57, 29
+	rotate: 90
+gun
+	bounds: 894, 251, 107, 103
+gun-nohand
+	bounds: 764, 241, 105, 102
+head
+	bounds: 756, 345, 136, 149
+lower-leg
+	bounds: 475, 237, 73, 98
+	rotate: 90
+mouth-grind
+	bounds: 975, 172, 47, 30
+mouth-smile
+	bounds: 975, 140, 47, 30
+neck
+	bounds: 366, 282, 18, 21
+raptor-back-arm
+	bounds: 636, 97, 82, 86
+	rotate: 90
+raptor-body
+	bounds: 2, 2, 632, 233
+raptor-front-arm
+	bounds: 871, 168, 81, 102
+	rotate: 90
+raptor-front-leg
+	bounds: 2, 237, 191, 257
+raptor-hindleg-back
+	bounds: 195, 279, 169, 215
+raptor-horn
+	bounds: 431, 312, 182, 80
+	rotate: 90
+raptor-horn-back
+	bounds: 513, 318, 176, 77
+	rotate: 90
+raptor-jaw
+	bounds: 894, 356, 126, 138
+raptor-jaw-tooth
+	bounds: 294, 240, 37, 48
+	rotate: 90
+raptor-mouth-inside
+	bounds: 344, 241, 36, 41
+	rotate: 90
+raptor-saddle-strap-back
+	bounds: 575, 242, 54, 74
+raptor-saddle-strap-front
+	bounds: 764, 182, 57, 95
+	rotate: 90
+raptor-saddle-w-shadow
+	bounds: 592, 323, 162, 171
+raptor-tail-shadow
+	bounds: 366, 305, 189, 63
+	rotate: 90
+raptor-tongue
+	bounds: 387, 239, 86, 64
+stirrup-back
+	bounds: 829, 136, 44, 35
+	rotate: 90
+stirrup-front
+	bounds: 866, 121, 45, 50
+	rotate: 90
+stirrup-strap
+	bounds: 918, 120, 49, 46
+torso
+	bounds: 636, 181, 54, 91
+	rotate: 90
+visor
+	bounds: 631, 237, 131, 84

BIN
spine-ts/spine-phaser/example/assets/raptor-pma.png


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 692 - 0
spine-ts/spine-phaser/example/assets/raptor-pro.json


+ 102 - 0
spine-ts/spine-phaser/example/assets/spineboy-pma.atlas

@@ -0,0 +1,102 @@
+spineboy-pma.png
+	size: 1024, 256
+	filter: Linear, Linear
+	pma: true
+	scale: 0.5
+crosshair
+	bounds: 813, 160, 45, 45
+eye-indifferent
+	bounds: 569, 2, 47, 45
+eye-surprised
+	bounds: 643, 7, 47, 45
+	rotate: 90
+front-bracer
+	bounds: 811, 51, 29, 40
+front-fist-closed
+	bounds: 807, 93, 38, 41
+front-fist-open
+	bounds: 815, 210, 43, 44
+front-foot
+	bounds: 706, 64, 63, 35
+	rotate: 90
+front-shin
+	bounds: 80, 11, 41, 92
+front-thigh
+	bounds: 754, 12, 23, 56
+front-upper-arm
+	bounds: 618, 5, 23, 49
+goggles
+	bounds: 214, 20, 131, 83
+gun
+	bounds: 347, 14, 105, 102
+	rotate: 90
+head
+	bounds: 80, 105, 136, 149
+hoverboard-board
+	bounds: 2, 8, 246, 76
+	rotate: 90
+hoverboard-thruster
+	bounds: 478, 2, 30, 32
+hoverglow-small
+	bounds: 218, 117, 137, 38
+	rotate: 90
+mouth-grind
+	bounds: 775, 80, 47, 30
+	rotate: 90
+mouth-oooo
+	bounds: 779, 31, 47, 30
+	rotate: 90
+mouth-smile
+	bounds: 783, 207, 47, 30
+	rotate: 90
+muzzle-glow
+	bounds: 779, 4, 25, 25
+muzzle-ring
+	bounds: 451, 14, 25, 105
+muzzle01
+	bounds: 664, 60, 67, 40
+	rotate: 90
+muzzle02
+	bounds: 580, 56, 68, 42
+	rotate: 90
+muzzle03
+	bounds: 478, 36, 83, 53
+	rotate: 90
+muzzle04
+	bounds: 533, 49, 75, 45
+	rotate: 90
+muzzle05
+	bounds: 624, 56, 68, 38
+	rotate: 90
+neck
+	bounds: 806, 8, 18, 21
+portal-bg
+	bounds: 258, 121, 133, 133
+portal-flare1
+	bounds: 690, 2, 56, 30
+	rotate: 90
+portal-flare2
+	bounds: 510, 3, 57, 31
+portal-flare3
+	bounds: 722, 4, 58, 30
+	rotate: 90
+portal-shade
+	bounds: 393, 121, 133, 133
+portal-streaks1
+	bounds: 528, 126, 126, 128
+portal-streaks2
+	bounds: 656, 129, 125, 125
+rear-bracer
+	bounds: 826, 13, 28, 36
+rear-foot
+	bounds: 743, 70, 57, 30
+	rotate: 90
+rear-shin
+	bounds: 174, 14, 38, 89
+rear-thigh
+	bounds: 783, 158, 28, 47
+rear-upper-arm
+	bounds: 783, 136, 20, 44
+	rotate: 90
+torso
+	bounds: 123, 13, 49, 90

BIN
spine-ts/spine-phaser/example/assets/spineboy-pma.png


BIN
spine-ts/spine-phaser/example/assets/spineboy-pro.skel


+ 16 - 0
spine-ts/spine-phaser/example/assets/stretchyman-pma.atlas

@@ -0,0 +1,16 @@
+stretchyman-pma.png
+	size: 1024, 256
+	filter: Linear, Linear
+	pma: true
+back-arm
+	bounds: 149, 45, 72, 202
+back-leg
+	bounds: 312, 4, 100, 318
+	rotate: 90
+body
+	bounds: 223, 106, 141, 452
+	rotate: 90
+front-arm
+	bounds: 2, 26, 145, 221
+head
+	bounds: 223, 2, 87, 102

BIN
spine-ts/spine-phaser/example/assets/stretchyman-pma.png


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 512 - 0
spine-ts/spine-phaser/example/assets/stretchyman-pro.json


BIN
spine-ts/spine-phaser/example/assets/stretchyman-pro.skel


+ 44 - 0
spine-ts/spine-phaser/example/basic-example.html

@@ -0,0 +1,44 @@
+<!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.js"></script>
+    <title>Spine Phaser Example</title>
+</head>
+<body>
+    <h1>Basic example</h1>
+</body>
+<script>
+var config = {
+    type: Phaser.AUTO,
+    width: 800,
+    height: 600,
+    type: Phaser.WEBGL,
+    scene: {
+        preload: preload,
+        create: create,
+    },
+    plugins: {
+        scene: [
+            { key: "spine.SpinePlugin", plugin: spine.SpinePlugin, mapping: "spine" }
+        ]
+    }
+};
+
+let game = new Phaser.Game(config);
+
+function preload () {
+    this.load.spineBinary("spineboy-data", "assets/spineboy-pro.skel");
+    this.load.spineAtlas("spineboy-atlas", "assets/spineboy-pma.atlas");
+}
+
+function create () {
+    let spineboy = this.add.spine(400, 300, 'spineboy-data', "spineboy-atlas");
+    spineboy.scale = 0.5;
+    spineboy.animationState.setAnimation(0, "walk", true);
+}
+</script>
+</html>

+ 63 - 0
spine-ts/spine-phaser/example/batching-test.html

@@ -0,0 +1,63 @@
+<!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.js"></script>
+    <title>Spine Phaser Example</title>
+</head>
+<body>
+    <h1>Batching test</h1>
+</body>
+<script>
+var config = {
+    type: Phaser.AUTO,
+    width: 800,
+    height: 600,
+    type: Phaser.WEBGL,
+    scene: {
+        preload: preload,
+        create: create,
+        update: update,
+    },
+    plugins: {
+        scene: [
+            { key: "spine.SpinePlugin", plugin: spine.SpinePlugin, mapping: "spine" }
+        ]
+    }
+};
+
+let game = new Phaser.Game(config);
+let debug;
+
+function preload () {
+    this.load.spineJson("raptor-data", "assets/raptor-pro.json");
+    this.load.spineAtlas("raptor-atlas", "assets/raptor-pma.atlas");
+    this.load.spineBinary("spineboy-data", "assets/spineboy-pro.skel");
+    this.load.spineAtlas("spineboy-atlas", "assets/spineboy-pma.atlas");
+}
+
+function create () {
+    let plugin = this.spine;
+    let x = 25;
+    let y = 60;
+    for (let j = 0; j < 10; j++, y+= 600 / 10) {
+        for (let i = 0; i < 20; i++, x += 800 / 20) {
+            let obj = Math.random() > 0.5
+                ? this.add.spine(x, y, 'spineboy-data', "spineboy-atlas")
+                : this.add.spine(x, y, 'raptor-data', "raptor-atlas");
+            obj.animationState.setAnimation(0, "walk", true);
+            obj.scale = 0.1;
+        }
+        x = 25;
+    }
+    debug = this.add.text(0, 600 - 40, "FPS: ");
+}
+
+function update () {
+    debug.setText("draw calls: " + spine.PolygonBatcher.getAndResetGlobalDrawCalls() + "\ndelta: " + game.loop.delta);
+}
+</script>
+</html>

+ 70 - 0
spine-ts/spine-phaser/example/blend-test.html

@@ -0,0 +1,70 @@
+<!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.js"></script>
+    <title>Spine Phaser Example</title>
+</head>
+<body>
+    <h1>Blend test</h1>
+</body>
+<script>
+var config = {
+    type: Phaser.AUTO,
+    width: 800,
+    height: 600,
+    type: Phaser.WEBGL,
+    backgroundColor: '#cdcdcd',
+    scene: {
+        preload: preload,
+        create: create,
+        update: update
+    },
+    plugins: {
+        scene: [
+            { key: "spine.SpinePlugin", plugin: spine.SpinePlugin, mapping: "spine" }
+        ]
+    }
+};
+
+let controls;
+let game = new Phaser.Game(config);
+
+function preload () {
+    this.load.spineBinary("spineboy-data", "assets/spineboy-pro.skel");
+    this.load.spineAtlas("spineboy-atlas", "assets/spineboy-pma.atlas");
+}
+
+function create () {
+    for (var i = 0; i < 4; i++) {
+        let obj = this.add.spine(i * 200, 600, 'spineboy-data', 'spineboy-atlas').setScale(0.25);
+        obj.setScale(0.25);
+        obj.animationState.setAnimation(0, "idle", true);
+        obj.animationState.setAnimation(1, "shoot", true);
+    }
+    var cursors = this.input.keyboard.createCursorKeys();
+
+    var controlConfig = {
+        camera: this.cameras.main,
+        left: cursors.left,
+        right: cursors.right,
+        up: cursors.up,
+        down: cursors.down,
+        zoomIn: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q),
+        zoomOut: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.E),
+        acceleration: 0.35,
+        drag: 0.01,
+        maxSpeed: 1.2
+    };
+
+    controls = new Phaser.Cameras.Controls.SmoothedKeyControl(controlConfig);
+}
+
+function update (time, delta) {
+    controls.update(delta);
+}
+</script>
+</html>

+ 56 - 0
spine-ts/spine-phaser/example/bounds-test.html

@@ -0,0 +1,56 @@
+<!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.js"></script>
+    <title>Spine Phaser Example</title>
+</head>
+<body>
+    <h1>Bounds test</h1>
+</body>
+<script>
+var config = {
+    type: Phaser.AUTO,
+    width: 800,
+    height: 600,
+    type: Phaser.WEBGL,
+    scene: {
+        preload: preload,
+        create: create,
+        update: update,
+    },
+    plugins: {
+        scene: [
+            { key: "spine.SpinePlugin", plugin: spine.SpinePlugin, mapping: "spine" }
+        ]
+    }
+};
+
+let game = new Phaser.Game(config);
+let spineboy;
+
+function preload () {
+    this.load.spineBinary("spineboy-data", "assets/spineboy-pro.skel");
+    this.load.spineAtlas("spineboy-atlas", "assets/spineboy-pma.atlas");
+}
+
+function create () {
+    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);
+    spineboy.on("pointerdown", () => spineboy.animationState.setAnimation(0, "run", true));
+}
+
+let time = 0;
+function update (t, delta) {
+    time += delta / 1000;
+    let scale = 0.4 + Math.cos(time) * 0.2;
+    spineboy.scale = scale;
+    spineboy.angle++;
+}
+</script>
+</html>

+ 113 - 0
spine-ts/spine-phaser/example/camera-pipeline-test.html

@@ -0,0 +1,113 @@
+<!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.js"></script>
+    <title>Spine Phaser Example</title>
+</head>
+
+<body>
+    <h1>Camera pipeline test</h1>
+    <script>
+        var config = {
+            type: Phaser.AUTO,
+            width: 800,
+            height: 600,
+            type: Phaser.WEBGL,
+            backgroundColor: '#cdcdcd',
+            scene: {
+                preload: preload,
+                create: create,
+            },
+            plugins: {
+                scene: [
+                    { key: "spine.SpinePlugin", plugin: spine.SpinePlugin, mapping: "spine" }
+                ]
+            }
+        };
+
+        const fragShader = `
+        #define SHADER_NAME PLASMA_FS
+
+        precision mediump float;
+
+        uniform sampler2D uMainSampler;
+        uniform float uTime;
+        uniform vec2 uResolution;
+
+        varying vec2 outTexCoord;
+
+        void main()
+        {
+        vec2 p = -1.0 + 2.0 * gl_FragCoord.xy / uResolution.xy;
+
+        float x = p.x;
+        float y = p.y;
+        float mov0 = x+y+cos(sin(uTime)*2.0)*100.+sin(x/100.)*1000.;
+        float mov1 = y / 0.9 +  uTime;
+        float mov2 = x / 0.2;
+        float c1 = abs(sin(mov1+uTime)/2.+mov2/2.-mov1-mov2+uTime);
+        float c2 = abs(sin(c1+sin(mov0/1000.+uTime)+sin(y/40.+uTime)+sin((x+y)/100.)*3.));
+        float c3 = abs(sin(c2+cos(mov1+mov2+c2)+cos(mov2)+sin(x/1000.)));
+
+        vec4 pixel = texture2D(uMainSampler, outTexCoord);
+
+        gl_FragColor = pixel * vec4(c1, c2, c3, 1);
+        }
+        `;
+
+        class PlasmaPostFX extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline {
+            constructor(game) {
+                super({
+                    game,
+                    name: 'PlasmaPostFX',
+                    fragShader,
+                    uniforms: [
+                        'uMainSampler',
+                        'uTime',
+                        'uResolution'
+                    ]
+                });
+            }
+
+            onPreRender() {
+                this.set1f('uTime', this.game.loop.time / 1000);
+            }
+
+            onDraw(renderTarget) {
+                this.set2f('uResolution', renderTarget.width, renderTarget.height);
+
+                this.bindAndDraw(renderTarget);
+            }
+        }
+
+        let game = new Phaser.Game(config);
+
+        function preload() {
+            this.load.spineBinary("spineboy-data", "assets/spineboy-pro.skel");
+            this.load.spineAtlas("spineboy-atlas", "assets/spineboy-pma.atlas");
+            this.load.image("img", "assets/raptor-pma.png")
+        }
+
+        function create() {
+            this.renderer.pipelines.addPostPipeline('PlasmaPostFX', PlasmaPostFX);
+
+            // FIXME: Need a dummy sprite so the MultiPipeline sets up state
+            // so rendering the Spine sprite actually works. Unsure what state
+            // is needed.
+            var s = this.add.sprite(0, 0, 'img');
+
+            let spineboy = this.add.spine(400, 300, 'spineboy-data', "spineboy-atlas");
+            spineboy.scale = 0.5;
+            spineboy.animationState.setAnimation(0, "walk", true);
+
+            this.cameras.main.setPostPipeline("PlasmaPostFX");
+        }
+    </script>
+</body>
+
+</html>

+ 71 - 0
spine-ts/spine-phaser/example/control-bones-test.html

@@ -0,0 +1,71 @@
+<!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.js"></script>
+    <title>Spine Phaser Example</title>
+</head>
+<body>
+    <h1>Control bones</h1>
+<script>
+var config = {
+    type: Phaser.AUTO,
+    width: 800,
+    height: 600,
+    type: Phaser.WEBGL,
+    scene: {
+        preload: preload,
+        create: create,
+    },
+    plugins: {
+        scene: [
+            { key: "spine.SpinePlugin", plugin: spine.SpinePlugin, mapping: "spine" }
+        ]
+    }
+};
+
+let game = new Phaser.Game(config);
+
+function preload () {
+    this.load.spineBinary("stretchyman-data", "assets/stretchyman-pro.skel");
+    this.load.spineAtlas("stretchyman-atlas", "assets/stretchyman-pma.atlas");
+}
+
+function create () {
+    let stretchyman = this.add.spine(400, 550, 'stretchyman-data', "stretchyman-atlas");
+    stretchyman.scale = 0.8;
+    stretchyman.skeleton.updateWorldTransform();
+
+    var controlBones = ["back-arm-ik-target", "back-leg-ik-target", "front-arm-ik-target", "front-leg-ik-target"];
+    for (var i = 0; i < controlBones.length; i++)
+    {
+        var bone = stretchyman.skeleton.findBone(controlBones[i]);
+        let point = {x: bone.worldX, y: bone.worldY};
+        stretchyman.skeletonToPhaserWorldCoordinates(point);
+
+        var control = this.add.circle(point.x, point.y, 4, 0xff00ff).setData('bone', bone);
+
+        control.setInteractive();
+        this.input.setDraggable(control);
+        this.input.on('drag', function (pointer, gameObject, dragX, dragY) {
+
+            gameObject.x = dragX;
+            gameObject.y = dragY;
+
+            var bone = gameObject.getData('bone');
+            let point = { x: dragX, y: dragY };
+            stretchyman.phaserWorldCoordinatesToBone(point, bone);
+
+            bone.x = point.x;
+            bone.y = point.y;
+            bone.update();
+
+        }, this);
+    }
+}
+</script>
+</body>
+</html>

+ 24 - 0
spine-ts/spine-phaser/example/index.html

@@ -0,0 +1,24 @@
+<!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>
+    <title>Spine Phaser Example</title>
+</head>
+<body>
+    <h1>Spine Phaser</h1>
+    <ul>
+        <li><a href="./basic-example.html">Basic example</a></li>
+        <li><a href="./batching-test.html">Batching test</a></li>
+        <li><a href="./multi-scene-test.html">Multi-scene test</a></li>
+        <li><a href="./bounds-test.html">Bounds test</a></li>
+        <li><a href="./visibility-test.html">Visibility test</a></li>
+        <li><a href="./arcade-physics-test.html">Arcade physics example</a></li>
+        <li><a href="./blend-test.html">Blend test</a></li>
+        <li><a href="./camera-pipeline-test.html">Camera pipeline test</a></li>
+        <li><a href="./control-bones-test.html">Control bones</a></li>
+    </ul>
+</body>
+</html>

+ 65 - 0
spine-ts/spine-phaser/example/multi-scene-test.html

@@ -0,0 +1,65 @@
+<!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.js"></script>
+    <title>Spine Phaser Example</title>
+</head>
+<body>
+    <h1>Multi-scene test</h1>
+</body>
+<script>
+class Scene1 extends Phaser.Scene {
+    constructor () {
+        super({key: "Scene1"})
+    }
+
+    preload() {
+        this.load.spineBinary("spineboy-data", "assets/spineboy-pro.skel");
+        this.load.spineAtlas("spineboy-atlas", "assets/spineboy-pma.atlas");
+    }
+
+    create() {
+        let spineboy = this.add.spine(400, 500, 'spineboy-data', "spineboy-atlas");
+        spineboy.scale = 0.5;
+        spineboy.animationState.setAnimation(0, "walk", true);
+        this.input.once('pointerdown', () => this.scene.start('Scene2'));
+    }
+}
+
+class Scene2 extends Phaser.Scene {
+    constructor () {
+        super({key: "Scene2"})
+    }
+
+    preload() {
+        this.load.spineJson("raptor-data", "assets/raptor-pro.json");
+        this.load.spineAtlas("raptor-atlas", "assets/raptor-pma.atlas");
+    }
+
+    create() {
+        let raptor = this.add.spine(300, 600, 'raptor-data', "raptor-atlas");
+        raptor.scale = 0.5;
+        raptor.animationState.setAnimation(0, "walk", true);
+        this.input.once('pointerdown', () => this.scene.start('Scene1'));
+    }
+}
+
+var config = {
+    type: Phaser.AUTO,
+    width: 800,
+    height: 600,
+    type: Phaser.WEBGL,
+    scene: [ Scene1, Scene2 ],
+    plugins: {
+        scene: [
+            { key: "spine.SpinePlugin", plugin: spine.SpinePlugin, mapping: "spine" }
+        ]
+    }
+};
+let game = new Phaser.Game(config);
+</script>
+</html>

BIN
spine-ts/spine-phaser/example/nyan.png


+ 50 - 0
spine-ts/spine-phaser/example/visibility-test.html

@@ -0,0 +1,50 @@
+<!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.js"></script>
+    <title>Spine Phaser Example</title>
+</head>
+<body>
+    <h1>Basic example</h1>
+</body>
+<script>
+var config = {
+    type: Phaser.AUTO,
+    width: 800,
+    height: 600,
+    type: Phaser.WEBGL,
+    scene: {
+        preload: preload,
+        create: create,
+    },
+    plugins: {
+        scene: [
+            { key: "spine.SpinePlugin", plugin: spine.SpinePlugin, mapping: "spine" }
+        ]
+    }
+};
+
+let game = new Phaser.Game(config);
+
+function preload () {
+    this.load.spineBinary("spineboy-data", "assets/spineboy-pro.skel");
+    this.load.spineAtlas("spineboy-atlas", "assets/spineboy-pma.atlas");
+}
+
+function create () {
+    let spineboy = this.add.spine(250, 500, 'spineboy-data', "spineboy-atlas");
+    spineboy.scale = 0.5;
+    spineboy.animationState.setAnimation(0, "walk", true);
+
+    let spineboy2 = this.add.spine(550, 500, 'spineboy-data', "spineboy-atlas");
+    spineboy2.scale = 0.5;
+    spineboy2.animationState.setAnimation(0, "run", true);
+
+    this.input.on('pointerdown', () => spineboy2.visible = !spineboy2.visible);
+}
+</script>
+</html>

+ 38 - 0
spine-ts/spine-phaser/package.json

@@ -0,0 +1,38 @@
+{
+  "name": "@esotericsoftware/spine-phaser",
+  "version": "4.2.10",
+  "description": "The official Spine Runtimes for the Phaser.",
+  "main": "dist/index.js",
+  "types": "dist/index.d.ts",
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE"
+  ],
+  "scripts": {},
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/esotericsoftware/spine-runtimes.git"
+  },
+  "keywords": [
+    "gamedev",
+    "animations",
+    "2d",
+    "spine",
+    "game-dev",
+    "runtimes",
+    "skeletal"
+  ],
+  "author": "Esoteric Software LLC",
+  "license": "LicenseRef-LICENSE",
+  "bugs": {
+    "url": "https://github.com/esotericsoftware/spine-runtimes/issues"
+  },
+  "homepage": "https://github.com/esotericsoftware/spine-runtimes#readme",
+  "dependencies": {
+    "@esotericsoftware/spine-core": "^4.2.10",
+    "@esotericsoftware/spine-webgl": "^4.2.10",
+    "@esotericsoftware/spine-canvas": "^4.2.10",
+    "phaser": "^3.55.2"
+  }
+}

+ 255 - 0
spine-ts/spine-phaser/src/SpineGameObject.ts

@@ -0,0 +1,255 @@
+import { SPINE_GAME_OBJECT_TYPE } from "./keys";
+import { SpinePlugin } from "./SpinePlugin";
+import { ComputedSizeMixin, DepthMixin, FlipMixin, ScrollFactorMixin, TransformMixin, VisibleMixin } from "./mixins";
+import { AnimationState, AnimationStateData, Bone, MathUtils, Skeleton, Skin, Vector2 } from "@esotericsoftware/spine-core";
+import { Vector3 } from "@esotericsoftware/spine-webgl";
+
+class BaseSpineGameObject extends Phaser.GameObjects.GameObject {
+	constructor(scene: Phaser.Scene, type: string) {
+		super(scene, type);
+	}
+}
+
+export interface SpineGameObjectBoundsProvider {
+	calculateBounds(gameObject: SpineGameObject): { x: number, y: number, width: number, height: number };
+}
+
+export class SetupPoseBoundsProvider implements SpineGameObjectBoundsProvider {
+	calculateBounds(gameObject: SpineGameObject) {
+		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();
+		return skeleton.getBoundsRect();
+	}
+}
+
+export class SkinsAndAnimationBoundsProvider implements SpineGameObjectBoundsProvider {
+	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 || !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;
+		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();
+			return skeleton.getBoundsRect();
+		} 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++) {
+				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 };
+		}
+	}
+}
+
+export class SpineGameObject extends ComputedSizeMixin(DepthMixin(FlipMixin(ScrollFactorMixin(TransformMixin(VisibleMixin(BaseSpineGameObject)))))) {
+	blendMode = -1;
+	skeleton: Skeleton | null = null;
+	animationStateData: AnimationStateData | null = null;
+	animationState: AnimationState | null = null;
+	private premultipliedAlpha = false;
+	private _displayOriginX = 0;
+	private _displayOriginY = 0;
+	private _scaleX = 1;
+	private _scaleY = 1;
+
+	constructor(scene: Phaser.Scene, private plugin: SpinePlugin, x: number, y: number, dataKey: string, atlasKey: string, public boundsProvider: SpineGameObjectBoundsProvider = new SetupPoseBoundsProvider()) {
+		super(scene, SPINE_GAME_OBJECT_TYPE);
+		this.setPosition(x, y); x
+		this.setSkeleton(dataKey, atlasKey);
+	}
+
+	setSkeleton(dataKey: string, atlasKey: string) {
+		if (dataKey && atlasKey) {
+			this.premultipliedAlpha = this.plugin.isAtlasPremultiplied(atlasKey);
+			this.skeleton = this.plugin.createSkeleton(dataKey, atlasKey);
+			this.animationStateData = new AnimationStateData(this.skeleton.data);
+			this.animationState = new AnimationState(this.animationStateData);
+			this.updateSize();
+		} else {
+			this.skeleton = null;
+			this.animationStateData = null;
+			this.animationState = null;
+		}
+	}
+
+	public get displayOriginX() {
+		return this._displayOriginX;
+	}
+
+	public set displayOriginX(value: number) {
+		this._displayOriginX = value;
+	}
+
+	public get displayOriginY() {
+		return this._displayOriginY;
+	}
+
+	public set displayOriginY(value: number) {
+		this._displayOriginY = value;
+	}
+
+	public get scaleX() {
+		return this._scaleX;
+	}
+
+	public set scaleX(value: number) {
+		this._scaleX = value;
+		this.updateSize();
+	}
+
+	public get scaleY() {
+		return this._scaleY;
+	}
+
+	public set scaleY(value: number) {
+		this._scaleY = value;
+		this.updateSize();
+	}
+
+	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...
+		let self = this as any;
+		self.width = bounds.width;
+		self.height = bounds.height;
+		this.displayOriginX = -bounds.x;
+		this.displayOriginY = -bounds.y;
+	}
+
+	skeletonToPhaserWorldCoordinates(point: {x: number, y: number}) {
+		let transform = this.getWorldTransformMatrix();
+		let a = transform.a, b = transform.b, c = transform.c, d = transform.d, tx = transform.tx, ty = transform.ty;
+		let x = point.x
+		let y = point.y
+		point.x = x * a + y * c + tx;
+		point.y = x * b + y * d + ty;
+	}
+
+	phaserWorldCoordinatesToSkeleton(point: {x: number, y: number}) {
+		let transform = this.getWorldTransformMatrix();
+		transform = transform.invert();
+		let a = transform.a, b = transform.b, c = transform.c, d = transform.d, tx = transform.tx, ty = transform.ty;
+		let x = point.x
+		let y = point.y
+		point.x = x * a + y * c + tx;
+		point.y = x * b + y * d + ty;
+	}
+
+	phaserWorldCoordinatesToBone(point: {x: number, y: number}, bone: Bone) {
+		this.phaserWorldCoordinatesToSkeleton(point);
+		if (bone.parent) {
+			bone.parent.worldToLocal(point as Vector2);
+		} else {
+			bone.worldToLocal(point as Vector2);
+		}
+	}
+
+	preUpdate(time: number, delta: number) {
+		if (!this.skeleton || !this.animationState) return;
+
+		this.animationState.update(delta / 1000);
+		this.animationState.apply(this.skeleton);
+		this.skeleton.updateWorldTransform();
+	}
+
+	preDestroy() {
+		this.skeleton = null;
+		this.animationState = null;
+		// FIXME tear down any event emitters
+	}
+
+	willRender(camera: Phaser.Cameras.Scene2D.Camera) {
+		if (!this.visible) return false;
+
+		var GameObjectRenderMask = 0xf;
+		var result = (!this.skeleton || !(GameObjectRenderMask !== this.renderFlags || (this.cameraFilter !== 0 && (this.cameraFilter & camera.id))));
+
+		return result;
+	}
+
+	renderWebGL(renderer: Phaser.Renderer.WebGL.WebGLRenderer, src: SpineGameObject, camera: Phaser.Cameras.Scene2D.Camera, parentMatrix: Phaser.GameObjects.Components.TransformMatrix) {
+		if (!this.skeleton || !this.animationState || !this.plugin.webGLRenderer) return;
+
+		let sceneRenderer = this.plugin.webGLRenderer;
+		if (renderer.newType) {
+			renderer.pipelines.clear();
+			sceneRenderer.begin();
+		}
+
+		camera.addToRenderList(src);
+		let transform = Phaser.GameObjects.GetCalcMatrix(src, camera, parentMatrix).calc;
+		let a = transform.a, b = transform.b, c = transform.c, d = transform.d, tx = transform.tx, ty = transform.ty;
+		sceneRenderer.drawSkeleton(this.skeleton, this.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;
+			}
+		});
+
+		if (!renderer.nextTypeMatch) {
+			sceneRenderer.end();
+			renderer.pipelines.rebind();
+		}
+	}
+
+	renderCanvas(renderer: Phaser.Renderer.Canvas.CanvasRenderer, src: SpineGameObject, camera: Phaser.Cameras.Scene2D.Camera, parentMatrix: Phaser.GameObjects.Components.TransformMatrix) {
+		if (!this.skeleton || !this.animationState || !this.plugin.canvasRenderer) return;
+
+		let context = renderer.currentContext;
+		let skeletonRenderer = this.plugin.canvasRenderer;
+		(skeletonRenderer as any).ctx = context;
+
+		camera.addToRenderList(src);
+		let transform = Phaser.GameObjects.GetCalcMatrix(src, camera, parentMatrix).calc;
+		let skeleton = this.skeleton;
+		skeleton.x = transform.tx;
+		skeleton.y = transform.ty;
+		skeleton.scaleX = transform.scaleX;
+		skeleton.scaleY = transform.scaleY;
+		let root = skeleton.getRootBone()!;
+		root.rotation = -MathUtils.radiansToDegrees * transform.rotationNormalized;
+		this.skeleton.updateWorldTransform();
+
+		context.save();
+		skeletonRenderer.draw(skeleton);
+		context.restore();
+	}
+}

+ 315 - 0
spine-ts/spine-phaser/src/SpinePlugin.ts

@@ -0,0 +1,315 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated September 24, 2021. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2021, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+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, SpineGameObjectBoundsProvider } from "./SpineGameObject";
+import { CanvasTexture, SkeletonRenderer } from "@esotericsoftware/spine-canvas";
+
+export class SpinePlugin extends Phaser.Plugins.ScenePlugin {
+	game: Phaser.Game;
+	isWebGL: boolean;
+	gl: WebGLRenderingContext | null;
+	textureManager: Phaser.Textures.TextureManager;
+	phaserRenderer: Phaser.Renderer.Canvas.CanvasRenderer | Phaser.Renderer.WebGL.WebGLRenderer | null;
+	webGLRenderer: SceneRenderer | null;
+	canvasRenderer: SkeletonRenderer | null;
+	skeletonDataCache: Phaser.Cache.BaseCache;
+	atlasCache: Phaser.Cache.BaseCache;
+
+	constructor (scene: Phaser.Scene, pluginManager: Phaser.Plugins.PluginManager, pluginKey: string) {
+		super(scene, pluginManager, pluginKey);
+		var game = this.game = pluginManager.game;
+		this.isWebGL = this.game.config.renderType === 2;
+		this.gl = this.isWebGL ? (this.game.renderer as Phaser.Renderer.WebGL.WebGLRenderer).gl : null;
+		this.textureManager = this.game.textures;
+		this.phaserRenderer = this.game.renderer;
+		this.webGLRenderer = null;
+		this.canvasRenderer = null;
+		this.skeletonDataCache = this.game.cache.addCustom(SPINE_SKELETON_DATA_CACHE_KEY);
+		this.atlasCache = this.game.cache.addCustom(SPINE_ATLAS_CACHE_KEY);
+
+		if (!this.phaserRenderer) {
+			this.phaserRenderer = {
+				width: game.scale.width,
+				height: game.scale.height,
+				preRender: () => { },
+				postRender: () => { },
+				render: () => { },
+				destroy: () => { }
+			} as unknown as Phaser.Renderer.Canvas.CanvasRenderer;
+		}
+
+		let skeletonJsonFileCallback = function (this: any, key: string,
+			url: string,
+			xhrSettings: Phaser.Types.Loader.XHRSettingsObject) {
+			let file = new SpineSkeletonDataFile(this as any, key, url, SpineSkeletonDataFileType.json, xhrSettings);
+			this.addFile(file.files);
+			return this;
+		};
+		pluginManager.registerFileType("spineJson", skeletonJsonFileCallback, scene);
+
+
+		let skeletonBinaryFileCallback = function (this: any, key: string,
+			url: string,
+			xhrSettings: Phaser.Types.Loader.XHRSettingsObject) {
+			let file = new SpineSkeletonDataFile(this as any, key, url, SpineSkeletonDataFileType.binary, xhrSettings);
+			this.addFile(file.files);
+			return this;
+		};
+		pluginManager.registerFileType("spineBinary", skeletonBinaryFileCallback, scene);
+
+
+		let atlasFileCallback = function (this: any, key: string,
+			url: string,
+			premultipliedAlpha: boolean,
+			xhrSettings: Phaser.Types.Loader.XHRSettingsObject) {
+			let file = new SpineAtlasFile(this as any, key, url, premultipliedAlpha, xhrSettings);
+			this.addFile(file.files);
+			return this;
+		};
+		pluginManager.registerFileType("spineAtlas", atlasFileCallback, scene);
+
+		let self = this;
+		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;
+		};
+
+		let makeSpineGameObject = function (this: Phaser.GameObjects.GameObjectFactory, config: any, addToScene: boolean) {
+			let dataKey = config.dataKey ? config.dataKey : null;
+			let atlasKey = config.atlasKey ? config.atlasKey : null;
+			let gameObject = new SpineGameObject(this.scene, self, 0, 0, dataKey, atlasKey);
+			if (addToScene !== undefined) {
+				config.add = addToScene;
+			}
+			Phaser.GameObjects.BuildGameObject(this.scene, gameObject, config);
+		}
+		pluginManager.registerGameObject(SPINE_GAME_OBJECT_TYPE, addSpineGameObject, makeSpineGameObject);
+	}
+
+	boot () {
+		Skeleton.yDown = true;
+		if (this.isWebGL) {
+			if (!this.webGLRenderer) {
+				this.webGLRenderer = new SceneRenderer((this.phaserRenderer! as Phaser.Renderer.WebGL.WebGLRenderer).canvas, this.gl!, true);
+			}
+			this.game.scale.on(Phaser.Scale.Events.RESIZE, this.onResize, this);
+		} else {
+			if (!this.canvasRenderer) {
+				this.canvasRenderer = new SkeletonRenderer(this.scene.sys.context);
+			}
+		}
+
+		var eventEmitter = this.systems.events;
+		eventEmitter.once('shutdown', this.shutdown, this);
+		eventEmitter.once('destroy', this.destroy, this);
+		this.game.events.once('destroy', this.gameDestroy, this);
+	}
+
+	onResize () {
+		var phaserRenderer = this.phaserRenderer;
+		var sceneRenderer = this.webGLRenderer;
+
+		if (phaserRenderer && sceneRenderer) {
+			var viewportWidth = phaserRenderer.width;
+			var viewportHeight = phaserRenderer.height;
+			sceneRenderer.camera.position.x = viewportWidth / 2;
+			sceneRenderer.camera.position.y = viewportHeight / 2;
+			sceneRenderer.camera.up.y = -1;
+			sceneRenderer.camera.direction.z = 1;
+			sceneRenderer.camera.setViewport(viewportWidth, viewportHeight);
+		}
+	}
+
+	shutdown () {
+		this.systems.events.off("shutdown", this.shutdown, this);
+		if (this.isWebGL) {
+			this.game.scale.off(Phaser.Scale.Events.RESIZE, this.onResize, this);
+		}
+	}
+
+	destroy () {
+		this.shutdown()
+	}
+
+	gameDestroy () {
+		this.pluginManager.removeGameObject(SPINE_GAME_OBJECT_TYPE, true, true);
+		this.pluginManager.removeGameObject(SPINE_CONTAINER_TYPE, true, true);
+		if (this.webGLRenderer) this.webGLRenderer.dispose();
+	}
+
+	isAtlasPremultiplied(atlasKey: string) {
+		let atlasFile = this.game.cache.text.get(atlasKey);
+		if (!atlasFile) return false;
+		return atlasFile.premultipliedAlpha;
+	}
+
+	createSkeleton (dataKey: string, atlasKey: string) {
+		let atlas: TextureAtlas;
+		if (this.atlasCache.exists(atlasKey)) {
+			atlas = this.atlasCache.get(atlasKey);
+		} else {
+			let atlasFile = this.game.cache.text.get(atlasKey) as { data: string, premultipliedAlpha: boolean };
+			atlas = new TextureAtlas(atlasFile.data);
+			if (this.isWebGL) {
+				let gl = this.gl!;
+				gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
+				for (let atlasPage of atlas.pages) {
+					atlasPage.setTexture(new GLTexture(gl, this.textureManager.get(atlasKey + "!" + atlasPage.name).getSourceImage() as HTMLImageElement | ImageBitmap, false));
+				}
+			} else {
+				for (let atlasPage of atlas.pages) {
+					atlasPage.setTexture(new CanvasTexture(this.textureManager.get(atlasKey + "!" + atlasPage.name).getSourceImage() as HTMLImageElement | ImageBitmap));
+				}
+			}
+			this.atlasCache.add(atlasKey, atlas);
+		}
+
+		let skeletonData: SkeletonData;
+		if (this.skeletonDataCache.exists(dataKey)) {
+			skeletonData = this.skeletonDataCache.get(dataKey);
+		} else {
+			if (this.game.cache.json.exists(dataKey)) {
+				let jsonFile = this.game.cache.json.get(dataKey) as any;
+				let json = new SkeletonJson(new AtlasAttachmentLoader(atlas));
+				skeletonData = json.readSkeletonData(jsonFile);
+			} else {
+				let binaryFile = this.game.cache.binary.get(dataKey) as ArrayBuffer;
+				let binary = new SkeletonBinary(new AtlasAttachmentLoader(atlas));
+				skeletonData = binary.readSkeletonData(new Uint8Array(binaryFile));
+			}
+			this.skeletonDataCache.add(dataKey, skeletonData);
+		}
+
+		return new Skeleton(skeletonData);
+	}
+}
+
+export enum SpineSkeletonDataFileType {
+	json,
+	binary
+}
+
+export class SpineSkeletonDataFile extends Phaser.Loader.MultiFile {
+	constructor (loader: Phaser.Loader.LoaderPlugin, key: string, url: string, public fileType: SpineSkeletonDataFileType, xhrSettings: Phaser.Types.Loader.XHRSettingsObject) {
+		let file = null;
+		let isJson = fileType == SpineSkeletonDataFileType.json;
+		if (isJson) {
+			file = new Phaser.Loader.FileTypes.JSONFile(loader, {
+				key: key,
+				url: url,
+				extension: "json",
+				xhrSettings: xhrSettings,
+			} as Phaser.Types.Loader.FileTypes.JSONFileConfig);
+		} else {
+			file = new Phaser.Loader.FileTypes.BinaryFile(loader, {
+				key: key,
+				url: url,
+				extension: "skel",
+				xhrSettings: xhrSettings,
+			} as Phaser.Types.Loader.FileTypes.BinaryFileConfig);
+		}
+		super(loader, SPINE_SKELETON_DATA_FILE_TYPE, key, [file]);
+	}
+
+	onFileComplete (file: Phaser.Loader.File) {
+		this.pending--;
+	}
+
+	addToCache () {
+		if (this.isReadyToProcess()) this.files[0].addToCache();
+	}
+}
+
+export class SpineAtlasFile extends Phaser.Loader.MultiFile {
+	constructor (loader: Phaser.Loader.LoaderPlugin, key: string, url: string, public premultipliedAlpha: boolean = true, xhrSettings: Phaser.Types.Loader.XHRSettingsObject) {
+		super(loader, SPINE_ATLAS_FILE_TYPE, key, [
+			new Phaser.Loader.FileTypes.TextFile(loader, {
+				key: key,
+				url: url,
+				xhrSettings: xhrSettings,
+				extension: "atlas"
+			})
+		]);
+	}
+
+	onFileComplete (file: Phaser.Loader.File) {
+		if (this.files.indexOf(file) != -1) {
+			this.pending--;
+
+			if (file.type == "text") {
+				var lines = file.data.split('\n');
+				let textures = [];
+				textures.push(lines[0]);
+				for (var t = 1; t < lines.length; t++) {
+					var line = lines[t];
+					if (line.trim() === '' && t < lines.length - 1) {
+						line = lines[t + 1];
+						textures.push(line);
+					}
+				}
+
+				let basePath = file.src.match(/^.*\//);
+				for (var i = 0; i < textures.length; i++) {
+					var url = basePath + textures[i];
+					var key = file.key + "!" + textures[i];
+					var image = new Phaser.Loader.FileTypes.ImageFile(this.loader, key, url);
+
+					if (!this.loader.keyExists(image)) {
+						this.addToMultiFile(image);
+						this.loader.addFile(image);
+					}
+				}
+			}
+		}
+	}
+
+	addToCache () {
+		if (this.isReadyToProcess()) {
+			let textureManager = this.loader.textureManager;
+			for (let file of this.files) {
+				if (file.type == "image") {
+					if (!textureManager.exists(file.key)) {
+						textureManager.addImage(file.key, file.data);
+					}
+				} else {
+					file.data = {
+						data: file.data,
+						premultipliedAlpha: this.premultipliedAlpha || file.data.indexOf("pma: true") >= 0
+					};
+					file.addToCache();
+				}
+			}
+		}
+	}
+}

+ 8 - 0
spine-ts/spine-phaser/src/index.ts

@@ -0,0 +1,8 @@
+export * from "./require-shim"
+export * from "./SpinePlugin"
+export * from "./SpineGameObject"
+export * from "./mixins"
+export * from "@esotericsoftware/spine-core";
+export * from "@esotericsoftware/spine-webgl";
+import { SpinePlugin } from "./SpinePlugin";
+(window as any).spine = { SpinePlugin: SpinePlugin };

+ 8 - 0
spine-ts/spine-phaser/src/keys.ts

@@ -0,0 +1,8 @@
+export const SPINE_SKELETON_FILE_CACHE_KEY = "esotericsoftware.spine.skeletonFile.cache";
+export const SPINE_ATLAS_CACHE_KEY = "esotericsoftware.spine.atlas.cache";
+export const SPINE_ATLAS_TEXTURE_CACHE_KEY = "esotericsoftware.spine.atlas.texture.cache";
+export const SPINE_LOADER_TYPE = "spine";
+export const SPINE_SKELETON_DATA_FILE_TYPE = "spineSkeletonData";
+export const SPINE_ATLAS_FILE_TYPE = "spineAtlasData";
+export const SPINE_GAME_OBJECT_TYPE = "spine";
+export const SPINE_CONTAINER_TYPE = "spineContainer";

+ 77 - 0
spine-ts/spine-phaser/src/mixins.ts

@@ -0,0 +1,77 @@
+/*
+The MIT License (MIT)
+
+Copyright (c) 2021-present AgogPixel
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+// Adapted from https://github.com/agogpixel/phaser3-ts-utils/tree/main
+
+let components = (Phaser.GameObjects.Components as any);
+export const ComputedSize = components.ComputedSize;
+export const Depth = components.Depth;
+export const Flip = components.Flip;
+export const ScrollFactor = components.ScrollFactor;
+export const Transform = components.Transform;
+export const Visible = components.Visible;
+
+export interface Type<
+	T,
+	P extends any[] = any[]
+	> extends Function {
+	new(...args: P): T;
+}
+
+export type Mixin<GameObjectComponent, GameObjectConstraint extends Phaser.GameObjects.GameObject> = <
+	GameObjectType extends Type<GameObjectConstraint>
+	>(
+	BaseGameObject: GameObjectType
+) => GameObjectType & Type<GameObjectComponent>;
+
+export function createMixin<
+	GameObjectComponent,
+	GameObjectConstraint extends Phaser.GameObjects.GameObject = Phaser.GameObjects.GameObject
+> (
+	...component: GameObjectComponent[]
+): Mixin<GameObjectComponent, GameObjectConstraint> {
+	return (BaseGameObject) => {
+		(Phaser as any).Class.mixin(BaseGameObject, component);
+		return BaseGameObject as any;
+	};
+}
+
+type ComputedSizeMixin = Mixin<Phaser.GameObjects.Components.Transform, Phaser.GameObjects.GameObject>;
+export const ComputedSizeMixin: ComputedSizeMixin = createMixin<Phaser.GameObjects.Components.ComputedSize>(ComputedSize);
+
+type DepthMixin = Mixin<Phaser.GameObjects.Components.Depth, Phaser.GameObjects.GameObject>;
+export const DepthMixin: DepthMixin = createMixin<Phaser.GameObjects.Components.Depth>(Depth);
+
+type FlipMixin = Mixin<Phaser.GameObjects.Components.Flip, Phaser.GameObjects.GameObject>;
+export const FlipMixin: FlipMixin = createMixin<Phaser.GameObjects.Components.Flip>(Flip);
+
+type ScrollFactorMixin = Mixin<Phaser.GameObjects.Components.ScrollFactor, Phaser.GameObjects.GameObject>;
+export const ScrollFactorMixin: ScrollFactorMixin = createMixin<Phaser.GameObjects.Components.ScrollFactor>(ScrollFactor);
+
+type TransformMixin = Mixin<Phaser.GameObjects.Components.Transform, Phaser.GameObjects.GameObject>;
+export const TransformMixin: TransformMixin = createMixin<Phaser.GameObjects.Components.Transform>(Transform);
+
+type VisibleMixin = Mixin<Phaser.GameObjects.Components.Visible, Phaser.GameObjects.GameObject>;
+export const VisibleMixin: VisibleMixin = createMixin<Phaser.GameObjects.Components.Visible>(Visible);
+

+ 11 - 0
spine-ts/spine-phaser/src/require-shim.ts

@@ -0,0 +1,11 @@
+declare global {
+	var require: any;
+}
+if (window.Phaser) {
+	let prevRequire = window.require;
+	window.require = (x: string) => {
+		if (prevRequire) return prevRequire(x);
+		else if (x === "Phaser") return window.Phaser;
+	}
+}
+export { }

+ 36 - 0
spine-ts/spine-phaser/tsconfig.json

@@ -0,0 +1,36 @@
+{
+	"extends": "../tsconfig.base.json",
+	"compilerOptions": {
+		"baseUrl": ".",
+		"rootDir": "./src",
+		"outDir": "./dist",
+		"paths": {
+			"@esotericsoftware/spine-core": [
+				"../spine-core/src"
+			],
+			"@esotericsoftware/spine-canvas": [
+				"../spine-canvas/src"
+			],
+			"@esotericsoftware/spine-webgl": [
+				"../spine-webgl/src"
+			]
+		}
+	},
+	"include": [
+		"**/*.ts",
+	],
+	"exclude": [
+		"dist/**/*.d.ts"
+	],
+	"references": [
+		{
+			"path": "../spine-core"
+		},
+		{
+			"path": "../spine-canvas"
+		},
+		{
+			"path": "../spine-webgl"
+		}
+	]
+}

+ 1 - 1
spine-ts/spine-player/src/index.ts

@@ -1,3 +1,3 @@
 export * from './Player';
 export * from "@esotericsoftware/spine-core";
-export * from "@esotericsoftware/spine-webgl";
+export * from "@esotericsoftware/spine-webgl";

+ 8 - 0
spine-ts/spine-webgl/src/PolygonBatcher.ts

@@ -36,6 +36,7 @@ import { ManagedWebGLRenderingContext } from "./WebGL";
 export class PolygonBatcher implements Disposable {
 	private context: ManagedWebGLRenderingContext;
 	private drawCalls = 0;
+	private static globalDrawCalls = 0;
 	private isDrawing = false;
 	private mesh: Mesh;
 	private shader: Shader | null = null;
@@ -120,6 +121,7 @@ export class PolygonBatcher implements Disposable {
 		this.mesh.setVerticesLength(0);
 		this.mesh.setIndicesLength(0);
 		this.drawCalls++;
+		PolygonBatcher.globalDrawCalls++;
 	}
 
 	end () {
@@ -138,6 +140,12 @@ export class PolygonBatcher implements Disposable {
 		return this.drawCalls;
 	}
 
+	static getAndResetGlobalDrawCalls () {
+		let result = PolygonBatcher.globalDrawCalls;
+		PolygonBatcher.globalDrawCalls = 0;
+		return result;
+	}
+
 	dispose () {
 		this.mesh.dispose();
 	}

+ 3 - 3
spine-ts/spine-webgl/src/SceneRenderer.ts

@@ -34,7 +34,7 @@ import { PolygonBatcher } from "./PolygonBatcher";
 import { Shader } from "./Shader";
 import { ShapeRenderer } from "./ShapeRenderer";
 import { SkeletonDebugRenderer } from "./SkeletonDebugRenderer";
-import { SkeletonRenderer } from "./SkeletonRenderer";
+import { SkeletonRenderer, VertexTransformer } from "./SkeletonRenderer";
 import { ManagedWebGLRenderingContext } from "./WebGL";
 ;
 
@@ -86,10 +86,10 @@ export class SceneRenderer implements Disposable {
 		this.enableRenderer(this.batcher);
 	}
 
-	drawSkeleton (skeleton: Skeleton, premultipliedAlpha = false, slotRangeStart = -1, slotRangeEnd = -1) {
+	drawSkeleton (skeleton: Skeleton, premultipliedAlpha = false, slotRangeStart = -1, slotRangeEnd = -1, transform: VertexTransformer | null = null) {
 		this.enableRenderer(this.batcher);
 		this.skeletonRenderer.premultipliedAlpha = premultipliedAlpha;
-		this.skeletonRenderer.draw(this.batcher, skeleton, slotRangeStart, slotRangeEnd);
+		this.skeletonRenderer.draw(this.batcher, skeleton, slotRangeStart, slotRangeEnd, transform);
 	}
 
 	drawSkeletonDebug (skeleton: Skeleton, premultipliedAlpha = false, ignoredBones?: Array<string>) {

+ 5 - 1
spine-ts/spine-webgl/src/SkeletonRenderer.ts

@@ -37,6 +37,8 @@ class Renderable {
 	constructor (public vertices: NumberArrayLike, public numVertices: number, public numFloats: number) { }
 };
 
+export type VertexTransformer = (vertices: NumberArrayLike, numVertices: number, stride: number) => void;
+
 export class SkeletonRenderer {
 	static QUAD_TRIANGLES = [0, 1, 2, 2, 3, 0];
 
@@ -60,7 +62,7 @@ export class SkeletonRenderer {
 		this.vertices = Utils.newFloatArray(this.vertexSize * 1024);
 	}
 
-	draw (batcher: PolygonBatcher, skeleton: Skeleton, slotRangeStart: number = -1, slotRangeEnd: number = -1) {
+	draw (batcher: PolygonBatcher, skeleton: Skeleton, slotRangeStart: number = -1, slotRangeEnd: number = -1, transformer: VertexTransformer | null = null) {
 		let clipper = this.clipper;
 		let premultipliedAlpha = this.premultipliedAlpha;
 		let twoColorTint = this.twoColorTint;
@@ -174,6 +176,7 @@ export class SkeletonRenderer {
 					clipper.clipTriangles(renderable.vertices, renderable.numFloats, triangles, triangles.length, uvs, finalColor, darkColor, twoColorTint);
 					let clippedVertices = new Float32Array(clipper.clippedVertices);
 					let clippedTriangles = clipper.clippedTriangles;
+					if (transformer) transformer(renderable.vertices, renderable.numFloats, vertexSize);
 					batcher.draw(texture, clippedVertices, clippedTriangles);
 				} else {
 					let verts = renderable.vertices;
@@ -201,6 +204,7 @@ export class SkeletonRenderer {
 						}
 					}
 					let view = (renderable.vertices as Float32Array).subarray(0, renderable.numFloats);
+					if (transformer) transformer(renderable.vertices, renderable.numFloats, vertexSize);
 					batcher.draw(texture, view, triangles);
 				}
 			}

+ 2 - 1
spine-ts/tsconfig.base.json

@@ -8,7 +8,8 @@
 		"esModuleInterop": true,
 		"lib": [
 			"DOM",
-			"ES2015"
+			"ES2015",
+			"ScriptHost"
 		],
 		"declaration": true,
 		"composite": true,

+ 3 - 0
spine-ts/tsconfig.json

@@ -10,6 +10,9 @@
 		{
 			"path": "./spine-webgl"
 		},
+		{
+			"path": "./spine-phaser"
+		},
 		{
 			"path": "./spine-player"
 		},

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.