Add PixiJS v8 support to spine (#2641)

* add v8 support to spine!

* Renamed examples folder for consistency.

* Gitignore dist.

* Tsconfig.

* Package json.

* Modification due to strictNullChecks=true.

* Run tsfmt.

* Use clipper.clipTriangles not deprecated version.

* Aligned example to spine-pixi (v7).

* Fix clipping dark tint wrong param.

* Removed useless clipper.

* Push texture issue repro example

* fix attachment.uvs by copying them

* SlotObject alpha connected to skeleton and slot alpha.

* add topology for future v8 release

* Dark tint rendered is enabled if at least one slot has dark tint, or by configuration.
Fixed clipping while using dark tint.

* Optimized clipping by using clipTrianglesUnpacked.

* Repro example for clipping issue.

* Aligned constructor and from signature of spine-pixi(-v7) to v8. Deprecated old signatures.

* Removed useless function.

* Fixed clipping issue flagging attachment as dirty if indices change.

* Clipping attachments clip slot object through Pixi Graphics masks.

* Add autoUpdate in SpineFromOptions

* Added javadoc to pixiv8

* Updated pixi7 examples to use SpineFromOptions interface

* Aligned atlas loader to use texturePreference for bundles.

* Add pool to manage slot objects masks

* Fixed minor issues with SpineDebugRenderer

* Aligned spine-pixi-v8 with latest spine-core

* Updated build and publish script

---------

Co-authored-by: Davide Tantillo <iamdjj@gmail.com>
This commit is contained in:
Mat Groves 2024-11-06 16:23:01 +00:00 committed by GitHub
parent b610bd7b7a
commit ecbe9b0247
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
111 changed files with 38050 additions and 45 deletions

1
.gitignore vendored
View File

@ -146,6 +146,7 @@ spine-ts/spine-webgl/dist
spine-ts/spine-player/dist
spine-ts/spine-threejs/dist
spine-ts/spine-pixi/dist
spine-ts/spine-pixi-v8/dist
spine-libgdx/gradle
spine-libgdx/gradlew
spine-libgdx/gradlew.bat

View File

@ -10,7 +10,8 @@ up into multiple modules:
1. `spine-threejs/`, a self-contained [THREE.JS](https://threejs.org/) backend, built on the core classes.
1. `spine-player/`, a self-contained player to easily display Spine animations on your website, built on the core classes and WebGL backend.
1. `spine-phaser/`, a [Phaser](https://phaser.io/) backend, built on the core classes.
1. `spine-pixi/`, a [PixiJS](https://pixijs.com/) backend, built on the core classes.
1. `spine-pixi/`, a [PixiJS v7](https://pixijs.com/) backend, built on the core classes.
1. `spine-pixi-v8/`, a [PixiJS v8](https://pixijs.com/) backend, built on the core classes.
In most cases, the `spine-player` module is best suited for your needs. Please refer to the [Spine Web Player documentation](https://esotericsoftware.com/spine-player) for more information.
@ -18,7 +19,7 @@ For documentation of the core API in `spine-core`, please refer to our [Spine Ru
For documentation of `spine-phaser`, please refer to our [spine-phaser Guide](https://esotericsoftware.com/spine-phaser).
For documentation of `spine-pixi`, please refer to our [spine-pixi Guide](https://esotericsoftware.com/spine-pixi).
For documentation of `spine-pixi` and `spine-pixi`, please refer to our [spine-pixi Guide](https://esotericsoftware.com/spine-pixi).
For documentation of `spine-canvaskit`, please refer to our [spine-canvaskit Guide](https://esotericsoftware.com/spine-canvaskit).
@ -79,6 +80,9 @@ You can include a module in your project via a `<script>` tag from the [unpkg](h
// spine-pixi
<script src="https://unpkg.com/@esotericsoftware/spine-pixi@4.2.*/dist/iife/spine-pixi.js"></script>
// spine-pixi-v8
<script src="https://unpkg.com/@esotericsoftware/spine-pixi-v8@4.2.*/dist/iife/spine-pixi-v8.js"></script>
```
We also provide `js.map` source maps. They will be automatically fetched from unpkg when debugging code of a spine-module in Chrome, Firefox, or Safari, mapping the JavaScript code back to its original TypeScript sources.
@ -98,6 +102,7 @@ npm install @esotericsoftware/spine-player
npm install @esotericsoftware/spine-threejs
npm install @esotericsoftware/spine-phaser
npm install @esotericsoftware/spine-pixi
npm install @esotericsoftware/spine-pixi-v8
```
spine-ts modules are provided in the [ECMAScript format](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules), which can be consumed natively by all modern browsers, or bundled by tools like [webpack](https://webpack.js.org/), [Babel](https://babeljs.io/), [Parcel](https://parceljs.org/), or [esbuild](https://esbuild.github.io/). You can import functions and classes from a spine-ts module in your JavaScript or TypeScript code using the `import` syntax to get access to all exported constants, functions, and classes of a module:

View File

@ -21,6 +21,7 @@ then
spine-player/dist/iife/* \
spine-threejs/dist/iife/* \
spine-pixi/dist/iife/* \
spine-pixi-v8/dist/iife/* \
spine-phaser/dist/iife/* \
spine-player/css/spine-player.css
curl -f -F "file=@spine-ts.zip" "$TS_UPDATE_URL$BRANCH"

View File

@ -54,6 +54,31 @@
<li><a href="/spine-pixi/example/physics4.html">Physics IV</a></li>
<li><a href="/spine-pixi/example/slot-objects.html">Slot Objects</a></li>
</ul>
<li>Pixi V8</li>
<ul>
<li><a href="/spine-pixi-v8/example/index.html">Basic example</a></li>
<li><a href="/spine-pixi-v8/example/manual-loading.html">Manual Loading</a></li>
<li>
<a href="/spine-pixi-v8/example/events-example.html">Events example</a>
</li>
<li>
<a href="/spine-pixi-v8/example/mix-and-match-example.html">Mix and match example</a>
</li>
<li>
<a href="/spine-pixi-v8/example/simple-input.html">Simple input</a>
</li>
<li>
<a href="/spine-pixi-v8/example/mouse-following.html">Mouse following</a>
</li>
<li>
<a href="/spine-pixi-v8/example/control-bones-example.html">Control bones example</a>
</li>
<li><a href="/spine-pixi-v8/example/physics.html">Physics</a></li>
<li><a href="/spine-pixi-v8/example/physics2.html">Physics II</a></li>
<li><a href="/spine-pixi-v8/example/physics3.html">Physics III</a></li>
<li><a href="/spine-pixi-v8/example/physics4.html">Physics IV</a></li>
<li><a href="/spine-pixi-v8/example/slot-objects.html">Slot Objects</a></li>
</ul>
<li>Phaser</li>
<ul>
<li>

View File

@ -15,6 +15,7 @@
"spine-player",
"spine-threejs",
"spine-pixi",
"spine-pixi-v8",
"spine-canvaskit",
"spine-webgl"
],
@ -77,6 +78,10 @@
"resolved": "spine-pixi",
"link": true
},
"node_modules/@esotericsoftware/spine-pixi-v8": {
"resolved": "spine-pixi-v8",
"link": true
},
"node_modules/@esotericsoftware/spine-player": {
"resolved": "spine-player",
"link": true
@ -290,6 +295,16 @@
"integrity": "sha512-pUrWq3V5PiSGFLeLxoGqReTZmiiXwY3jRkIG5sLLKjyqNxrwm/04b4nw7LSmGWJcKk59XOM/YRTUwOzo4MMlow==",
"license": "BSD-3-Clause"
},
"node_modules/@xmldom/xmldom": {
"version": "0.8.10",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
"integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/accepts": {
"version": "1.3.8",
"dev": true,
@ -2010,6 +2025,13 @@
"dev": true,
"license": "(MIT AND Zlib)"
},
"node_modules/parse-svg-path": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz",
"integrity": "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==",
"license": "MIT",
"peer": true
},
"node_modules/parseurl": {
"version": "1.3.3",
"dev": true,
@ -2057,6 +2079,31 @@
"eventemitter3": "^5.0.1"
}
},
"node_modules/pixi.js": {
"version": "8.5.2",
"resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.5.2.tgz",
"integrity": "sha512-TOt9g8ifOj4R9DN9ST1M8t2nvnuhr5oWL5YW9ywFLbnOVgFMDcEz+Xek5Mo8Xr64D+QU3qre3IFgreBlsHxTNw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@pixi/colord": "^2.9.6",
"@types/css-font-loading-module": "^0.0.12",
"@types/earcut": "^2.1.4",
"@webgpu/types": "^0.1.40",
"@xmldom/xmldom": "^0.8.10",
"earcut": "^2.2.4",
"eventemitter3": "^5.0.1",
"ismobilejs": "^1.1.1",
"parse-svg-path": "^0.1.2"
}
},
"node_modules/pixi.js/node_modules/@webgpu/types": {
"version": "0.1.51",
"resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.51.tgz",
"integrity": "sha512-ktR3u64NPjwIViNCck+z9QeyN0iPkQCUOQ07ZCV1RzlkfP+olLTeEZ95O1QHS+v4w9vJeY9xj/uJuSphsHy5rQ==",
"license": "BSD-3-Clause",
"peer": true
},
"node_modules/posix-character-classes": {
"version": "0.1.1",
"dev": true,
@ -3133,6 +3180,17 @@
"@pixi/text": "^7.2.4"
}
},
"spine-pixi-v8": {
"name": "@esotericsoftware/spine-pixi-v8",
"version": "4.2.62",
"license": "LicenseRef-LICENSE",
"dependencies": {
"@esotericsoftware/spine-core": "4.2.62"
},
"peerDependencies": {
"pixi.js": "^8.4.0"
}
},
"spine-player": {
"name": "@esotericsoftware/spine-player",
"version": "4.2.62",

View File

@ -8,8 +8,8 @@
],
"scripts": {
"prepublish": "npm run clean && npm run build",
"clean": "npx rimraf spine-core/dist spine-canvas/dist spine-canvaskit/dist spine-webgl/dist spine-phaser/dist spine-player/dist spine-threejs/dist spine-pixi/dist",
"build": "npm run clean && npm run build:modules && concurrently \"npm run build:core\" \"npm run build:canvas\" \"npm run build:canvaskit\" \"npm run build:webgl\" \"npm run build:phaser\" \"npm run build:player\" \"npm run build:threejs\" \"npm run build:pixi\"",
"clean": "npx rimraf spine-core/dist spine-canvas/dist spine-canvaskit/dist spine-webgl/dist spine-phaser/dist spine-player/dist spine-threejs/dist spine-pixi/dist spine-pixi-v8/dist",
"build": "npm run clean && npm run build:modules && concurrently \"npm run build:core\" \"npm run build:canvas\" \"npm run build:canvaskit\" \"npm run build:webgl\" \"npm run build:phaser\" \"npm run build:player\" \"npm run build:threejs\" \"npm run build:pixi\" \"npm run build:pixi-v8\"",
"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",
@ -20,8 +20,9 @@
"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",
"build:pixi": "npx esbuild --bundle spine-pixi/src/index.ts --tsconfig=spine-pixi/tsconfig.json --sourcemap --outfile=spine-pixi/dist/iife/spine-pixi.js --external:@pixi/* --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-canvaskit/dist/iife/spine-canvaskit.js --outfile=spine-canvaskit/dist/iife/spine-canvaskit.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 && npx esbuild --minify spine-pixi/dist/iife/spine-pixi.js --outfile=spine-pixi/dist/iife/spine-pixi.min.js",
"dev": "concurrently \"npx live-server\" \"npm run dev:canvas\" \"npm run dev:canvaskit\" \"npm run dev:webgl\" \"npm run dev:phaser\" \"npm run dev:player\" \"npm run dev:threejs\" \"npm run dev:pixi\" \"npm run dev:modules\"",
"build:pixi-v8": "npx esbuild --bundle spine-pixi-v8/src/index.ts --tsconfig=spine-pixi-v8/tsconfig.json --sourcemap --outfile=spine-pixi-v8/dist/iife/spine-pixi-v8.js --external:pixi.js --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-canvaskit/dist/iife/spine-canvaskit.js --outfile=spine-canvaskit/dist/iife/spine-canvaskit.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 && npx esbuild --minify spine-pixi/dist/iife/spine-pixi.js --outfile=spine-pixi/dist/iife/spine-pixi.min.js && npx esbuild --minify spine-pixi-v8/dist/iife/spine-pixi-v8.js --outfile=spine-pixi-v8/dist/iife/spine-pixi-v8.min.js",
"dev": "concurrently \"npx live-server\" \"npm run dev:canvas\" \"npm run dev:canvaskit\" \"npm run dev:webgl\" \"npm run dev:phaser\" \"npm run dev:player\" \"npm run dev:threejs\" \"npm run dev:pixi\" \"npm run dev:pixi-v8\" \"npm run dev:modules\"",
"dev:modules": "npm run build:modules -- --watch",
"dev:canvas": "npm run build:canvas -- --watch",
"dev:canvaskit": "npm run build:canvaskit -- --watch",
@ -29,7 +30,8 @@
"dev:phaser": "npm run build:phaser -- --watch",
"dev:player": "npm run build:player -- --watch",
"dev:threejs": "npm run build:threejs -- --watch",
"dev:pixi": "npm run build:pixi -- --watch"
"dev:pixi": "npm run build:pixi -- --watch",
"dev:pixi-v8": "npm run build:pixi-v8 -- --watch"
},
"repository": {
"type": "git",
@ -57,6 +59,7 @@
"spine-player",
"spine-threejs",
"spine-pixi",
"spine-pixi-v8",
"spine-canvaskit",
"spine-webgl"
],

View File

@ -17,6 +17,7 @@ sed -i '' "s/$currentVersion/$newVersion/" spine-canvaskit/package.json
sed -i '' "s/$currentVersion/$newVersion/" spine-core/package.json
sed -i '' "s/$currentVersion/$newVersion/" spine-phaser/package.json
sed -i '' "s/$currentVersion/$newVersion/" spine-pixi/package.json
sed -i '' "s/$currentVersion/$newVersion/" spine-pixi-v8/package.json
sed -i '' "s/$currentVersion/$newVersion/" spine-player/package.json
sed -i '' "s/$currentVersion/$newVersion/" spine-threejs/package.json
sed -i '' "s/$currentVersion/$newVersion/" spine-webgl/package.json

View File

@ -0,0 +1,3 @@
# spine-ts Pixi.js v8
Please see the top-level [README.md](../README.md) for more information.

View File

@ -0,0 +1,174 @@
celestial-circus-pma.png
size: 1024, 1024
filter: Linear, Linear
pma: true
scale: 0.4
arm-back-down
bounds: 324, 401, 38, 82
rotate: 90
arm-back-up
bounds: 290, 44, 83, 116
rotate: 90
arm-front-down
bounds: 706, 2, 36, 78
rotate: 90
arm-front-up
bounds: 860, 138, 77, 116
bench
bounds: 725, 256, 189, 48
body-bottom
bounds: 879, 868, 154, 124
rotate: 90
body-top
bounds: 725, 128, 126, 133
rotate: 90
chest
bounds: 408, 26, 104, 93
cloud-back
bounds: 752, 378, 202, 165
cloud-front
bounds: 2, 2, 325, 196
rotate: 90
collar
bounds: 786, 13, 47, 26
ear
bounds: 1002, 643, 20, 28
eye-back-shadow
bounds: 428, 395, 14, 10
eye-front-shadow
bounds: 704, 529, 24, 14
eye-reflex-back
bounds: 860, 128, 8, 7
rotate: 90
eye-reflex-front
bounds: 726, 386, 10, 7
eye-white-back
bounds: 835, 23, 13, 16
eye-white-front
bounds: 1005, 1000, 22, 17
rotate: 90
eyelashes-down-back
bounds: 232, 329, 11, 6
rotate: 90
eyelashes-down-front
bounds: 913, 851, 15, 6
rotate: 90
eyelashes-top-back
bounds: 408, 395, 18, 10
eyelashes-top-front
bounds: 702, 179, 30, 16
rotate: 90
face
bounds: 514, 26, 93, 102
rotate: 90
feathers-back
bounds: 954, 625, 46, 46
feathers-front
bounds: 706, 40, 72, 86
fringe-middle-back
bounds: 200, 6, 33, 52
rotate: 90
fringe-middle-front
bounds: 878, 76, 60, 50
rotate: 90
fringe-side-back
bounds: 780, 41, 27, 94
rotate: 90
fringe-side-front
bounds: 939, 161, 26, 93
glove-bottom-back
bounds: 954, 572, 51, 41
rotate: 90
glove-bottom-front
bounds: 916, 256, 47, 48
hair-back-1
bounds: 444, 395, 132, 306
rotate: 90
hair-back-2
bounds: 438, 211, 80, 285
rotate: 90
hair-back-3
bounds: 719, 306, 70, 268
rotate: 90
hair-back-4
bounds: 438, 121, 88, 262
rotate: 90
hair-back-5
bounds: 438, 293, 88, 279
rotate: 90
hair-back-6
bounds: 200, 41, 88, 286
hair-hat-shadow
bounds: 232, 398, 90, 41
hand-back
bounds: 954, 673, 60, 47
rotate: 90
hand-front
bounds: 967, 172, 53, 60
hat-back
bounds: 954, 802, 64, 45
rotate: 90
hat-front
bounds: 780, 70, 96, 56
head-back
bounds: 618, 17, 102, 86
rotate: 90
jabot
bounds: 967, 234, 70, 55
rotate: 90
leg-back
bounds: 232, 441, 210, 333
leg-front
bounds: 444, 529, 258, 320
logo-brooch
bounds: 954, 545, 16, 25
mouth
bounds: 408, 121, 22, 6
neck
bounds: 232, 342, 39, 56
rotate: 90
nose
bounds: 742, 529, 6, 7
rotate: 90
nose-highlight
bounds: 719, 300, 4, 4
nose-shadow
bounds: 869, 128, 7, 8
pupil-back
bounds: 730, 529, 10, 14
pupil-front
bounds: 254, 21, 12, 18
rope-back
bounds: 232, 383, 10, 492
rotate: 90
rope-front
bounds: 232, 383, 10, 492
rotate: 90
rope-front-bottom
bounds: 954, 735, 42, 65
skirt
bounds: 2, 776, 440, 246
sock-bow
bounds: 408, 407, 33, 32
spine-logo-body
bounds: 879, 853, 13, 32
rotate: 90
star-big
bounds: 939, 141, 18, 24
rotate: 90
star-medium
bounds: 742, 537, 6, 8
rotate: 90
star-small
bounds: 719, 378, 3, 4
rotate: 90
underskirt
bounds: 2, 329, 445, 228
rotate: 90
underskirt-back
bounds: 444, 851, 433, 171
wing-back
bounds: 290, 129, 146, 252
wing-front
bounds: 704, 545, 304, 248
rotate: 90

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 KiB

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,173 @@
celestial-circus.png
size: 1024, 1024
filter: Linear, Linear
scale: 0.4
arm-back-down
bounds: 324, 401, 38, 82
rotate: 90
arm-back-up
bounds: 290, 44, 83, 116
rotate: 90
arm-front-down
bounds: 706, 2, 36, 78
rotate: 90
arm-front-up
bounds: 860, 138, 77, 116
bench
bounds: 725, 256, 189, 48
body-bottom
bounds: 879, 868, 154, 124
rotate: 90
body-top
bounds: 725, 128, 126, 133
rotate: 90
chest
bounds: 408, 26, 104, 93
cloud-back
bounds: 752, 378, 202, 165
cloud-front
bounds: 2, 2, 325, 196
rotate: 90
collar
bounds: 786, 13, 47, 26
ear
bounds: 1002, 643, 20, 28
eye-back-shadow
bounds: 428, 395, 14, 10
eye-front-shadow
bounds: 704, 529, 24, 14
eye-reflex-back
bounds: 860, 128, 8, 7
rotate: 90
eye-reflex-front
bounds: 726, 386, 10, 7
eye-white-back
bounds: 835, 23, 13, 16
eye-white-front
bounds: 1005, 1000, 22, 17
rotate: 90
eyelashes-down-back
bounds: 232, 329, 11, 6
rotate: 90
eyelashes-down-front
bounds: 913, 851, 15, 6
rotate: 90
eyelashes-top-back
bounds: 408, 395, 18, 10
eyelashes-top-front
bounds: 702, 179, 30, 16
rotate: 90
face
bounds: 514, 26, 93, 102
rotate: 90
feathers-back
bounds: 954, 625, 46, 46
feathers-front
bounds: 706, 40, 72, 86
fringe-middle-back
bounds: 200, 6, 33, 52
rotate: 90
fringe-middle-front
bounds: 878, 76, 60, 50
rotate: 90
fringe-side-back
bounds: 780, 41, 27, 94
rotate: 90
fringe-side-front
bounds: 939, 161, 26, 93
glove-bottom-back
bounds: 954, 572, 51, 41
rotate: 90
glove-bottom-front
bounds: 916, 256, 47, 48
hair-back-1
bounds: 444, 395, 132, 306
rotate: 90
hair-back-2
bounds: 438, 211, 80, 285
rotate: 90
hair-back-3
bounds: 719, 306, 70, 268
rotate: 90
hair-back-4
bounds: 438, 121, 88, 262
rotate: 90
hair-back-5
bounds: 438, 293, 88, 279
rotate: 90
hair-back-6
bounds: 200, 41, 88, 286
hair-hat-shadow
bounds: 232, 398, 90, 41
hand-back
bounds: 954, 673, 60, 47
rotate: 90
hand-front
bounds: 967, 172, 53, 60
hat-back
bounds: 954, 802, 64, 45
rotate: 90
hat-front
bounds: 780, 70, 96, 56
head-back
bounds: 618, 17, 102, 86
rotate: 90
jabot
bounds: 967, 234, 70, 55
rotate: 90
leg-back
bounds: 232, 441, 210, 333
leg-front
bounds: 444, 529, 258, 320
logo-brooch
bounds: 954, 545, 16, 25
mouth
bounds: 408, 121, 22, 6
neck
bounds: 232, 342, 39, 56
rotate: 90
nose
bounds: 742, 529, 6, 7
rotate: 90
nose-highlight
bounds: 719, 300, 4, 4
nose-shadow
bounds: 869, 128, 7, 8
pupil-back
bounds: 730, 529, 10, 14
pupil-front
bounds: 254, 21, 12, 18
rope-back
bounds: 232, 383, 10, 492
rotate: 90
rope-front
bounds: 232, 383, 10, 492
rotate: 90
rope-front-bottom
bounds: 954, 735, 42, 65
skirt
bounds: 2, 776, 440, 246
sock-bow
bounds: 408, 407, 33, 32
spine-logo-body
bounds: 879, 853, 13, 32
rotate: 90
star-big
bounds: 939, 141, 18, 24
rotate: 90
star-medium
bounds: 742, 537, 6, 8
rotate: 90
star-small
bounds: 719, 378, 3, 4
rotate: 90
underskirt
bounds: 2, 329, 445, 228
rotate: 90
underskirt-back
bounds: 444, 851, 433, 171
wing-back
bounds: 290, 129, 146, 252
wing-front
bounds: 704, 545, 304, 248
rotate: 90

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 KiB

View File

@ -0,0 +1,86 @@
cloud-pot-pma.png
size: 1024, 512
filter: Linear, Linear
pma: true
scale: 0.5
cloud-base-1
bounds: 2, 300, 233, 210
cloud-base-10
bounds: 214, 113, 97, 101
cloud-base-2
bounds: 2, 90, 210, 208
cloud-base-3
bounds: 237, 346, 175, 164
cloud-base-4
bounds: 414, 347, 176, 163
cloud-base-5
bounds: 313, 89, 145, 125
cloud-base-6
bounds: 744, 374, 161, 136
cloud-base-7
bounds: 592, 361, 150, 149
cloud-base-8
bounds: 237, 216, 154, 128
cloud-base-9
bounds: 907, 402, 107, 108
cloud-cheeks
bounds: 2, 9, 218, 79
cloud-eyes-closed
bounds: 744, 350, 132, 22
cloud-eyes-open
bounds: 592, 333, 133, 26
cloud-eyes-reflex
bounds: 393, 224, 120, 17
rotate: 90
cloud-mouth-closed
bounds: 907, 374, 49, 16
cloud-mouth-open
bounds: 222, 15, 59, 35
leaf-big
bounds: 214, 218, 20, 49
leaf-small
bounds: 958, 373, 17, 30
rotate: 90
petal-1
bounds: 283, 2, 26, 18
petal-2
bounds: 283, 22, 28, 17
rotate: 90
petal-3
bounds: 214, 269, 29, 21
rotate: 90
pot-base
bounds: 222, 52, 76, 59
pot-eyes-closed
bounds: 878, 363, 46, 9
pot-eyes-open
bounds: 222, 2, 40, 11
pot-mouth-open
bounds: 990, 374, 14, 16
pot-mouth-pouty
bounds: 300, 93, 18, 10
rotate: 90
pot-mouth-smile
bounds: 300, 77, 14, 10
rotate: 90
pot-mouth-smile-big
bounds: 878, 352, 20, 9
rain-blue
bounds: 926, 360, 12, 18
rotate: 90
rain-color
bounds: 264, 4, 9, 17
rotate: 90
rain-green
bounds: 900, 349, 12, 18
rotate: 90
rain-white
bounds: 727, 337, 12, 22
rain-white-reflex
bounds: 2, 2, 5, 10
rotate: 90
stem
bounds: 907, 392, 8, 105
rotate: 90
stem-end
bounds: 300, 62, 13, 13

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

View File

@ -0,0 +1,842 @@
{
"skeleton": {
"hash": "CKnF82un6n8",
"spine": "4.2.22",
"x": -345,
"y": -272846.84,
"width": 756,
"height": 273927.84,
"images": "./images/",
"audio": ""
},
"bones": [
{ "name": "root" },
{ "name": "pot-control", "parent": "root", "x": 5, "y": 42, "color": "8828ffff", "icon": "arrowsB" },
{ "name": "cloud", "parent": "pot-control", "x": 26.5, "y": 772, "color": "1ee8c0ff", "icon": "circle" },
{ "name": "cloud-base-1", "parent": "cloud", "x": -4, "y": 57, "color": "b0d5eaff" },
{ "name": "cloud-base-2", "parent": "cloud-base-1", "x": 148.5, "y": -18.5, "color": "b0d5eaff" },
{ "name": "cloud-base-3", "parent": "cloud-base-1", "x": -182, "y": -87.5, "color": "b0d5eaff" },
{ "name": "cloud-base-4", "parent": "cloud", "x": -31.5, "y": -77, "color": "b0d5eaff" },
{ "name": "cloud-base-5", "parent": "cloud-base-4", "x": 177.5, "y": 8, "color": "b0d5eaff" },
{ "name": "cloud-base-6", "parent": "cloud-base-1", "x": -150.5, "y": 40, "color": "b0d5eaff" },
{ "name": "cloud-base-7", "parent": "cloud-base-1", "x": 8.5, "y": 36.5, "color": "b0d5eaff" },
{ "name": "cloud-base-8", "parent": "cloud-base-2", "x": 3.5, "y": 68.5, "color": "b0d5eaff" },
{ "name": "cloud-base-9", "parent": "cloud-base-3", "x": -83.5, "y": 30.5, "color": "b0d5eaff" },
{ "name": "cloud-base-10", "parent": "cloud-base-5", "x": 137, "y": 54.5, "color": "b0d5eaff" },
{ "name": "rain-blue", "parent": "cloud", "x": 102.49, "y": -26, "color": "2360e3ff", "icon": "diamond" },
{ "name": "rain-color", "parent": "cloud", "x": -39.42, "y": -26, "color": "2360e3ff", "icon": "diamond" },
{ "name": "rain-green", "parent": "cloud", "x": 35.08, "y": -26, "color": "2360e3ff", "icon": "diamond" },
{ "name": "rain-white", "parent": "cloud", "x": -103.92, "y": -26, "color": "2360e3ff", "icon": "diamond" },
{ "name": "pot", "parent": "pot-control", "x": -5, "y": -42, "color": "8828ffff" },
{ "name": "pot-face", "parent": "pot", "x": -1.06, "y": 28.16, "color": "f38383ff", "icon": "gear" },
{
"name": "leaf-big",
"parent": "pot",
"length": 46.73,
"rotation": 119.24,
"x": 4.04,
"y": 95.05,
"color": "abe323ff"
},
{ "name": "leaf-big-tip", "parent": "leaf-big", "length": 46.73, "x": 46.73, "color": "abe323ff" },
{
"name": "leaf-small",
"parent": "pot",
"length": 51.32,
"rotation": 50.93,
"x": 10.16,
"y": 96.81,
"color": "abe323ff"
},
{
"name": "stem",
"parent": "pot",
"length": 104.76,
"rotation": 90,
"x": 7.24,
"y": 92.61,
"color": "abe323ff"
},
{ "name": "stem2", "parent": "stem", "length": 69.84, "x": 104.76, "color": "abe323ff" },
{ "name": "stem3", "parent": "stem2", "length": 34.92, "x": 69.84, "color": "abe323ff" },
{
"name": "petal-3",
"parent": "stem3",
"length": 37.74,
"rotation": 1.03,
"x": 30.73,
"y": 0.64,
"color": "2381e3ff"
},
{
"name": "petal-1",
"parent": "stem3",
"length": 40.11,
"rotation": 70.18,
"x": 34.13,
"y": 3.02,
"color": "2381e3ff"
},
{
"name": "petal-2",
"parent": "stem3",
"length": 48.62,
"rotation": -80.34,
"x": 32.09,
"y": -4.46,
"color": "2381e3ff"
},
{ "name": "cloud-face", "parent": "cloud", "y": 14.93, "color": "9e82ffff", "icon": "arrowsB" }
],
"slots": [
{ "name": "rain/rain-green", "bone": "rain-green", "attachment": "rain-green" },
{ "name": "rain/rain-blue", "bone": "rain-blue", "attachment": "rain-blue" },
{ "name": "rain/rain-color", "bone": "rain-color", "attachment": "rain-color" },
{ "name": "rain/rain-white", "bone": "rain-white", "attachment": "rain-white" },
{ "name": "rain/rain-white-reflex", "bone": "rain-white", "attachment": "rain-white-reflex" },
{ "name": "flower/petal-1", "bone": "petal-1", "attachment": "petal-1" },
{ "name": "flower/petal-2", "bone": "petal-2", "attachment": "petal-2" },
{ "name": "flower/petal-3", "bone": "petal-3", "attachment": "petal-3" },
{ "name": "flower/stem", "bone": "stem", "attachment": "stem" },
{ "name": "flower/leaf-big", "bone": "leaf-big", "attachment": "leaf-big" },
{ "name": "flower/leaf-small", "bone": "leaf-small", "attachment": "leaf-small" },
{ "name": "flower/stem-end", "bone": "stem3", "attachment": "stem-end" },
{ "name": "pot/pot-base", "bone": "pot", "attachment": "pot-base" },
{ "name": "pot/pot-mouth", "bone": "pot-face", "attachment": "pot-mouth-smile-big" },
{ "name": "pot/pot-eyes", "bone": "pot-face", "attachment": "pot-eyes-open" },
{ "name": "cloud/cloud-base/cloud-base-1", "bone": "cloud-base-1", "attachment": "cloud-base-1" },
{ "name": "cloud/cloud-base/cloud-base-2", "bone": "cloud-base-2", "attachment": "cloud-base-2" },
{ "name": "cloud/cloud-base/cloud-base-3", "bone": "cloud-base-3", "attachment": "cloud-base-3" },
{ "name": "cloud/cloud-base/cloud-base-4", "bone": "cloud-base-4", "attachment": "cloud-base-4" },
{ "name": "cloud/cloud-base/cloud-base-5", "bone": "cloud-base-5", "attachment": "cloud-base-5" },
{ "name": "cloud/cloud-base/cloud-base-6", "bone": "cloud-base-6", "attachment": "cloud-base-6" },
{ "name": "cloud/cloud-base/cloud-base-7", "bone": "cloud-base-7", "attachment": "cloud-base-7" },
{ "name": "cloud/cloud-base/cloud-base-8", "bone": "cloud-base-8", "attachment": "cloud-base-8" },
{ "name": "cloud/cloud-base/cloud-base-9", "bone": "cloud-base-9", "attachment": "cloud-base-9" },
{ "name": "cloud/cloud-base/cloud-base-10", "bone": "cloud-base-10", "attachment": "cloud-base-10" },
{ "name": "cloud/cloud-cheeks", "bone": "cloud-face", "attachment": "cloud-cheeks" },
{ "name": "cloud/cloud-eyes", "bone": "cloud-face", "attachment": "cloud-eyes-open" },
{ "name": "cloud/cloud-eyes-reflex", "bone": "cloud-face", "attachment": "cloud-eyes-reflex" },
{ "name": "cloud/cloud-mouth", "bone": "cloud-face", "attachment": "cloud-mouth-closed" }
],
"physics": [
{
"name": "cloud",
"order": 25,
"bone": "cloud",
"x": 1,
"y": 1,
"inertia": 0.5,
"strength": 172.8,
"damping": 0.8571,
"mass": 3
},
{
"name": "cloud-face",
"order": 24,
"bone": "cloud-face",
"x": 0.1923,
"y": 0.141,
"limit": 500,
"inertia": 0.5,
"damping": 0.15
},
{
"name": "pot-face",
"order": 23,
"bone": "pot-face",
"x": 0.1667,
"y": 0.1026,
"limit": 500,
"inertia": 0.5,
"strength": 137.3,
"damping": 0.6078
},
{
"name": "cloud-base/cloud-base-1",
"order": 4,
"bone": "cloud-base-1",
"x": 1,
"y": 1,
"limit": 500,
"inertia": 0.3741,
"strength": 134.7,
"damping": 0.8163,
"mass": 2.8
},
{
"name": "cloud-base/cloud-base-2",
"order": 5,
"bone": "cloud-base-2",
"x": 1,
"y": 1,
"limit": 300,
"inertia": 0.3741,
"strength": 134.7,
"damping": 0.8163,
"mass": 2.8
},
{
"name": "cloud-base/cloud-base-3",
"order": 6,
"bone": "cloud-base-3",
"x": 1,
"y": 1,
"limit": 300,
"inertia": 0.3741,
"strength": 134.7,
"damping": 0.8163,
"mass": 2.8
},
{
"name": "cloud-base/cloud-base-4",
"order": 7,
"bone": "cloud-base-4",
"x": 1,
"y": 1,
"limit": 500,
"inertia": 0.3741,
"strength": 134.7,
"damping": 0.8163,
"mass": 2.8
},
{
"name": "cloud-base/cloud-base-5",
"order": 8,
"bone": "cloud-base-5",
"x": 1,
"y": 1,
"limit": 300,
"inertia": 0.3741,
"strength": 134.7,
"damping": 0.8163,
"mass": 2.8
},
{
"name": "cloud-base/cloud-base-6",
"order": 9,
"bone": "cloud-base-6",
"x": 1,
"y": 1,
"limit": 300,
"inertia": 0.3741,
"strength": 134.7,
"damping": 0.8163,
"mass": 2.8
},
{
"name": "cloud-base/cloud-base-7",
"order": 10,
"bone": "cloud-base-7",
"x": 1,
"y": 1,
"limit": 300,
"inertia": 0.3741,
"strength": 134.7,
"damping": 0.8163,
"mass": 2.8
},
{
"name": "cloud-base/cloud-base-8",
"order": 11,
"bone": "cloud-base-8",
"x": 1,
"y": 1,
"limit": 300,
"inertia": 0.3741,
"strength": 134.7,
"damping": 0.8163,
"mass": 2.8
},
{
"name": "cloud-base/cloud-base-9",
"order": 12,
"bone": "cloud-base-9",
"x": 1,
"y": 1,
"limit": 300,
"inertia": 0.3741,
"strength": 134.7,
"damping": 0.8163,
"mass": 2.8
},
{
"name": "cloud-base/cloud-base-10",
"order": 13,
"bone": "cloud-base-10",
"x": 1,
"y": 1,
"limit": 300,
"inertia": 0.3741,
"strength": 134.7,
"damping": 0.8163,
"mass": 2.8
},
{
"name": "plant/leaf-big",
"order": 14,
"bone": "leaf-big",
"rotate": 0.7532,
"shearX": 0.2468,
"limit": 500,
"inertia": 0.5,
"strength": 160.5,
"damping": 0.8367,
"mass": 4
},
{
"name": "plant/leaf-big-tip",
"order": 22,
"bone": "leaf-big-tip",
"rotate": 1,
"limit": 500,
"inertia": 0.5,
"strength": 160.5,
"damping": 0.8367,
"mass": 4
},
{
"name": "plant/leaf-small",
"order": 15,
"bone": "leaf-small",
"rotate": 0.6026,
"limit": 500,
"inertia": 0.5,
"strength": 160.5,
"damping": 0.8367,
"mass": 4
},
{
"name": "plant/petal-1",
"order": 19,
"bone": "petal-1",
"rotate": 1,
"limit": 500,
"inertia": 0.5,
"strength": 164.6,
"damping": 0.6531,
"mass": 2.6
},
{
"name": "plant/petal-2",
"order": 21,
"bone": "petal-2",
"rotate": 1,
"limit": 500,
"inertia": 0.5,
"strength": 164.6,
"damping": 0.6531,
"mass": 2.6
},
{
"name": "plant/petal-3",
"order": 20,
"bone": "petal-3",
"rotate": 1,
"limit": 500,
"inertia": 0.5,
"strength": 164.6,
"damping": 0.7823,
"mass": 3.83
},
{
"name": "plant/stem",
"order": 16,
"bone": "stem",
"rotate": 0.8205,
"limit": 700,
"inertia": 0.5,
"strength": 152.4,
"damping": 0.9388,
"mass": 2.6
},
{
"name": "plant/stem2",
"order": 17,
"bone": "stem2",
"rotate": 0.8205,
"limit": 700,
"inertia": 0.5,
"strength": 152.4,
"damping": 0.9388,
"mass": 2.6
},
{
"name": "plant/stem3",
"order": 18,
"bone": "stem3",
"rotate": 0.8205,
"limit": 700,
"inertia": 0.5,
"strength": 152.4,
"damping": 0.9388,
"mass": 2.6
},
{
"name": "rain/rain-blue",
"order": 3,
"bone": "rain-blue",
"x": 1,
"y": 1,
"strength": 0,
"gravity": 70
},
{
"name": "rain/rain-color",
"order": 2,
"bone": "rain-color",
"x": 1,
"y": 1,
"strength": 0,
"gravity": 70
},
{
"name": "rain/rain-green",
"order": 1,
"bone": "rain-green",
"x": 1,
"y": 1,
"strength": 0,
"gravity": 70
},
{ "name": "rain/rain-white", "bone": "rain-white", "x": 1, "y": 1, "strength": 0, "gravity": 70 }
],
"skins": [
{
"name": "default",
"attachments": {
"cloud/cloud-base/cloud-base-1": {
"cloud-base-1": { "width": 465, "height": 420 }
},
"cloud/cloud-base/cloud-base-2": {
"cloud-base-2": { "width": 420, "height": 415 }
},
"cloud/cloud-base/cloud-base-3": {
"cloud-base-3": { "width": 349, "height": 327 }
},
"cloud/cloud-base/cloud-base-4": {
"cloud-base-4": { "width": 352, "height": 326 }
},
"cloud/cloud-base/cloud-base-5": {
"cloud-base-5": { "width": 289, "height": 250 }
},
"cloud/cloud-base/cloud-base-6": {
"cloud-base-6": { "width": 322, "height": 272 }
},
"cloud/cloud-base/cloud-base-7": {
"cloud-base-7": { "width": 300, "height": 297 }
},
"cloud/cloud-base/cloud-base-8": {
"cloud-base-8": { "width": 307, "height": 256 }
},
"cloud/cloud-base/cloud-base-9": {
"cloud-base-9": { "width": 214, "height": 216 }
},
"cloud/cloud-base/cloud-base-10": {
"cloud-base-10": { "width": 193, "height": 201 }
},
"cloud/cloud-cheeks": {
"cloud-cheeks": { "x": -19, "y": -53.93, "width": 435, "height": 158 }
},
"cloud/cloud-eyes": {
"cloud-eyes-closed": { "x": -10, "y": -5.43, "width": 263, "height": 43 },
"cloud-eyes-open": { "x": -8, "y": -4.43, "width": 265, "height": 51 }
},
"cloud/cloud-eyes-reflex": {
"cloud-eyes-reflex": { "x": -10, "y": 2.07, "width": 239, "height": 34 }
},
"cloud/cloud-mouth": {
"cloud-mouth-closed": { "y": -14.93, "width": 97, "height": 32 },
"cloud-mouth-open": { "x": -0.5, "y": -27.93, "width": 118, "height": 70 }
},
"flower/leaf-big": {
"leaf-big": {
"type": "mesh",
"uvs": [ 1, 1, 0, 1, 0, 0.75, 0, 0.5, 0, 0.25, 0, 0, 1, 0, 1, 0.25, 1, 0.5, 1, 0.75 ],
"triangles": [ 8, 3, 7, 3, 4, 7, 7, 4, 6, 4, 5, 6, 0, 1, 9, 1, 2, 9, 9, 2, 8, 2, 3, 8 ],
"vertices": [ 1, 19, -5.05, -21.72, 1, 1, 19, -5.05, 18.28, 1, 2, 19, 19.45, 18.28, 0.75483, 20, -27.28, 18.28, 0.24517, 2, 19, 43.95, 18.28, 0.50538, 20, -2.78, 18.28, 0.49462, 2, 19, 68.45, 18.28, 0.25278, 20, 21.72, 18.28, 0.74722, 1, 20, 46.22, 18.28, 1, 1, 20, 46.22, -21.72, 1, 2, 19, 68.45, -21.72, 0.24458, 20, 21.72, -21.72, 0.75542, 2, 19, 43.95, -21.72, 0.4937, 20, -2.78, -21.72, 0.5063, 2, 19, 19.45, -21.72, 0.74651, 20, -27.28, -21.72, 0.25349 ],
"hull": 10,
"edges": [ 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14, 16, 16, 18, 18, 0 ],
"width": 40,
"height": 98
}
},
"flower/leaf-small": {
"leaf-small": { "x": 25.02, "y": 0.4, "rotation": -91.36, "width": 34, "height": 59 }
},
"flower/petal-1": {
"petal-1": { "x": 18.88, "y": -4.54, "rotation": -160.18, "width": 52, "height": 36 }
},
"flower/petal-2": {
"petal-2": { "x": 21.96, "y": 2.06, "rotation": -9.66, "width": 56, "height": 34 }
},
"flower/petal-3": {
"petal-3": { "x": 16.97, "y": -5.71, "rotation": -91.03, "width": 58, "height": 42 }
},
"flower/stem": {
"stem": {
"type": "mesh",
"uvs": [ 1, 1, 0, 1, 0, 0.90909, 0, 0.81818, 0, 0.72727, 0, 0.63636, 0, 0.54545, 0, 0.45455, 0, 0.36364, 0, 0.27273, 0, 0.18182, 0, 0.09091, 0, 0, 1, 0, 1, 0.09091, 1, 0.18182, 1, 0.27273, 1, 0.36364, 1, 0.45455, 1, 0.54545, 1, 0.63636, 1, 0.72727, 1, 0.81818, 1, 0.90909 ],
"triangles": [ 15, 10, 14, 10, 11, 14, 14, 11, 13, 11, 12, 13, 18, 7, 17, 7, 8, 17, 17, 8, 16, 8, 9, 16, 16, 9, 15, 9, 10, 15, 0, 1, 23, 1, 2, 23, 23, 2, 22, 2, 3, 22, 22, 3, 21, 3, 4, 21, 21, 4, 20, 4, 5, 20, 20, 5, 19, 5, 6, 19, 19, 6, 18, 6, 7, 18 ],
"vertices": [ 1, 22, -3.61, -6.76, 1, 1, 22, -3.61, 9.24, 1, 3, 22, 15.49, 9.24, 0.97258, 23, -89.27, 9.24, 0.02734, 24, -159.11, 9.24, 8.0E-5, 3, 22, 34.58, 9.24, 0.92758, 23, -70.18, 9.24, 0.07175, 24, -140.02, 9.24, 6.7E-4, 3, 22, 53.67, 9.24, 0.851, 23, -51.09, 9.24, 0.14565, 24, -120.93, 9.24, 0.00335, 3, 22, 72.76, 9.24, 0.73702, 23, -32, 9.24, 0.25075, 24, -101.84, 9.24, 0.01223, 3, 22, 91.85, 9.24, 0.59184, 23, -12.91, 9.24, 0.37282, 24, -82.74, 9.24, 0.03534, 3, 22, 110.94, 9.24, 0.43333, 23, 6.18, 9.24, 0.482, 24, -63.65, 9.24, 0.08467, 3, 22, 130.03, 9.24, 0.28467, 23, 25.27, 9.24, 0.54153, 24, -44.56, 9.24, 0.1738, 3, 22, 149.12, 9.24, 0.16502, 23, 44.37, 9.24, 0.52188, 24, -25.47, 9.24, 0.3131, 3, 22, 168.21, 9.24, 0.08234, 23, 63.46, 9.24, 0.4129, 24, -6.38, 9.24, 0.50477, 3, 22, 187.3, 9.24, 0.03198, 23, 82.55, 9.24, 0.228, 24, 12.71, 9.24, 0.74001, 1, 24, 31.8, 9.24, 1, 1, 24, 31.8, -6.76, 1, 3, 22, 187.3, -6.76, 0.02989, 23, 82.55, -6.76, 0.23389, 24, 12.71, -6.76, 0.73622, 3, 22, 168.21, -6.76, 0.07799, 23, 63.46, -6.76, 0.42357, 24, -6.38, -6.76, 0.49844, 3, 22, 149.12, -6.76, 0.1584, 23, 44.37, -6.76, 0.53549, 24, -25.47, -6.76, 0.30611, 3, 22, 130.03, -6.76, 0.27629, 23, 25.27, -6.76, 0.55594, 24, -44.56, -6.76, 0.16777, 3, 22, 110.94, -6.76, 0.42428, 23, 6.18, -6.76, 0.49529, 24, -63.65, -6.76, 0.08044, 3, 22, 91.85, -6.76, 0.58346, 23, -12.91, -6.76, 0.38366, 24, -82.74, -6.76, 0.03289, 3, 22, 72.76, -6.76, 0.73038, 23, -32, -6.76, 0.25856, 24, -101.84, -6.76, 0.01107, 3, 22, 53.67, -6.76, 0.84652, 23, -51.09, -6.76, 0.15057, 24, -120.93, -6.76, 0.00291, 3, 22, 34.58, -6.76, 0.92506, 23, -70.18, -6.76, 0.0744, 24, -140.02, -6.76, 5.4E-4, 3, 22, 15.49, -6.76, 0.97151, 23, -89.27, -6.76, 0.02843, 24, -159.11, -6.76, 6.0E-5 ],
"hull": 24,
"edges": [ 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14, 16, 16, 18, 18, 20, 20, 22, 22, 24, 24, 26, 26, 28, 28, 30, 30, 32, 32, 34, 34, 36, 36, 38, 38, 40, 40, 42, 42, 44, 44, 46, 46, 0 ],
"width": 16,
"height": 210
}
},
"flower/stem-end": {
"stem-end": { "x": 25.8, "y": -0.26, "rotation": -90, "width": 25, "height": 26 }
},
"pot/pot-base": {
"pot-base": { "x": 5, "y": 42, "width": 152, "height": 118 }
},
"pot/pot-eyes": {
"pot-eyes-closed": { "x": -0.94, "y": 2.34, "width": 92, "height": 17 },
"pot-eyes-open": { "x": 0.06, "y": 3.84, "width": 80, "height": 22 }
},
"pot/pot-mouth": {
"pot-mouth-open": { "x": -1.44, "y": -13.66, "width": 27, "height": 31 },
"pot-mouth-pouty": { "x": 0.56, "y": -12.66, "width": 35, "height": 19 },
"pot-mouth-smile": { "x": 0.56, "y": -12.16, "width": 27, "height": 20 },
"pot-mouth-smile-big": { "x": 1.56, "y": -9.16, "width": 39, "height": 18 }
},
"rain/rain-blue": {
"rain-blue": { "width": 23, "height": 36 }
},
"rain/rain-color": {
"rain-color": { "width": 18, "height": 34 }
},
"rain/rain-green": {
"rain-green": { "width": 23, "height": 36 }
},
"rain/rain-white": {
"rain-white": { "width": 23, "height": 44 }
},
"rain/rain-white-reflex": {
"rain-white-reflex": { "x": -0.5, "y": 3.5, "width": 10, "height": 19 }
}
}
}
],
"animations": {
"playing-in-the-rain": {
"slots": {
"cloud/cloud-eyes": {
"attachment": [
{ "time": 0.2, "name": "cloud-eyes-closed" },
{ "time": 0.9, "name": "cloud-eyes-open" },
{ "time": 1.7667, "name": "cloud-eyes-closed" },
{ "time": 1.9333, "name": "cloud-eyes-open" },
{ "time": 2.4333, "name": "cloud-eyes-closed" },
{ "time": 2.6, "name": "cloud-eyes-open" },
{ "time": 3.9333, "name": "cloud-eyes-closed" },
{ "time": 4.1, "name": "cloud-eyes-open" }
]
},
"cloud/cloud-mouth": {
"attachment": [
{ "time": 0.2, "name": "cloud-mouth-open" },
{ "time": 0.9, "name": "cloud-mouth-closed" }
]
},
"pot/pot-eyes": {
"attachment": [
{ "time": 0.1333, "name": "pot-eyes-closed" },
{ "time": 0.3, "name": "pot-eyes-open" },
{ "time": 1.0667, "name": "pot-eyes-closed" },
{ "time": 1.5, "name": "pot-eyes-open" },
{ "time": 3.0333, "name": "pot-eyes-closed" },
{ "time": 3.2333, "name": "pot-eyes-open" },
{ "time": 3.4667, "name": "pot-eyes-closed" },
{ "time": 3.6667, "name": "pot-eyes-open" }
]
},
"pot/pot-mouth": {
"attachment": [
{ "time": 0.1333, "name": "pot-mouth-open" },
{ "time": 0.3, "name": "pot-mouth-smile-big" },
{ "time": 1.0667, "name": "pot-mouth-pouty" },
{ "time": 2.4, "name": "pot-mouth-smile" },
{ "time": 3.0333, "name": "pot-mouth-smile-big" }
]
}
},
"bones": {
"pot": {
"rotate": [
{ "time": 1.1 },
{ "time": 1.2, "value": -12.76 },
{ "time": 1.5333, "curve": "stepped" },
{ "time": 3.7667 },
{ "time": 3.9, "value": 8.28 },
{ "time": 4.2333, "value": -4.34 },
{ "time": 4.4333 }
],
"scale": [
{},
{ "time": 0.2, "y": 0.752 },
{ "time": 0.4, "x": 0.845, "y": 1.068 },
{ "time": 0.6333 }
]
},
"pot-control": {
"translatex": [
{
"time": 1.0667,
"curve": [ 1.222, -203.48, 1.378, -610.44 ]
},
{ "time": 1.5333, "value": -610.44, "curve": "stepped" },
{
"time": 2.2333,
"value": -610.44,
"curve": [ 2.389, -610.44, 2.544, -478.45 ]
},
{ "time": 2.7, "value": -478.45, "curve": "stepped" },
{
"time": 3.8333,
"value": -478.45,
"curve": [ 3.971, -478.45, 4.095, -135.56 ]
},
{ "time": 4.2333 }
],
"translatey": [
{
"time": 1.0333,
"curve": [ 1.089, 10.56, 1.144, 44.34 ]
},
{
"time": 1.2,
"value": 44.34,
"curve": [ 1.256, 44.34, 1.311, 0 ]
},
{ "time": 1.3667, "curve": "stepped" },
{
"time": 2.2333,
"curve": [ 2.408, 0, 2.392, 44.34 ]
},
{
"time": 2.4333,
"value": 44.34,
"curve": [ 2.455, 44.34, 2.51, 0 ]
},
{ "time": 2.6, "curve": "stepped" },
{
"time": 3.8,
"curve": [ 3.841, 14.78, 3.893, 44.34 ]
},
{
"time": 3.9333,
"value": 44.34,
"curve": [ 4.023, 44.34, 4.111, 14.78 ]
},
{ "time": 4.2 }
]
},
"cloud-base-1": {
"rotate": [
{
"curve": [ 0.144, -9.36, 0.289, -17.29 ]
},
{
"time": 0.4333,
"value": -17.29,
"curve": [ 0.5, -17.29, 0.567, -4.32 ]
},
{ "time": 0.6333 }
],
"scale": [
{
"curve": [ 0.089, 1, 0.178, 1.064, 0.089, 1, 0.178, 1.064 ]
},
{
"time": 0.2667,
"x": 1.064,
"y": 1.064,
"curve": [ 0.411, 1.064, 0.556, 1.021, 0.411, 1.064, 0.556, 1.021 ]
},
{ "time": 0.7 }
]
},
"cloud-base-4": {
"rotate": [
{
"curve": [ 0.1, 5.55, 0.2, 14.81 ]
},
{
"time": 0.3,
"value": 14.81,
"curve": [ 0.467, 14.81, 0.633, 9.25 ]
},
{ "time": 0.8 }
],
"scale": [
{
"curve": [ 0.089, 1, 0.178, 1.064, 0.089, 1, 0.178, 1.064 ]
},
{
"time": 0.2667,
"x": 1.064,
"y": 1.064,
"curve": [ 0.411, 1.064, 0.556, 1.021, 0.411, 1.064, 0.556, 1.021 ]
},
{ "time": 0.7 }
]
},
"cloud": {
"translate": [
{ "time": 0.2333 },
{ "time": 0.3333, "y": 30.43 },
{ "time": 0.4667 },
{ "time": 0.5667, "y": 30.43 },
{ "time": 0.6667 },
{ "time": 0.7667, "y": 30.43 },
{ "time": 0.9333 }
]
}
},
"physics": {
"rain/rain-blue": {
"reset": [
{ "time": 0.4667 },
{ "time": 0.9333 },
{ "time": 1.4 },
{ "time": 1.8667 },
{ "time": 2.3333 },
{ "time": 2.8 },
{ "time": 3.2667 },
{ "time": 3.7333 },
{ "time": 4.2 },
{ "time": 4.6667 }
]
},
"rain/rain-color": {
"reset": [
{ "time": 0.3 },
{ "time": 0.7667 },
{ "time": 1.2333 },
{ "time": 1.7 },
{ "time": 2.1667 },
{ "time": 2.6333 },
{ "time": 3.1 },
{ "time": 3.5667 },
{ "time": 4.0333 },
{ "time": 4.5 }
]
},
"rain/rain-green": {
"reset": [
{ "time": 0.1333 },
{ "time": 0.6 },
{ "time": 1.0667 },
{ "time": 1.5333 },
{ "time": 2 },
{ "time": 2.4667 },
{ "time": 2.9333 },
{ "time": 3.4 },
{ "time": 3.8667 },
{ "time": 4.3333 }
]
},
"rain/rain-white": {
"reset": [
{},
{ "time": 0.4667 },
{ "time": 0.9333 },
{ "time": 1.4 },
{ "time": 1.8667 },
{ "time": 2.3333 },
{ "time": 2.8 },
{ "time": 3.2667 },
{ "time": 3.7333 },
{ "time": 4.2 }
]
}
}
},
"pot-moving-followed-by-rain": {
"bones": {
"pot-control": {
"translate": [
{},
{ "time": 0.5667, "x": -389.34, "curve": "stepped" },
{ "time": 1.1667, "x": -389.34 },
{ "time": 2.2, "x": 463.88, "curve": "stepped" },
{ "time": 2.4667, "x": 463.88 },
{ "time": 3 }
]
}
},
"physics": {
"rain/rain-blue": {
"reset": [
{ "time": 0.4667 },
{ "time": 0.9333 },
{ "time": 1.4 },
{ "time": 1.8667 },
{ "time": 2.3333 },
{ "time": 2.8 },
{ "time": 3.2667 }
]
},
"rain/rain-color": {
"reset": [
{ "time": 0.3 },
{ "time": 0.7667 },
{ "time": 1.2333 },
{ "time": 1.7 },
{ "time": 2.1667 },
{ "time": 2.6333 },
{ "time": 3.1 }
]
},
"rain/rain-green": {
"reset": [
{ "time": 0.1333 },
{ "time": 0.6 },
{ "time": 1.0667 },
{ "time": 1.5333 },
{ "time": 2 },
{ "time": 2.4667 },
{ "time": 2.9333 }
]
},
"rain/rain-white": {
"reset": [
{},
{ "time": 0.4667 },
{ "time": 0.9333 },
{ "time": 1.4 },
{ "time": 1.8667 },
{ "time": 2.3333 },
{ "time": 2.8 }
]
}
}
},
"rain": {
"physics": {
"rain/rain-blue": {
"reset": [
{ "time": 0.4667 }
]
},
"rain/rain-color": {
"reset": [
{ "time": 0.3 }
]
},
"rain/rain-green": {
"reset": [
{ "time": 0.1333 }
]
},
"rain/rain-white": {
"reset": [
{}
]
}
}
}
}
}

Binary file not shown.

View File

@ -0,0 +1,19 @@
coin-pma.png
size: 1024, 1024
filter: Linear, Linear
pma: true
coin-front-logo
bounds: 2, 609, 305, 302
coin-front-shine-logo
bounds: 309, 629, 282, 282
coin-front-shine-spineboy
bounds: 2, 21, 282, 282
coin-front-spineboy
bounds: 2, 305, 305, 302
coin-side-round
bounds: 309, 345, 144, 282
coin-side-straight
bounds: 2, 2, 17, 282
rotate: 90
shine
bounds: 593, 666, 72, 245

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

View File

@ -0,0 +1,25 @@
coin-pro.png
size:1399,302
filter:Linear,Linear
pma:true
coin-front-logo
bounds:319,2,298,298
offsets:2,2,305,302
coin-front-shine-logo
bounds:901,20,280,280
offsets:1,1,282,282
coin-front-shine-spineboy
bounds:619,20,280,280
offsets:1,1,282,282
coin-front-spineboy
bounds:19,2,298,298
offsets:2,2,305,302
coin-side-round
bounds:1183,20,142,280
offsets:1,1,144,282
coin-side-straight
bounds:2,20,15,280
offsets:1,1,17,282
shine
bounds:1327,57,70,243
offsets:1,1,72,245

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,117 @@
dragon-pma.png
size: 1024, 1024
filter: Linear, Linear
pma: true
front-toe-a
bounds: 797, 381, 29, 50
front-toe-b
bounds: 942, 118, 56, 57
head
bounds: 647, 81, 296, 260
rotate: 90
left-front-leg
bounds: 942, 250, 84, 57
rotate: 90
left-front-thigh
bounds: 852, 7, 84, 72
left-wing01
bounds: 736, 433, 264, 589
right-rear-toe
bounds: 647, 2, 109, 77
right-wing01
bounds: 2, 379, 365, 643
right-wing02
bounds: 369, 379, 365, 643
right-wing03
bounds: 2, 12, 365, 643
rotate: 90
tail03
bounds: 758, 6, 73, 92
rotate: 90
tail04
bounds: 942, 177, 56, 71
tail05
bounds: 736, 379, 52, 59
rotate: 90
tail06
bounds: 942, 336, 95, 68
rotate: 90
thiagobrayner
bounds: 909, 81, 350, 31
rotate: 90
dragon-pma_2.png
size: 1024, 1024
filter: Linear, Linear
pma: true
back
bounds: 795, 32, 190, 185
chin
bounds: 647, 157, 214, 146
rotate: 90
left-rear-leg
bounds: 795, 219, 206, 177
rotate: 90
left-wing02
bounds: 736, 427, 264, 589
right-wing04
bounds: 2, 373, 365, 643
right-wing05
bounds: 369, 373, 365, 643
right-wing06
bounds: 2, 6, 365, 643
rotate: 90
tail01
bounds: 647, 2, 120, 153
dragon-pma_3.png
size: 1024, 1024
filter: Linear, Linear
pma: true
chest
bounds: 740, 299, 136, 122
left-rear-thigh
bounds: 647, 218, 91, 149
left-wing03
bounds: 736, 423, 264, 589
right-front-leg
bounds: 850, 196, 101, 89
rotate: 90
right-front-thigh
bounds: 740, 189, 108, 108
right-rear-leg
bounds: 878, 321, 116, 100
right-rear-thigh
bounds: 647, 67, 91, 149
right-wing07
bounds: 2, 369, 365, 643
right-wing08
bounds: 369, 369, 365, 643
right-wing09
bounds: 2, 2, 365, 643
rotate: 90
tail02
bounds: 740, 67, 95, 120
dragon-pma_4.png
size: 1024, 1024
filter: Linear, Linear
pma: true
left-wing04
bounds: 2, 268, 264, 589
left-wing05
bounds: 268, 268, 264, 589
left-wing06
bounds: 534, 268, 264, 589
left-wing07
bounds: 2, 2, 264, 589
rotate: 90
dragon-pma_5.png
size: 1024, 1024
filter: Linear, Linear
pma: true
left-wing08
bounds: 2, 2, 264, 589
left-wing09
bounds: 268, 2, 264, 589

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,359 @@
mix-and-match-pma.png
size: 1024, 512
filter: Linear, Linear
pma: true
scale: 0.5
base-head
bounds: 118, 70, 95, 73
boy/arm-front
bounds: 831, 311, 36, 115
rotate: 90
boy/backpack
bounds: 249, 357, 119, 153
boy/backpack-pocket
bounds: 628, 193, 34, 62
rotate: 90
boy/backpack-strap-front
bounds: 330, 263, 38, 88
rotate: 90
boy/backpack-up
bounds: 482, 171, 21, 70
boy/body
bounds: 845, 413, 97, 132
rotate: 90
boy/boot-ribbon-front
bounds: 234, 304, 9, 11
boy/collar
bounds: 471, 243, 73, 29
rotate: 90
boy/ear
bounds: 991, 352, 19, 23
rotate: 90
boy/eye-back-low-eyelid
bounds: 66, 72, 17, 6
boy/eye-back-pupil
bounds: 694, 279, 8, 9
rotate: 90
boy/eye-back-up-eyelid
bounds: 460, 101, 23, 5
rotate: 90
boy/eye-back-up-eyelid-back
bounds: 979, 414, 19, 10
rotate: 90
boy/eye-front-low-eyelid
bounds: 1015, 203, 22, 7
rotate: 90
boy/eye-front-pupil
bounds: 309, 50, 9, 9
boy/eye-front-up-eyelid
bounds: 991, 373, 31, 6
boy/eye-front-up-eyelid-back
bounds: 107, 76, 26, 9
rotate: 90
boy/eye-iris-back
bounds: 810, 260, 17, 17
boy/eye-iris-front
bounds: 902, 230, 18, 18
boy/eye-white-back
bounds: 599, 179, 20, 12
boy/eye-white-front
bounds: 544, 183, 27, 13
boy/eyebrow-back
bounds: 1002, 225, 20, 11
rotate: 90
boy/eyebrow-front
bounds: 975, 234, 25, 11
boy/hair-back
bounds: 629, 289, 122, 81
rotate: 90
boy/hair-bangs
bounds: 505, 180, 70, 37
rotate: 90
boy/hair-side
bounds: 979, 435, 25, 43
rotate: 90
boy/hand-backfingers
bounds: 858, 183, 19, 21
boy/hand-front-fingers
bounds: 879, 183, 19, 21
boy/hat
bounds: 218, 121, 93, 56
boy/leg-front
bounds: 85, 104, 31, 158
boy/mouth-close
bounds: 467, 100, 21, 5
girl-blue-cape/mouth-close
bounds: 467, 100, 21, 5
girl-spring-dress/mouth-close
bounds: 467, 100, 21, 5
girl/mouth-close
bounds: 467, 100, 21, 5
boy/mouth-smile
bounds: 1015, 258, 29, 7
rotate: 90
boy/nose
bounds: 323, 79, 17, 10
boy/pompom
bounds: 979, 462, 48, 43
rotate: 90
boy/zip
bounds: 922, 231, 14, 23
rotate: 90
girl-blue-cape/back-eyebrow
bounds: 527, 106, 18, 12
rotate: 90
girl-blue-cape/body-dress
bounds: 2, 264, 109, 246
girl-blue-cape/body-ribbon
bounds: 576, 193, 50, 38
girl-blue-cape/cape-back
bounds: 113, 317, 134, 193
girl-blue-cape/cape-back-up
bounds: 504, 305, 123, 106
girl-blue-cape/cape-ribbon
bounds: 396, 118, 50, 18
rotate: 90
girl-blue-cape/cape-shoulder-back
bounds: 420, 243, 49, 59
girl-blue-cape/cape-shoulder-front
bounds: 2, 2, 62, 76
girl-blue-cape/cape-up-front
bounds: 118, 145, 98, 117
girl-blue-cape/ear
bounds: 837, 181, 19, 23
girl-spring-dress/ear
bounds: 837, 181, 19, 23
girl/ear
bounds: 837, 181, 19, 23
girl-blue-cape/eye-back-low-eyelid
bounds: 810, 252, 17, 6
girl-spring-dress/eye-back-low-eyelid
bounds: 810, 252, 17, 6
girl/eye-back-low-eyelid
bounds: 810, 252, 17, 6
girl-blue-cape/eye-back-pupil
bounds: 309, 40, 8, 9
rotate: 90
girl-spring-dress/eye-back-pupil
bounds: 309, 40, 8, 9
rotate: 90
girl/eye-back-pupil
bounds: 309, 40, 8, 9
rotate: 90
girl-blue-cape/eye-back-up-eyelid
bounds: 573, 179, 24, 12
girl-spring-dress/eye-back-up-eyelid
bounds: 573, 179, 24, 12
girl/eye-back-up-eyelid
bounds: 573, 179, 24, 12
girl-blue-cape/eye-back-up-eyelid-back
bounds: 380, 105, 17, 11
rotate: 90
girl-spring-dress/eye-back-up-eyelid-back
bounds: 380, 105, 17, 11
rotate: 90
girl/eye-back-up-eyelid-back
bounds: 380, 105, 17, 11
rotate: 90
girl-blue-cape/eye-front-low-eyelid
bounds: 1016, 353, 18, 6
rotate: 90
girl-spring-dress/eye-front-low-eyelid
bounds: 1016, 353, 18, 6
rotate: 90
girl/eye-front-low-eyelid
bounds: 1016, 353, 18, 6
rotate: 90
girl-blue-cape/eye-front-pupil
bounds: 363, 94, 9, 9
girl-spring-dress/eye-front-pupil
bounds: 363, 94, 9, 9
girl/eye-front-pupil
bounds: 363, 94, 9, 9
girl-blue-cape/eye-front-up-eyelid
bounds: 679, 413, 30, 14
rotate: 90
girl-spring-dress/eye-front-up-eyelid
bounds: 679, 413, 30, 14
rotate: 90
girl/eye-front-up-eyelid
bounds: 679, 413, 30, 14
rotate: 90
girl-blue-cape/eye-front-up-eyelid-back
bounds: 947, 234, 26, 11
girl-spring-dress/eye-front-up-eyelid-back
bounds: 947, 234, 26, 11
girl/eye-front-up-eyelid-back
bounds: 947, 234, 26, 11
girl-blue-cape/eye-iris-back
bounds: 323, 105, 17, 17
girl-blue-cape/eye-iris-front
bounds: 467, 107, 18, 18
girl-blue-cape/eye-white-back
bounds: 621, 175, 20, 16
girl-spring-dress/eye-white-back
bounds: 621, 175, 20, 16
girl-blue-cape/eye-white-front
bounds: 643, 175, 20, 16
girl-spring-dress/eye-white-front
bounds: 643, 175, 20, 16
girl/eye-white-front
bounds: 643, 175, 20, 16
girl-blue-cape/front-eyebrow
bounds: 309, 101, 18, 12
rotate: 90
girl-blue-cape/hair-back
bounds: 712, 317, 117, 98
girl-blue-cape/hair-bangs
bounds: 313, 170, 91, 40
rotate: 90
girl-blue-cape/hair-head-side-back
bounds: 544, 198, 30, 52
girl-blue-cape/hair-head-side-front
bounds: 466, 127, 41, 42
girl-blue-cape/hair-side
bounds: 175, 2, 36, 71
rotate: 90
girl-blue-cape/hand-front-fingers
bounds: 902, 207, 19, 21
girl-spring-dress/hand-front-fingers
bounds: 902, 207, 19, 21
girl-blue-cape/leg-front
bounds: 519, 413, 30, 158
rotate: 90
girl-blue-cape/mouth-smile
bounds: 1015, 227, 29, 7
rotate: 90
girl-spring-dress/mouth-smile
bounds: 1015, 227, 29, 7
rotate: 90
girl/mouth-smile
bounds: 1015, 227, 29, 7
rotate: 90
girl-blue-cape/nose
bounds: 342, 82, 11, 7
girl-spring-dress/nose
bounds: 342, 82, 11, 7
girl/nose
bounds: 342, 82, 11, 7
girl-blue-cape/sleeve-back
bounds: 416, 95, 42, 29
girl-blue-cape/sleeve-front
bounds: 249, 303, 52, 119
rotate: 90
girl-spring-dress/arm-front
bounds: 829, 292, 17, 111
rotate: 90
girl-spring-dress/back-eyebrow
bounds: 309, 81, 18, 12
rotate: 90
girl-spring-dress/body-up
bounds: 66, 2, 64, 66
girl-spring-dress/cloak-down
bounds: 758, 227, 50, 50
girl-spring-dress/cloak-up
bounds: 628, 229, 64, 58
girl-spring-dress/eye-iris-back
bounds: 342, 105, 17, 17
girl-spring-dress/eye-iris-front
bounds: 487, 107, 18, 18
girl-spring-dress/front-eyebrow
bounds: 323, 91, 18, 12
girl-spring-dress/hair-back
bounds: 370, 417, 147, 93
girl-spring-dress/hair-bangs
bounds: 829, 250, 91, 40
girl-spring-dress/hair-head-side-back
bounds: 509, 126, 30, 52
girl-spring-dress/hair-head-side-front
bounds: 816, 206, 41, 42
girl-spring-dress/hair-side
bounds: 248, 2, 36, 71
rotate: 90
girl-spring-dress/leg-front
bounds: 831, 381, 30, 158
rotate: 90
girl-spring-dress/neck
bounds: 85, 70, 20, 32
girl-spring-dress/shoulder-ribbon
bounds: 175, 44, 36, 24
girl-spring-dress/skirt
bounds: 2, 80, 182, 81
rotate: 90
girl-spring-dress/underskirt
bounds: 519, 445, 175, 65
girl/arm-front
bounds: 712, 279, 36, 115
rotate: 90
girl/back-eyebrow
bounds: 309, 61, 18, 12
rotate: 90
girl/bag-base
bounds: 694, 219, 62, 58
girl/bag-strap-front
bounds: 370, 304, 12, 96
rotate: 90
girl/bag-top
bounds: 765, 175, 49, 50
girl/body
bounds: 370, 318, 97, 132
rotate: 90
girl/boot-ribbon-front
bounds: 323, 64, 13, 13
girl/eye-iris-back
bounds: 361, 105, 17, 17
girl/eye-iris-front
bounds: 507, 106, 18, 18
girl/eye-white-back
bounds: 665, 175, 20, 16
girl/front-eyebrow
bounds: 343, 91, 18, 12
girl/hair-back
bounds: 696, 417, 147, 93
girl/hair-bangs
bounds: 922, 247, 91, 40
girl/hair-flap-down-front
bounds: 415, 171, 70, 65
rotate: 90
girl/hair-head-side-back
bounds: 991, 381, 30, 52
girl/hair-head-side-front
bounds: 859, 206, 41, 42
girl/hair-patch
bounds: 132, 2, 66, 41
rotate: 90
girl/hair-side
bounds: 692, 181, 36, 71
rotate: 90
girl/hair-strand-back-1
bounds: 948, 289, 58, 74
rotate: 90
girl/hair-strand-back-2
bounds: 355, 170, 91, 58
rotate: 90
girl/hair-strand-back-3
bounds: 215, 40, 92, 79
girl/hair-strand-front-1
bounds: 234, 263, 38, 94
rotate: 90
girl/hair-strand-front-2
bounds: 576, 233, 70, 50
rotate: 90
girl/hair-strand-front-3
bounds: 313, 124, 44, 81
rotate: 90
girl/hand-front-fingers
bounds: 923, 208, 19, 21
girl/hat
bounds: 218, 179, 93, 82
girl/leg-front
bounds: 831, 349, 30, 158
rotate: 90
girl/pompom
bounds: 416, 126, 48, 43
girl/scarf
bounds: 113, 264, 119, 51
girl/scarf-back
bounds: 502, 252, 72, 51
girl/zip
bounds: 816, 179, 19, 25

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,93 @@
raptor.png
size: 1024, 512
filter: Linear, Linear
scale: 0.5
back-arm
bounds: 895, 295, 46, 25
back-bracer
bounds: 992, 216, 39, 28
rotate: 90
back-hand
bounds: 594, 58, 36, 34
back-knee
bounds: 729, 86, 49, 67
rotate: 90
back-thigh
bounds: 379, 2, 39, 24
eyes-open
bounds: 902, 194, 47, 45
rotate: 90
front-arm
bounds: 945, 306, 48, 26
front-bracer
bounds: 949, 197, 41, 29
front-hand
bounds: 949, 266, 41, 38
front-open-hand
bounds: 875, 148, 43, 44
front-thigh
bounds: 793, 171, 57, 29
rotate: 90
gun
bounds: 379, 28, 107, 103
rotate: 90
gun-nohand
bounds: 487, 87, 105, 102
head
bounds: 807, 361, 136, 149
lower-leg
bounds: 827, 195, 73, 98
mouth-grind
bounds: 920, 145, 47, 30
rotate: 90
mouth-smile
bounds: 992, 257, 47, 30
rotate: 90
neck
bounds: 359, 114, 18, 21
raptor-back-arm
bounds: 653, 142, 82, 86
raptor-body
bounds: 2, 277, 632, 233
raptor-front-arm
bounds: 484, 4, 81, 102
rotate: 90
raptor-front-leg
bounds: 2, 18, 191, 257
raptor-hindleg-back
bounds: 636, 295, 169, 215
raptor-horn
bounds: 195, 22, 182, 80
raptor-horn-back
bounds: 945, 334, 176, 77
rotate: 90
raptor-jaw
bounds: 359, 137, 126, 138
raptor-jaw-tooth
bounds: 895, 322, 37, 48
rotate: 90
raptor-mouth-inside
bounds: 949, 228, 36, 41
rotate: 90
raptor-saddle-strap-back
bounds: 653, 86, 54, 74
rotate: 90
raptor-saddle-strap-front
bounds: 594, 94, 57, 95
raptor-saddle-w-shadow
bounds: 195, 104, 162, 171
raptor-tail-shadow
bounds: 636, 230, 189, 63
raptor-tongue
bounds: 807, 295, 86, 64
stirrup-back
bounds: 952, 151, 44, 35
rotate: 90
stirrup-front
bounds: 902, 243, 45, 50
stirrup-strap
bounds: 824, 147, 49, 46
torso
bounds: 737, 137, 54, 91
visor
bounds: 487, 191, 131, 84

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 KiB

View File

@ -0,0 +1,11 @@
sack-pma.png
size: 512, 512
filter: Linear, Linear
pma: true
scale: 0.5
cape-back
bounds: 237, 149, 260, 260
cape-front
bounds: 237, 43, 200, 104
sack
bounds: 2, 2, 233, 407

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -0,0 +1,210 @@
snowglobe-pma.png
size: 1024, 1024
filter: Linear, Linear
pma: true
scale: 0.5
arm-down-r
bounds: 884, 129, 76, 53
arm-up-l
bounds: 718, 23, 49, 114
rotate: 90
arm-up-r
bounds: 867, 69, 58, 104
rotate: 90
blue-present-base
bounds: 884, 883, 126, 139
eye-reflex-l
bounds: 991, 347, 12, 13
eye-reflex-r
bounds: 867, 129, 10, 12
rotate: 90
eye-white-l
bounds: 987, 697, 35, 43
eye-white-r
bounds: 560, 2, 34, 48
eyelashes-l
bounds: 982, 2, 32, 40
gift-base
bounds: 884, 335, 125, 105
rotate: 90
gift-decoration
bounds: 518, 2, 48, 40
rotate: 90
globe-borders
bounds: 2, 141, 880, 881
glove-l
bounds: 982, 44, 40, 61
glove-shadow-l
bounds: 991, 403, 28, 57
glove-shadow-r
bounds: 960, 204, 38, 62
rotate: 90
green-present-base
bounds: 138, 13, 126, 139
rotate: 90
hair-front
bounds: 884, 590, 150, 101
rotate: 90
hair-side
bounds: 995, 574, 27, 53
hair-strand-2
bounds: 987, 629, 26, 66
hair-strand-5
bounds: 690, 7, 25, 47
hair-strand-6
bounds: 995, 507, 14, 35
head-base
bounds: 2, 4, 134, 135
leg-down-l
bounds: 596, 3, 92, 51
leg-up-l
bounds: 718, 74, 65, 147
rotate: 90
leg-up-l-fuzzy
bounds: 834, 2, 73, 65
leg-up-r
bounds: 576, 56, 83, 140
rotate: 90
leg-up-r-fuzzy
bounds: 909, 2, 65, 71
rotate: 90
mouth
bounds: 991, 362, 39, 13
rotate: 90
neck-scarf
bounds: 279, 25, 142, 114
nose
bounds: 995, 488, 17, 14
rotate: 90
nose-shadow
bounds: 299, 8, 15, 15
red-present-base
bounds: 884, 742, 126, 139
scarf-end-l
bounds: 884, 462, 126, 109
rotate: 90
scarf-end-r
bounds: 423, 52, 151, 87
scarf-ribbon-middle-r
bounds: 960, 244, 62, 89
scarf-shadow
bounds: 884, 184, 149, 74
rotate: 90
shoe-l
bounds: 973, 107, 49, 95
shoe-r
bounds: 423, 6, 44, 93
rotate: 90
shoelace
bounds: 279, 2, 21, 18
rotate: 90
snow
bounds: 995, 544, 27, 28
string
bounds: 138, 6, 5, 53
rotate: 90
snowglobe-pma_2.png
size: 1024, 1024
filter: Linear, Linear
pma: true
scale: 0.5
arm-down-l
bounds: 884, 579, 56, 54
arm-down-l-fuzzy
bounds: 884, 635, 57, 59
arm-down-r-fuzzy
bounds: 884, 696, 61, 66
blue-present-decoration
bounds: 884, 216, 41, 40
green-present-decoration
bounds: 884, 216, 41, 40
ear-l
bounds: 884, 527, 55, 50
ear-r
bounds: 291, 94, 45, 66
rotate: 90
eyelashes-r
bounds: 2, 2, 32, 47
rotate: 90
globe-texture-strong
bounds: 2, 141, 880, 881
glove-fingers-l
bounds: 884, 361, 39, 51
glove-fingers-r
bounds: 884, 469, 41, 56
glove-r
bounds: 76, 36, 44, 65
rotate: 90
hair-strand-1
bounds: 359, 102, 37, 65
rotate: 90
hair-strand-3
bounds: 884, 414, 40, 53
hair-strand-4
bounds: 939, 893, 37, 69
iris-l
bounds: 884, 173, 40, 41
iris-r
bounds: 143, 39, 40, 41
leg-down-r
bounds: 2, 36, 72, 103
pupil-l
bounds: 51, 2, 32, 32
pupil-r
bounds: 85, 2, 32, 32
red-present-decoration
bounds: 426, 99, 41, 40
scarf-pompom-l
bounds: 884, 309, 50, 46
rotate: 90
scarf-pompom-r
bounds: 884, 258, 49, 47
rotate: 90
scarf-ribbon-bottom-l
bounds: 884, 856, 106, 53
rotate: 90
scarf-ribbon-bottom-r
bounds: 76, 82, 105, 57
scarf-ribbon-middle-l
bounds: 884, 764, 63, 90
scarf-ribbon-top-l
bounds: 884, 964, 105, 58
scarf-ribbon-top-r
bounds: 183, 86, 106, 53
snowglobe-pma_3.png
size: 1024, 1024
filter: Linear, Linear
pma: true
scale: 0.5
globe-texture
bounds: 2, 2, 880, 881
snowglobe-pma_4.png
size: 1024, 1024
filter: Linear, Linear
pma: true
scale: 0.5
elf-shadow
bounds: 2, 2, 395, 158
globe-reflections
bounds: 2, 162, 646, 835
globe-shadow
bounds: 650, 77, 920, 366
rotate: 90
hat
bounds: 399, 7, 153, 221
rotate: 90
snowglobe-pma_5.png
size: 1024, 1024
filter: Linear, Linear
pma: true
scale: 0.5
body
bounds: 710, 569, 139, 151
globe-base-back
bounds: 2, 2, 606, 258
globe-base-front
bounds: 2, 262, 706, 458

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 784 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -0,0 +1,95 @@
spineboy-pma.png
size: 1024, 256
filter: Linear, Linear
pma: true
scale: 0.5
crosshair
bounds: 352, 7, 45, 45
eye-indifferent
bounds: 862, 105, 47, 45
eye-surprised
bounds: 505, 79, 47, 45
front-bracer
bounds: 826, 66, 29, 40
front-fist-closed
bounds: 786, 65, 38, 41
front-fist-open
bounds: 710, 51, 43, 44
rotate: 90
front-foot
bounds: 210, 6, 63, 35
front-shin
bounds: 665, 128, 41, 92
rotate: 90
front-thigh
bounds: 2, 2, 23, 56
rotate: 90
front-upper-arm
bounds: 250, 205, 23, 49
goggles
bounds: 665, 171, 131, 83
gun
bounds: 798, 152, 105, 102
head
bounds: 2, 27, 136, 149
hoverboard-board
bounds: 2, 178, 246, 76
hoverboard-thruster
bounds: 722, 96, 30, 32
rotate: 90
hoverglow-small
bounds: 275, 81, 137, 38
mouth-grind
bounds: 614, 97, 47, 30
mouth-oooo
bounds: 612, 65, 47, 30
mouth-smile
bounds: 661, 64, 47, 30
muzzle-glow
bounds: 382, 54, 25, 25
muzzle-ring
bounds: 275, 54, 25, 105
rotate: 90
muzzle01
bounds: 911, 95, 67, 40
rotate: 90
muzzle02
bounds: 792, 108, 68, 42
muzzle03
bounds: 956, 171, 83, 53
rotate: 90
muzzle04
bounds: 275, 7, 75, 45
muzzle05
bounds: 140, 3, 68, 38
neck
bounds: 250, 182, 18, 21
portal-bg
bounds: 140, 43, 133, 133
portal-flare1
bounds: 554, 65, 56, 30
portal-flare2
bounds: 759, 112, 57, 31
rotate: 90
portal-flare3
bounds: 554, 97, 58, 30
portal-shade
bounds: 275, 121, 133, 133
portal-streaks1
bounds: 410, 126, 126, 128
portal-streaks2
bounds: 538, 129, 125, 125
rear-bracer
bounds: 857, 67, 28, 36
rear-foot
bounds: 663, 96, 57, 30
rear-shin
bounds: 414, 86, 38, 89
rotate: 90
rear-thigh
bounds: 756, 63, 28, 47
rear-upper-arm
bounds: 60, 5, 20, 44
rotate: 90
torso
bounds: 905, 164, 49, 90

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,94 @@
spineboy.png
size: 1024, 256
filter: Linear, Linear
scale: 0.5
crosshair
bounds: 352, 7, 45, 45
eye-indifferent
bounds: 862, 105, 47, 45
eye-surprised
bounds: 505, 79, 47, 45
front-bracer
bounds: 826, 66, 29, 40
front-fist-closed
bounds: 786, 65, 38, 41
front-fist-open
bounds: 710, 51, 43, 44
rotate: 90
front-foot
bounds: 210, 6, 63, 35
front-shin
bounds: 665, 128, 41, 92
rotate: 90
front-thigh
bounds: 2, 2, 23, 56
rotate: 90
front-upper-arm
bounds: 250, 205, 23, 49
goggles
bounds: 665, 171, 131, 83
gun
bounds: 798, 152, 105, 102
head
bounds: 2, 27, 136, 149
hoverboard-board
bounds: 2, 178, 246, 76
hoverboard-thruster
bounds: 722, 96, 30, 32
rotate: 90
hoverglow-small
bounds: 275, 81, 137, 38
mouth-grind
bounds: 614, 97, 47, 30
mouth-oooo
bounds: 612, 65, 47, 30
mouth-smile
bounds: 661, 64, 47, 30
muzzle-glow
bounds: 382, 54, 25, 25
muzzle-ring
bounds: 275, 54, 25, 105
rotate: 90
muzzle01
bounds: 911, 95, 67, 40
rotate: 90
muzzle02
bounds: 792, 108, 68, 42
muzzle03
bounds: 956, 171, 83, 53
rotate: 90
muzzle04
bounds: 275, 7, 75, 45
muzzle05
bounds: 140, 3, 68, 38
neck
bounds: 250, 182, 18, 21
portal-bg
bounds: 140, 43, 133, 133
portal-flare1
bounds: 554, 65, 56, 30
portal-flare2
bounds: 759, 112, 57, 31
rotate: 90
portal-flare3
bounds: 554, 97, 58, 30
portal-shade
bounds: 275, 121, 133, 133
portal-streaks1
bounds: 410, 126, 126, 128
portal-streaks2
bounds: 538, 129, 125, 125
rear-bracer
bounds: 857, 67, 28, 36
rear-foot
bounds: 663, 96, 57, 30
rear-shin
bounds: 414, 86, 38, 89
rotate: 90
rear-thigh
bounds: 756, 63, 28, 47
rear-upper-arm
bounds: 60, 5, 20, 44
rotate: 90
torso
bounds: 905, 164, 49, 90

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

View File

@ -0,0 +1,19 @@
stretchyman-pma.png
size: 1024, 256
filter: Linear, Linear
pma: true
back-arm
bounds: 679, 173, 72, 202
rotate: 90
back-leg
bounds: 2, 2, 100, 318
rotate: 90
body
bounds: 2, 104, 141, 452
rotate: 90
front-arm
bounds: 456, 100, 145, 221
rotate: 90
head
bounds: 322, 15, 87, 102
rotate: 90

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@ -0,0 +1,71 @@
<html>
<head>
<meta charset="UTF-8" />
<title>spine-pixi-v8</title>
<script src="https://cdn.jsdelivr.net/npm/pixi.js@8.4.1/dist/pixi.min.js"></script>
<script src="../dist/iife/spine-pixi-v8.js"></script>
<script src="./assets/lil-gui.umd.min.js"></script>
<link href="./assets/lil-gui.min.css" rel="stylesheet">
<link rel="stylesheet" href="../../index.css">
</head>
<body>
<script>
(async function () {
var app = new PIXI.Application();
await app.init({
width: window.innerWidth,
height: window.innerHeight,
resolution: window.devicePixelRatio || 1,
autoDensity: true,
resizeTo: window,
backgroundColor: 0x2c3e50,
hello: true,
})
document.body.appendChild(app.view);
// Pre-load the skeleton data and atlas. You can also load .json skeleton data.
PIXI.Assets.add({alias: "spineboyData", src: "./assets/coin-pro.json"});
PIXI.Assets.add({alias: "spineboyAtlas", src: "./assets/coin-pro.atlas"});
await PIXI.Assets.load(["spineboyData", "spineboyAtlas"]);
// Create the spine display object
const spineboy = spine.Spine.from({
skeleton: "spineboyData",
atlas: "spineboyAtlas",
scale: 1,
// darkTint: true,
});
spineboy.autoUpdate = false;
// Set the default mix time to use when transitioning
// from one animation to the next.
spineboy.state.data.defaultMix = 0.2;
// Center the spine object on screen.
spineboy.x = window.innerWidth / 2;
spineboy.y = window.innerHeight / 2;
// Set animation "run" on track 0, looped.
spineboy.state.setAnimation(0, "animation", true);
// Add the display object to the stage.
app.stage.addChild(spineboy);
const myObject = { time: 0, time2: 0 };
let prevValue = myObject.time;
let prevValue2 = myObject.time2;
spineboy.update(prevValue / 10)
const gui = new lil.GUI({});
gui
.add(myObject, 'time').min(0).max(10).step(0.001)
.name( 'time' )
.onChange(value => {
spineboy.update((value - prevValue) / 10)
prevValue = value;
});
})();
</script>
</body>
</html>

View File

@ -0,0 +1,109 @@
<html>
<head>
<meta charset="UTF-8" />
<title>spine-pixi-v8</title>
<script src="https://cdn.jsdelivr.net/npm/pixi.js@8.4.1/dist/pixi.min.js"></script>
<script src="../dist/iife/spine-pixi-v8.js"></script>
<link rel="stylesheet" href="../../index.css">
</head>
<body>
<script>
(async function () {
var app = new PIXI.Application();
await app.init({
width: window.innerWidth,
height: window.innerHeight,
resolution: window.devicePixelRatio || 1,
autoDensity: true,
resizeTo: window,
backgroundColor: 0x2c3e50,
hello: true,
})
document.body.appendChild(app.view);
app.stage.eventMode = 'static';
app.stage.hitArea = app.screen;
let dragObject = null;
let lastX = -1, lastY = -1;
const endDrag = () => (dragObject = null);
app.stage
.on('pointerup', endDrag)
.on('pointerupoutside', endDrag)
.on('pointermove', ({ x, y }) => {
if (dragObject) {
dragObject.x += x - lastX;
dragObject.y += y - lastY;
lastX = x;
lastY = y;
}
});
// Pre-load the skeleton data and atlas. You can also load .json skeleton data.
PIXI.Assets.add({alias: "stretchymanData", src: "./assets/stretchyman-pro.skel"});
PIXI.Assets.add({alias: "stretchymanAtlas", src: "./assets/stretchyman-pma.atlas"});
await PIXI.Assets.load(["stretchymanData", "stretchymanAtlas"]);
// Create the spine display object
const stretchyman = spine.Spine.from({skeleton: "stretchymanData", atlas: "stretchymanAtlas",
scale: 0.75,
});
// Set the default mix time to use when transitioning
// from one animation to the next.
stretchyman.state.data.defaultMix = 0.2;
// Center the spine object on screen.
stretchyman.x = window.innerWidth / 2;
stretchyman.y = window.innerHeight / 2 + stretchyman.getBounds().height / 2;
// Set animation "run" on track 0, looped.
stretchyman.state.setAnimation(0, "idle", true);
app.stage.addChild(stretchyman);
const controlBoneNames = [
"back-arm-ik-target",
"back-leg-ik-target",
"front-arm-ik-target",
"front-leg-ik-target",
];
const controlBones = [];
// wait a frame as pixi bounds do not work until rendered
await new Promise((resolve) => requestAnimationFrame(resolve));
for (var i = 0; i < controlBoneNames.length; i++) {
const bone = stretchyman.skeleton.findBone(controlBoneNames[i]);
const point = { x: bone.worldX, y: bone.worldY };
stretchyman.skeletonToPixiWorldCoordinates(point);
const control = new PIXI.Graphics()
.circle(0, 0, 6)
.fill('#ff00ff')
control.x = point.x;
control.y = point.y;
controlBones.push({ bone, control });
app.stage.addChild(control);
control.interactive = "static";
control.on('pointerdown', ({ x, y }) => {
dragObject = control;
lastX = x;
lastY = y;
})
}
const point = { x: 0, y: 0 };
stretchyman.beforeUpdateWorldTransforms = () => {
for (let { bone, control } of controlBones) {
point.x = control.x;
point.y = control.y;
stretchyman.pixiWorldCoordinatesToBone(point, bone);
bone.x = point.x;
bone.y = point.y;
}
};
})();
</script>
</body>
</html>

View File

@ -0,0 +1,86 @@
<html>
<head>
<meta charset="UTF-8" />
<title>spine-pixi-v8</title>
<script src="./assets/pixi.min.js"></script>
<script src="../dist/iife/spine-pixi-v8.js"></script>
<script src="./assets/lil-gui.umd.min.js"></script>
<link href="./assets/lil-gui.min.css" rel="stylesheet">
<link rel="stylesheet" href="../../index.css">
</head>
<body>
<script>
(async function () {
var app = new PIXI.Application();
await app.init({
width: window.innerWidth,
height: window.innerHeight,
resolution: window.devicePixelRatio || 1,
autoDensity: true,
resizeTo: window,
backgroundColor: 0x2c3e50,
hello: true,
})
document.body.appendChild(app.view);
// Pre-load the skeleton data and atlas. You can also load .json skeleton data.
PIXI.Assets.add({alias: "spineboyData", src: "./assets/dragon-ess.skel" });
PIXI.Assets.add({alias: "spineboyAtlas", src: "./assets/dragon-pma.atlas" });
PIXI.Assets.add({alias: "spineboyData2", src: "./assets/dragon-ess.skel" });
PIXI.Assets.add({alias: "spineboyAtlas2", src: "./assets/dragon-pma.atlas" });
await PIXI.Assets.load(["spineboyData", "spineboyAtlas", "spineboyData", "spineboyAtlas2", "raptor_jaw"]);
// Create the spine display object
const spineboy = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: 0.5 });
const spineboy2 = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: 0.5 });
spineboy.autoUpdate = false;
spineboy2.autoUpdate = false;
// Set the default mix time to use when transitioning
// from one animation to the next.
spineboy.state.data.defaultMix = 0.2;
// Center the spine object on screen.
spineboy.x = window.innerWidth / 2;
spineboy.y = window.innerHeight / 2 - 30;
spineboy2.x = window.innerWidth / 2;
spineboy2.y = window.innerHeight / 2 + 200;
spineboy2.state.setAnimation(0, "flying", true);
// Set animation "run" on track 0, looped.
spineboy.state.setAnimation(0, "flying", true);
// Add the display object to the stage.
app.stage.addChild(spineboy);
app.stage.addChild(spineboy2);
const myObject = { time: 0, time2: 0 };
let prevValue = myObject.time;
let prevValue2 = myObject.time2;
spineboy.update(prevValue / 10)
spineboy2.update(prevValue2 / 10)
const gui = new lil.GUI({});
gui
.add(myObject, 'time').min(0).max(10).step(0.01)
.name( 'time' )
.onChange(value => {
spineboy.update((value - prevValue) / 10)
prevValue = value;
});
gui
.add(myObject, 'time2').min(0).max(10).step(0.01)
.name( 'time2' )
.onChange(value => {
spineboy2.update((value - prevValue2) / 10)
prevValue2 = value;
});
})();
</script>
</body>
</html>

View File

@ -0,0 +1,81 @@
<html>
<head>
<meta charset="UTF-8" />
<title>spine-pixi-v8</title>
<script src="https://cdn.jsdelivr.net/npm/pixi.js@8.4.1/dist/pixi.min.js"></script>
<script src="../dist/iife/spine-pixi-v8.js"></script>
<link rel="stylesheet" href="../../index.css">
</head>
<body>
<!-- Creates a transparent logging overlay. -->
<div id="log" class="overlay" style="user-select: none;" max-height: 300px; overflow: auto;>
</div>
<script>
function log(message) {
const log = document.querySelector("#log");
log.innerText += message + "\n";
log.scrollTop = log.scrollHeight;
console.log(message);
}
(async function () {
var app = new PIXI.Application();
await app.init({
width: window.innerWidth,
height: window.innerHeight,
resolution: window.devicePixelRatio || 1,
autoDensity: true,
resizeTo: window,
backgroundColor: 0x2c3e50,
hello: true,
})
document.body.appendChild(app.view);
// Pre-load the skeleton data and atlas. You can also load .json skeleton data.
PIXI.Assets.add({alias: "spineboyData", src: "./assets/spineboy-pro.skel" });
PIXI.Assets.add({alias: "spineboyAtlas", src: "./assets/spineboy-pma.atlas" });
await PIXI.Assets.load(["spineboyData", "spineboyAtlas"]);
// Create the Spine display object
const spineboy = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas",
scale: 0.5,
});
// Set animation "run" on track 0, looped.
spineboy.state.setAnimation(0, "run", true);
// Set callbacks to receive animation state events.
spineboy.state.addListener({
start: (entry) => log(`Started animation ${entry.animation.name}`),
interrupt: (entry) => log(`Interrupted animation ${entry.animation.name}`),
end: (entry) => log(`Ended animation ${entry.animation.name}`),
dispose: (entry) => log(`Disposed animation ${entry.animation.name}`),
complete: (entry) => log(`Completed animation ${entry.animation.name}`),
});
// Add a custom event listener along with an
// unlooped animation to see the custom event logged.
const trackEntry = spineboy.state.addAnimation(0, "walk", false, 3);
trackEntry.listener = {
event: (entry, event) =>
log(`Custom event for ${entry.animation.name}: ${event.data.name}`),
};
spineboy.state.addAnimation(0, "run", true, 0);
// Set the default mix time to use when transitioning
// from one animation to the next.
spineboy.state.data.defaultMix = 0.2;
// Center the spine object on screen.
spineboy.x = window.innerWidth / 2;
spineboy.y = window.innerHeight / 2 + spineboy.getBounds().height / 2;
// Add the display object to the stage.
app.stage.addChild(spineboy);
})();
</script>
</body>
</html>

View File

@ -0,0 +1,51 @@
<html>
<head>
<meta charset="UTF-8" />
<title>spine-pixi-v8</title>
<script src="https://cdn.jsdelivr.net/npm/pixi.js@8.4.1/dist/pixi.min.js"></script>
<script src="../dist/iife/spine-pixi-v8.js"></script>
<link rel="stylesheet" href="../../index.css">
</head>
<body>
<script>
(async function () {
var app = new PIXI.Application();
await app.init({
width: window.innerWidth,
height: window.innerHeight,
resolution: window.devicePixelRatio || 1,
autoDensity: true,
resizeTo: window,
backgroundColor: 0x2c3e50,
hello: true,
})
document.body.appendChild(app.view);
// Pre-load the skeleton data and atlas. You can also load .json skeleton data.
PIXI.Assets.add({alias: "spineboyData", src: "./assets/spineboy-pro.skel"});
PIXI.Assets.add({alias: "spineboyAtlas", src: "./assets/spineboy-pma.atlas"});
await PIXI.Assets.load(["spineboyData", "spineboyAtlas"]);
// Create the spine display object
const spineboy = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas",
scale: 0.5,
});
// Set the default mix time to use when transitioning
// from one animation to the next.
spineboy.state.data.defaultMix = 0.2;
// Center the spine object on screen.
spineboy.x = window.innerWidth / 2;
spineboy.y = window.innerHeight / 2 + spineboy.getBounds().height / 2;
// Set animation "run" on track 0, looped.
spineboy.state.setAnimation(0, "run", true);
// Add the display object to the stage.
app.stage.addChild(spineboy);
})();
</script>
</body>
</html>

View File

@ -0,0 +1,58 @@
<html>
<head>
<meta charset="UTF-8" />
<title>spine-pixi-v8</title>
<script src="https://cdn.jsdelivr.net/npm/pixi.js@8.4.1/dist/pixi.min.js"></script>
<script src="../dist/iife/spine-pixi-v8.js"></script>
<link rel="stylesheet" href="../../index.css">
</head>
<body>
<script>
(async function () {
var app = new PIXI.Application();
await app.init({
width: window.innerWidth,
height: window.innerHeight,
resolution: window.devicePixelRatio || 1,
autoDensity: true,
resizeTo: window,
backgroundColor: 0x2c3e50,
hello: true,
})
document.body.appendChild(app.view);
// Pre-load the skeleton data and atlas. You can also load .json skeleton data.
PIXI.Assets.add({alias: "spineboyData", src: "./assets/spineboy-pro.skel" });
PIXI.Assets.add({alias: "spineboyAtlas", src: "./assets/spineboy-pma.atlas" });
await PIXI.Assets.load(["spineboyData", "spineboyAtlas"]);
// Manually load the data and create a Spine display object from it using
// the Spine core API. This will not use the interal cache like Spine.from(),
// so you have to cache data yourself.
const atlas = PIXI.Assets.get("spineboyAtlas");
const attachmentLoader = new spine.AtlasAttachmentLoader(atlas);
const binaryLoader = new spine.SkeletonBinary(attachmentLoader);
binaryLoader.scale = 0.5;
const skeletonData = binaryLoader.readSkeletonData(
PIXI.Assets.get("spineboyData")
);
const spineboy = new spine.Spine(skeletonData);
// Set the default mix time to use when transitioning
// from one animation to the next.
spineboy.state.data.defaultMix = 0.2;
// Center the spine object on screen.
spineboy.x = window.innerWidth / 2;
spineboy.y = window.innerHeight / 2 + spineboy.getBounds().height / 2;
// Set animation "run" on track 0, looped.
spineboy.state.setAnimation(0, "run", true);
// Add the display object to the stage.
app.stage.addChild(spineboy);
})();
</script>
</body>
</html>

View File

@ -0,0 +1,68 @@
<html>
<head>
<meta charset="UTF-8" />
<title>spine-pixi-v8</title>
<script src="https://cdn.jsdelivr.net/npm/pixi.js@8.4.1/dist/pixi.min.js"></script>
<script src="../dist/iife/spine-pixi-v8.js"></script>
<link rel="stylesheet" href="../../index.css">
</head>
<body>
<script>
(async function () {
var app = new PIXI.Application();
await app.init({
width: window.innerWidth,
height: window.innerHeight,
resolution: window.devicePixelRatio || 1,
autoDensity: true,
resizeTo: window,
backgroundColor: 0x2c3e50,
hello: true,
})
document.body.appendChild(app.view);
// Pre-load the skeleton data and atlas. You can also load .json skeleton data.
PIXI.Assets.add({alias: "mixAndMatchData", src: "./assets/mix-and-match-pro.skel" });
PIXI.Assets.add({alias: "mixAndMatchAtlas", src: "./assets/mix-and-match-pma.atlas" });
await PIXI.Assets.load(["mixAndMatchData", "mixAndMatchAtlas"]);
// Create the Spine display object
const mixAndMatch = spine.Spine.from({skeleton: "mixAndMatchData", atlas: "mixAndMatchAtlas",
scale: 0.5,
});
// Set the default mix time to use when transitioning
// from one animation to the next.
mixAndMatch.state.data.defaultMix = 0.2;
// Add animations.
mixAndMatch.state.setAnimation(0, "walk", true);
mixAndMatch.state.addAnimation(0, "dance", true, 1.0);
mixAndMatch.state.addAnimation(0, "walk", true, 1.0);
// Add a custom skin
const skeletonData = mixAndMatch.skeleton.data;
const skin = new spine.Skin("custom");
skin.addSkin(skeletonData.findSkin("nose/short"));
skin.addSkin(skeletonData.findSkin("skin-base"));
skin.addSkin(skeletonData.findSkin("eyes/violet"));
skin.addSkin(skeletonData.findSkin("hair/brown"));
skin.addSkin(skeletonData.findSkin("clothes/hoodie-orange"));
skin.addSkin(skeletonData.findSkin("legs/pants-jeans"));
skin.addSkin(skeletonData.findSkin("accessories/bag"));
skin.addSkin(skeletonData.findSkin("accessories/hat-red-yellow"));
skin.addSkin(skeletonData.findSkin("eyelids/girly"));
mixAndMatch.skeleton.setSkin(skin);
mixAndMatch.skeleton.setSlotsToSetupPose();
// Center the spine object on screen.
mixAndMatch.x = window.innerWidth / 2;
mixAndMatch.y = window.innerHeight / 2 + mixAndMatch.getBounds().height / 2;
// Add the display object to the stage.
app.stage.addChild(mixAndMatch);
})();
</script>
</body>
</html>

View File

@ -0,0 +1,91 @@
<html>
<head>
<meta charset="UTF-8" />
<title>spine-pixi-v8</title>
<script src="https://cdn.jsdelivr.net/npm/pixi.js@8.4.1/dist/pixi.min.js"></script>
<script src="../dist/iife/spine-pixi-v8.js"></script>
<link rel="stylesheet" href="../../index.css">
</head>
<body>
<script>
(async function () {
var app = new PIXI.Application();
await app.init({
width: window.innerWidth,
height: window.innerHeight,
resolution: window.devicePixelRatio || 1,
autoDensity: true,
resizeTo: window,
backgroundColor: 0x2c3e50,
hello: true,
})
document.body.appendChild(app.view);
// Pre-load the skeleton data and atlas. You can also load .json skeleton data.
PIXI.Assets.add({alias: "spineboyData", src: "./assets/spineboy-pro.skel" });
PIXI.Assets.add({alias: "spineboyAtlas", src: "./assets/spineboy-pma.atlas" });
await PIXI.Assets.load(["spineboyData", "spineboyAtlas"]);
// Create the spine display object
const spineboy = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas",
scale: 0.5,
});
// Set the default mix time to use when transitioning
// from one animation to another.
spineboy.state.data.defaultMix = 0.2;
// Center the Spine object on screen.
spineboy.x = window.innerWidth / 2;
spineboy.y = window.innerHeight / 2 + spineboy.getBounds().height / 2;
// Set looping animations "idle" on track 0 and "aim" on track 1.
spineboy.state.setAnimation(0, "idle", true);
spineboy.state.setAnimation(1, "aim", true);
// Add the display object to the stage.
app.stage.addChild(spineboy);
app.stage.hitArea = new PIXI.Rectangle(0, 0, app.view.width, app.view.height);
// Make the stage interactive and register pointer events
app.stage.eventMode = "dynamic";
let isDragging = false;
app.stage.on("pointerdown", (e) => {
isDragging = true;
setBonePosition(e);
});
app.stage.on("globalpointermove", (e) => {
if (isDragging) setBonePosition(e);
});
app.stage.on("pointerup", (e) => (isDragging = false));
const setBonePosition = (e) => {
// Transform the mouse/touch coordinates to Spineboy's coordinate
// system origin. `position` is then relative to Spineboy's root
// bone.
const position = new spine.Vector2(
e.data.global.x - spineboy.x,
e.data.global.y - spineboy.y
);
// Find the crosshair bone.
const crosshairBone = spineboy.skeleton.findBone("crosshair");
// Take the mouse position, which is relative to the root bone,
// and transform it to the crosshair bone's parent root bone
// coordinate system via `worldToLocal()`. `position` is relative
// to the crosshair bone's parent bone after this
crosshairBone.parent.worldToLocal(position);
// Set the crosshair bone's position to the mouse position
crosshairBone.x = position.x;
crosshairBone.y = position.y;
};
})();
</script>
</body>
</html>

View File

@ -0,0 +1,47 @@
<html>
<head>
<meta charset="UTF-8" />
<title>spine-pixi-v8</title>
<script src="https://cdn.jsdelivr.net/npm/pixi.js@8.4.1/dist/pixi.min.js"></script>
<script src="../dist/iife/spine-pixi-v8.js"></script>
<link rel="stylesheet" href="../../index.css">
</head>
<body>
<script>
(async function () {
var app = new PIXI.Application();
await app.init({
width: window.innerWidth,
height: window.innerHeight,
resolution: window.devicePixelRatio || 1,
autoDensity: true,
resizeTo: window,
backgroundColor: 0x2c3e50,
hello: true,
})
document.body.appendChild(app.view);
// Pre-load the skeleton data and atlas. You can also load .json skeleton data.
PIXI.Assets.add({alias: "sackData", src: "./assets/sack-pro.skel"});
PIXI.Assets.add({alias: "sackAtlas", src: "./assets/sack-pma.atlas"});
await PIXI.Assets.load(["sackData", "sackAtlas"]);
// Create the spine display object
const sack = spine.Spine.from({skeleton: "sackData", atlas: "sackAtlas",
scale: 0.2,
});
// Center the spine object on screen.
sack.x = window.innerWidth / 2;
sack.y = window.innerHeight / 2 + sack.getBounds().height / 2;
// Set animation "cape-follow-example" on track 0, looped.
sack.state.setAnimation(0, "cape-follow-example", true);
// Add the display object to the stage.
app.stage.addChild(sack);
})();
</script>
</body>
</html>

View File

@ -0,0 +1,142 @@
<html>
<head>
<meta charset="UTF-8" />
<title>spine-pixi-v8</title>
<script src="https://cdn.jsdelivr.net/npm/pixi.js@8.4.1/dist/pixi.min.js"></script>
<script src="../dist/iife/spine-pixi-v8.js"></script>
<link rel="stylesheet" href="../../index.css">
</head>
<body>
<script>
(async function () {
var app = new PIXI.Application();
await app.init({
width: window.innerWidth,
height: window.innerHeight,
resolution: window.devicePixelRatio || 1,
autoDensity: true,
resizeTo: window,
backgroundColor: 0x2c3e50,
hello: true,
})
document.body.appendChild(app.view);
// Pre-load the skeleton data and atlas. You can also load .json skeleton data.
PIXI.Assets.add({alias: "girlData", src: "./assets/celestial-circus-pro.skel"});
PIXI.Assets.add({alias: "girlAtlas", src: "./assets/celestial-circus-pma.atlas"});
await PIXI.Assets.load(["girlData", "girlAtlas"]);
// Create the spine display object
const girl = spine.Spine.from({skeleton: "girlData", atlas: "girlAtlas",
scale: 0.2,
});
// Center the spine object on screen.
girl.x = window.innerWidth / 2;
girl.y = window.innerHeight / 2 + girl.getBounds().height / 4;
// Set animation "eyeblink-long" on track 0, looped.
girl.state.setAnimation(0, "eyeblink-long", true);
// Add the display object to the stage.
app.stage.addChild(girl);
// Make the stage interactive and register pointer events
app.stage.eventMode = "dynamic";
app.stage.hitArea = app.screen;
let isDragging = false;
let lastX = -1, lastY = -1;
app.stage.on("pointerdown", (e) => {
isDragging = true;
let mousePosition = new spine.Vector2(e.data.global.x, e.data.global.y);
lastX = mousePosition.x;
lastY = mousePosition.y;
});
app.stage.on("pointermove", (e) => {
if (isDragging) {
let mousePosition = new spine.Vector2(e.data.global.x, e.data.global.y);
girl.x += mousePosition.x - lastX;
girl.y += mousePosition.y - lastY;
girl.skeleton.physicsTranslate(mousePosition.x - lastX, mousePosition.y - lastY);
lastX = mousePosition.x;
lastY = mousePosition.y;
}
});
const endDrag = () => (isDragging = false);
app.stage.on("pointerup", endDrag);
app.stage.on("pointerupoutside", endDrag);
document.addEventListener('fullscreenchange', () => {
endDrag();
});
const buttonContainer = new PIXI.Container();
buttonContainer.position.set(0, 0);
const buttonBackground = new PIXI.Graphics();
buttonBackground.beginFill(0x000000); // Button background color
buttonBackground.drawRoundedRect(0, 0, 140, 100, 5); // Button dimensions
buttonBackground.endFill();
buttonContainer.addChild(buttonBackground);
buttonContainer.alpha = 0.7;
const fontStyle = {
fill: 0xdddddd,
fontFamily: 'sans-serif',
fontSize: 16,
};
// Create the text label for the heading
const textHeading = new PIXI.Text("Drag anywhere", fontStyle); // Button text and color
textHeading.position.set(15, 15); // Set the position of the text within the button
buttonContainer.addChild(textHeading);
// Create the text label for the FPS counter
const textFps = new PIXI.Text("0 fps", fontStyle);
textFps.position.set(15, 40);
buttonContainer.addChild(textFps);
// Create the text label for the button toggle fullscreen
const textButton = new PIXI.Text("Fullscreen", fontStyle); // Button text and color
textButton.position.set(15, 65); // Set the position of the text within the button
buttonContainer.addChild(textButton);
// Add the button container to the stage
app.stage.addChild(buttonContainer);
buttonContainer.interactive = true;
buttonContainer.buttonMode = true;
let fsEnabled = false;
buttonContainer.on('pointerdown', () => {
if (fsEnabled) {
document.exitFullscreen();
textButton.text = "Fullscreen";
} else {
app.renderer.view.requestFullscreen();
textButton.text = "Windowed";
}
fsEnabled = !fsEnabled;
});
app.ticker.add(throttle(() => textFps.text = app.ticker.FPS.toFixed(2) + " fps", 250));
})();
const throttle = (func, delay) => {
let lastCall = 0;
return (...args) => {
const now = Date.now();
if (now - lastCall >= delay) {
func.apply(this, args);
lastCall = now;
}
};
}
</script>
</body>
</html>

View File

@ -0,0 +1,47 @@
<html>
<head>
<meta charset="UTF-8" />
<title>spine-pixi-v8</title>
<script src="https://cdn.jsdelivr.net/npm/pixi.js@8.4.1/dist/pixi.min.js"></script>
<script src="../dist/iife/spine-pixi-v8.js"></script>
<link rel="stylesheet" href="../../index.css">
</head>
<body>
<script>
(async function () {
var app = new PIXI.Application();
await app.init({
width: window.innerWidth,
height: window.innerHeight,
resolution: window.devicePixelRatio || 1,
autoDensity: true,
resizeTo: window,
backgroundColor: 0x2c3e50,
hello: true,
})
document.body.appendChild(app.view);
// Pre-load the skeleton data and atlas. You can also load .json skeleton data.
PIXI.Assets.add({alias: "snowglobeData", src: "./assets/snowglobe-pro.skel"});
PIXI.Assets.add({alias: "snowglobeAtlas", src: "./assets/snowglobe-pma.atlas"});
await PIXI.Assets.load(["snowglobeData", "snowglobeAtlas"]);
// Create the spine display object
const snowglobe = spine.Spine.from({skeleton: "snowglobeData", atlas: "snowglobeAtlas",
scale: 0.25,
});
// Center the spine object on screen.
snowglobe.x = window.innerWidth / 2;
snowglobe.y = window.innerHeight / 2 + snowglobe.getBounds().height / 4;
// Set animation "FOA" on track 0, looped.
snowglobe.state.setAnimation(0, "shake", true);
// Add the display object to the stage.
app.stage.addChild(snowglobe);
})();
</script>
</body>
</html>

View File

@ -0,0 +1,47 @@
<html>
<head>
<meta charset="UTF-8" />
<title>spine-pixi-v8</title>
<script src="https://cdn.jsdelivr.net/npm/pixi.js@8.4.1/dist/pixi.min.js"></script>
<script src="../dist/iife/spine-pixi-v8.js"></script>
<link rel="stylesheet" href="../../index.css">
</head>
<body>
<script>
(async function () {
var app = new PIXI.Application();
await app.init({
width: window.innerWidth,
height: window.innerHeight,
resolution: window.devicePixelRatio || 1,
autoDensity: true,
resizeTo: window,
backgroundColor: 0x2c3e50,
hello: true,
})
document.body.appendChild(app.view);
// Pre-load the skeleton data and atlas. You can also load .json skeleton data.
PIXI.Assets.add({alias: "cloudPotData", src: "./assets/cloud-pot.skel"});
PIXI.Assets.add({alias: "cloudPotAtlas", src: "./assets/cloud-pot-pma.atlas"});
await PIXI.Assets.load(["cloudPotData", "cloudPotAtlas"]);
// Create the spine display object
const cloudPot = spine.Spine.from({skeleton: "cloudPotData", atlas: "cloudPotAtlas",
scale: 0.25,
});
// Center the spine object on screen.
cloudPot.x = window.innerWidth / 2;
cloudPot.y = window.innerHeight / 2 + cloudPot.getBounds().height / 4;
// Set animation "playing-in-the-rain" on track 0, looped.
cloudPot.state.setAnimation(0, "playing-in-the-rain", true);
// Add the display object to the stage.
app.stage.addChild(cloudPot);
})();
</script>
</body>
</html>

View File

@ -0,0 +1,98 @@
<html>
<head>
<meta charset="UTF-8" />
<title>spine-pixi-v8</title>
<script src="https://cdn.jsdelivr.net/npm/pixi.js@8.4.1/dist/pixi.min.js"></script>
<script src="../dist/iife/spine-pixi-v8.js"></script>
<link rel="stylesheet" href="../../index.css">
</head>
<body>
<script>
(async function () {
var app = new PIXI.Application();
await app.init({
width: window.innerWidth,
height: window.innerHeight,
resolution: window.devicePixelRatio || 1,
autoDensity: true,
resizeTo: window,
backgroundColor: 0x2c3e50,
hello: true,
})
document.body.appendChild(app.view);
// Pre-load the skeleton data and atlas. You can also load .json skeleton data.
PIXI.Assets.add({alias: "spineboyData", src: "./assets/spineboy-pro.skel" });
PIXI.Assets.add({alias: "spineboyAtlas", src: "./assets/spineboy-pma.atlas" });
await PIXI.Assets.load(["spineboyData", "spineboyAtlas"]);
// Create the spine display object
const spineboy = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas",
scale: 0.5,
});
// Set the default animation and the
// default mix for transitioning between animations.
spineboy.state.setAnimation(0, "hoverboard", true);
spineboy.state.data.defaultMix = 0.2;
// Center the spine object on screen.
spineboy.x = window.innerWidth / 2;
spineboy.y = window.innerHeight / 2 + spineboy.getBounds().height / 2;
// Make it so that you can interact with Spineboy.
// Handle the case that you click/tap the screen.
spineboy.eventMode = 'static';
spineboy.on('pointerdown', onClick);
// Add the display object to the stage.
app.stage.addChild(spineboy);
// Add variables for movement, speed.
let moveLeft = false;
let moveRight = false;
const speed = 5;
// Handle the case that the keyboard keys specified below are pressed.
function onKeyDown(key) {
if (key.code === "ArrowLeft" || key.code === "KeyA") {
moveLeft = true;
spineboy.skeleton.scaleX = -1;
} else if (key.code === "ArrowRight" || key.code === "KeyD") {
moveRight = true;
spineboy.skeleton.scaleX = 1;
}
}
// Handle when the keys are released, if they were pressed.
function onKeyUp(key) {
if (key.code === "ArrowLeft" || key.code === "KeyA") {
moveLeft = false;
} else if (key.code === "ArrowRight" || key.code === "KeyD") {
moveRight = false;
}
}
// Handle if you click/tap the screen.
function onClick() {
spineboy.state.setAnimation(1, "shoot", false, 0);
}
// Add event listeners so that the window will correctly handle input.
window.addEventListener("keydown", onKeyDown);
window.addEventListener("keyup", onKeyUp);
// Update the application to move Spineboy if input is detected.
app.ticker.add(() => {
if (moveLeft) {
spineboy.x -= speed;
}
if (moveRight) {
spineboy.x += speed;
}
});
})();
</script>
</body>
</html>

View File

@ -0,0 +1,141 @@
<html>
<head>
<meta charset="UTF-8" />
<title>spine-pixi-v8</title>
<script src="https://cdn.jsdelivr.net/npm/pixi.js@8.4.1/dist/pixi.min.js"></script>
<script src="../dist/iife/spine-pixi-v8.js"></script>
<link rel="stylesheet" href="../../index.css">
</head>
<body>
<script>
(async function () {
var app = new PIXI.Application();
await app.init({
width: window.innerWidth,
height: window.innerHeight,
resolution: window.devicePixelRatio || 1,
autoDensity: true,
resizeTo: window,
backgroundColor: 0x2c3e50,
hello: true,
})
document.body.appendChild(app.view);
// Pre-load the skeleton data and atlas. You can also load .json skeleton data.
PIXI.Assets.add({alias: "spineboyData", src: "./assets/spineboy-pro.skel" });
PIXI.Assets.add({alias: "spineboyAtlas", src: "./assets/spineboy-pma.atlas" });
PIXI.Assets.add({alias: "raptor_jaw", src: "./assets/raptor-jaw-tooth.png" });
await PIXI.Assets.load(["spineboyData", "spineboyAtlas", "raptor_jaw"]);
// Create the spine display object
const spineboy = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas", scale: 0.25 });
// Set the default mix time to use when transitioning
// from one animation to the next.
spineboy.state.data.defaultMix = 0.2;
// Center the spine object on screen.
spineboy.x = window.innerWidth / 2;
spineboy.y = window.innerHeight / 2 + spineboy.getBounds().height / 2;
// Set animation "run" on track 0, looped.
spineboy.state.setAnimation(0, "walk", true);
// Add the display object to the stage.
app.stage.addChild(spineboy);
const tooth1 = PIXI.Sprite.from('raptor_jaw');
const tooth2 = PIXI.Sprite.from('raptor_jaw');
const text = new PIXI.Text('Text GUN');
const toothContainer = new PIXI.Container();
toothContainer.addChild(tooth1);
toothContainer.name = "tooth";
text.name = "text";
// putting logo2 on top of the hand using slot directly and remove the attachment hand
let frontFist;
setTimeout(() => {
frontFist = spineboy.skeleton.findSlot("front-foot");
tooth1.x = -10;
tooth1.y = -70;
spineboy.addSlotObject(frontFist, toothContainer);
frontFist.setAttachment(null);
}, 1000);
// scaling the bone, will scale the pixi object too
setTimeout(() => {
frontFist.bone.scaleX = .5;
frontFist.bone.scaleY = .5;
}, 2000);
// adding a pixi text in a slot using slot index
let mouth;
setTimeout(() => {
spineboy.addSlotObject("gun", text);
}, 4000);
// adding one pixi object to an already "occupied" slot will remove the old one,
// and move the given one to the slot
setTimeout(() => {
spineboy.addSlotObject("gun", toothContainer);
}, 5000);
// adding multiple DisplayObjects to a slot using a Container to control their offset, size, ...
setTimeout(() => {
toothContainer.addChild(tooth2);
tooth2.x = 30;
tooth2.y = -70;
tooth2.angle = 30;
tooth2.tint = 0xFF5500;
}, 6000);
// removing the container won't automatically destroy the displayObject contained, so take care of them
setTimeout(() => {
spineboy.removeSlotObject("gun");
console.log(toothContainer.destroyed)
console.log(tooth1.destroyed)
console.log(tooth2.destroyed)
toothContainer.destroy();
tooth1.destroy();
console.log(toothContainer.destroyed)
console.log(tooth1.destroyed)
console.log(tooth2.destroyed)
}, 7000);
// removing a specific slot object, that is not in that slot do nothing
setTimeout(() => {
spineboy.addSlotObject("gun", tooth2);
spineboy.removeSlotObject("gun", text);
}, 8000);
// removing a specific slot object
setTimeout(() => {
spineboy.removeSlotObject("gun", tooth2);
tooth2.destroy();
}, 9000);
// resetting the slot with the original attachment
setTimeout(() => {
frontFist.setToSetupPose();
frontFist.bone.setToSetupPose();
}, 10000);
// showing an animation with clipping -> Pixi masks will be created
// for clipping attachments having slot objects
setTimeout(() => {
spineboy.state.setAnimation(0, "portal", true)
const tooth3 = PIXI.Sprite.from('raptor_jaw');
tooth3.scale.set(2);
tooth3.x = -60;
tooth3.y = 120;
tooth3.angle = 180;
const foot1 = new PIXI.Container();
foot1.addChild(tooth3);
spineboy.addSlotObject("rear-foot", foot1);
}, 11000);
})();
</script>
</body>
</html>

View File

@ -0,0 +1,39 @@
{
"name": "@esotericsoftware/spine-pixi-v8",
"version": "4.2.62",
"description": "The official Spine Runtimes for PixiJS v8.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"type": "module",
"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.62"
},
"peerDependencies": {
"pixi.js": "^8.4.0"
}
}

View File

@ -0,0 +1,138 @@
/** ****************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 { AttachmentCacheData, Spine } from './Spine';
import type { Batch, Batcher, BLEND_MODES, DefaultBatchableMeshElement, Matrix, Texture, Topology } from 'pixi.js';
export class BatchableSpineSlot implements DefaultBatchableMeshElement {
indexOffset = 0;
attributeOffset = 0;
indexSize!: number;
attributeSize!: number;
batcherName = 'darkTint';
topology:Topology = 'triangle-list';
readonly packAsQuad = false;
renderable!: Spine;
positions!: Float32Array;
indices!: number[] | Uint16Array;
uvs!: Float32Array;
roundPixels!: 0 | 1;
data!: AttachmentCacheData;
blendMode!: BLEND_MODES;
darkTint!: number;
texture!: Texture;
transform!: Matrix;
// used internally by batcher specific. Stored for efficient updating.
_textureId!: number;
_attributeStart!: number;
_indexStart!: number;
_batcher!: Batcher;
_batch!: Batch;
get color () {
const slotColor = this.data.color;
const parentColor: number = this.renderable.groupColor;
const parentAlpha: number = this.renderable.groupAlpha;
let abgr: number;
const mixedA = (slotColor.a * parentAlpha) * 255;
if (parentColor !== 0xFFFFFF) {
const parentB = (parentColor >> 16) & 0xFF;
const parentG = (parentColor >> 8) & 0xFF;
const parentR = parentColor & 0xFF;
const mixedR = (slotColor.r * parentR);
const mixedG = (slotColor.g * parentG);
const mixedB = (slotColor.b * parentB);
abgr = ((mixedA) << 24) | (mixedB << 16) | (mixedG << 8) | mixedR;
}
else {
abgr = ((mixedA) << 24) | ((slotColor.b * 255) << 16) | ((slotColor.g * 255) << 8) | (slotColor.r * 255);
}
return abgr;
}
get darkColor () {
const darkColor = this.data.darkColor;
return ((darkColor.b * 255) << 16) | ((darkColor.g * 255) << 8) | (darkColor.r * 255);
}
get groupTransform () { return this.renderable.groupTransform; }
setData (
renderable: Spine,
data: AttachmentCacheData,
blendMode: BLEND_MODES,
roundPixels: 0 | 1) {
this.renderable = renderable;
this.transform = renderable.groupTransform;
this.data = data;
if (data.clipped) {
const clippedData = data.clippedData;
this.indexSize = clippedData!.indicesCount;
this.attributeSize = clippedData!.vertexCount;
this.positions = clippedData!.vertices;
this.indices = clippedData!.indices;
this.uvs = clippedData!.uvs;
}
else {
this.indexSize = data.indices.length;
this.attributeSize = data.vertices.length / 2;
this.positions = data.vertices;
this.indices = data.indices;
this.uvs = data.uvs;
}
this.texture = data.texture;
this.roundPixels = roundPixels;
this.blendMode = blendMode;
this.batcherName = data.darkTint ? 'darkTint' : 'default';
}
}

View File

@ -0,0 +1,912 @@
/** ****************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 {
Assets,
Bounds,
Cache,
Container,
ContainerOptions,
DEG_TO_RAD,
DestroyOptions,
fastCopy,
Graphics,
PointData,
Texture,
Ticker,
ViewContainer,
} from 'pixi.js';
import { ISpineDebugRenderer } from './SpineDebugRenderer';
import {
AnimationState,
AnimationStateData,
AtlasAttachmentLoader,
Attachment,
Bone,
ClippingAttachment,
Color,
MeshAttachment,
Physics,
Pool,
RegionAttachment,
Skeleton,
SkeletonBinary,
SkeletonBounds,
SkeletonClipping,
SkeletonData,
SkeletonJson,
Slot,
type TextureAtlas,
TrackEntry,
Vector2,
} from '@esotericsoftware/spine-core';
/**
* Options to create a {@link Spine} using {@link Spine.from}.
*/
export interface SpineFromOptions {
/** the asset name for the skeleton `.skel` or `.json` file previously loaded into the Assets */
skeleton: string;
/** the asset name for the atlas file previously loaded into the Assets */
atlas: string;
/** The value passed to the skeleton reader. If omitted, 1 is passed. See {@link SkeletonBinary.scale} for details. */
scale?: number;
/** Set the {@link Spine.autoUpdate} value. If omitted, it is set to `true`. */
autoUpdate?: boolean;
/**
* If `true`, use the dark tint renderer to render the skeleton
* If `false`, use the default pixi renderer to render the skeleton
* If `undefined`, use the dark tint renderer if at least one slot has tint black
*/
darkTint?: boolean;
};
const vectorAux = new Vector2();
Skeleton.yDown = true;
const clipper = new SkeletonClipping();
export interface SpineOptions extends ContainerOptions {
/** the {@link SkeletonData} used to instantiate the skeleton */
skeletonData: SkeletonData;
/** See {@link SpineFromOptions.autoUpdate}. */
autoUpdate?: boolean;
/** See {@link SpineFromOptions.darkTint}. */
darkTint?: boolean;
}
/**
* AnimationStateListener {@link https://en.esotericsoftware.com/spine-api-reference#AnimationStateListener events} exposed for Pixi.
*/
export interface SpineEvents {
complete: [trackEntry: TrackEntry];
dispose: [trackEntry: TrackEntry];
end: [trackEntry: TrackEntry];
event: [trackEntry: TrackEntry, event: Event];
interrupt: [trackEntry: TrackEntry];
start: [trackEntry: TrackEntry];
}
export interface AttachmentCacheData {
id: string;
clipped: boolean;
vertices: Float32Array;
uvs: Float32Array;
indices: number[];
color: Color;
darkColor: Color;
darkTint: boolean;
skipRender: boolean;
texture: Texture;
clippedData?: {
vertices: Float32Array;
uvs: Float32Array;
indices: Uint16Array;
vertexCount: number;
indicesCount: number;
};
}
interface SlotsToClipping {
slot: Slot,
mask?: Graphics,
maskComputed?: boolean,
vertices: Array<number>,
};
const maskPool = new Pool<Graphics>(() => new Graphics);
/**
* The class to instantiate a {@link Spine} game object in Pixi.
* The static method {@link Spine.from} should be used to instantiate a Spine game object.
*/
export class Spine extends ViewContainer {
// Pixi properties
public batched = true;
public buildId = 0;
public override readonly renderPipeId = 'spine';
public _didSpineUpdate = false;
public beforeUpdateWorldTransforms: (object: Spine) => void = () => { /** */ };
public afterUpdateWorldTransforms: (object: Spine) => void = () => { /** */ };
// Spine properties
/** The skeleton for this Spine game object. */
public skeleton: Skeleton;
/** The animation state for this Spine game object. */
public state: AnimationState;
public skeletonBounds?: SkeletonBounds;
private darkTint = false;
private _debug?: ISpineDebugRenderer | undefined = undefined;
readonly _slotsObject: Record<string, { slot: Slot, container: Container } | null> = Object.create(null);
private clippingSlotToPixiMasks: Record<string, SlotsToClipping> = Object.create(null);
private getSlotFromRef (slotRef: number | string | Slot): Slot {
let slot: Slot | null;
if (typeof slotRef === 'number') slot = this.skeleton.slots[slotRef];
else if (typeof slotRef === 'string') slot = this.skeleton.findSlot(slotRef);
else slot = slotRef;
if (!slot) throw new Error(`No slot found with the given slot reference: ${slotRef}`);
return slot;
}
public spineAttachmentsDirty = true;
public spineTexturesDirty = true;
private _lastAttachments: Attachment[] = [];
private _stateChanged = true;
private attachmentCacheData: Record<string, AttachmentCacheData>[] = [];
public get debug (): ISpineDebugRenderer | undefined {
return this._debug;
}
/** Pass a {@link SpineDebugRenderer} or create your own {@link ISpineDebugRenderer} to render bones, meshes, ...
* @example spineGO.debug = new SpineDebugRenderer();
*/
public set debug (value: ISpineDebugRenderer | undefined) {
if (this._debug) {
this._debug.unregisterSpine(this);
}
if (value) {
value.registerSpine(this);
}
this._debug = value;
}
private autoUpdateWarned = false;
private _autoUpdate = true;
public get autoUpdate (): boolean {
return this._autoUpdate;
}
/** When `true`, the Spine AnimationState and the Skeleton will be automatically updated using the {@link Ticker.shared} instance. */
public set autoUpdate (value: boolean) {
if (value) {
Ticker.shared.add(this.internalUpdate, this);
this.autoUpdateWarned = false;
}
else {
Ticker.shared.remove(this.internalUpdate, this);
}
this._autoUpdate = value;
}
constructor (options: SpineOptions | SkeletonData) {
if (options instanceof SkeletonData) {
options = {
skeletonData: options,
};
}
super();
const skeletonData = options instanceof SkeletonData ? options : options.skeletonData;
this.skeleton = new Skeleton(skeletonData);
this.state = new AnimationState(new AnimationStateData(skeletonData));
this.autoUpdate = options?.autoUpdate ?? true;
// dark tint can be enabled by options, otherwise is enable if at least one slot has tint black
this.darkTint = options?.darkTint === undefined
? this.skeleton.slots.some(slot => !!slot.data.darkColor)
: options?.darkTint;
const slots = this.skeleton.slots;
for (let i = 0; i < slots.length; i++) {
this.attachmentCacheData[i] = Object.create(null);
}
this._updateState(0);
}
/** If {@link Spine.autoUpdate} is `false`, this method allows to update the AnimationState and the Skeleton with the given delta. */
public update (dt: number): void {
if (this.autoUpdate && !this.autoUpdateWarned) {
console.warn('You are calling update on a Spine instance that has autoUpdate set to true. This is probably not what you want.');
this.autoUpdateWarned = true;
}
this.internalUpdate(0, dt);
}
protected internalUpdate (_deltaFrame: any, deltaSeconds?: number): void {
// Because reasons, pixi uses deltaFrames at 60fps.
// We ignore the default deltaFrames and use the deltaSeconds from pixi ticker.
this._updateState(deltaSeconds ?? Ticker.shared.deltaMS / 1000);
}
get bounds () {
if (this._boundsDirty) {
this.updateBounds();
}
return this._bounds;
}
/**
* Set the position of the bone given in input through a {@link IPointData}.
* @param bone: the bone name or the bone instance to set the position
* @param outPos: the new position of the bone.
* @throws {Error}: if the given bone is not found in the skeleton, an error is thrown
*/
public setBonePosition (bone: string | Bone, position: PointData): void {
const boneAux = bone;
if (typeof bone === 'string') {
bone = this.skeleton.findBone(bone) as Bone;
}
if (!bone) throw Error(`Cant set bone position, bone ${String(boneAux)} not found`);
vectorAux.set(position.x, position.y);
if (bone.parent) {
const aux = bone.parent.worldToLocal(vectorAux);
bone.x = aux.x;
bone.y = -aux.y;
}
else {
bone.x = vectorAux.x;
bone.y = vectorAux.y;
}
}
/**
* Return the position of the bone given in input into an {@link IPointData}.
* @param bone: the bone name or the bone instance to get the position from
* @param outPos: an optional {@link IPointData} to use to return the bone position, rathern than instantiating a new object.
* @returns {IPointData | undefined}: the position of the bone, or undefined if no matching bone is found in the skeleton
*/
public getBonePosition (bone: string | Bone, outPos?: PointData): PointData | undefined {
const boneAux = bone;
if (typeof bone === 'string') {
bone = this.skeleton.findBone(bone) as Bone;
}
if (!bone) {
console.error(`Cant set bone position! Bone ${String(boneAux)} not found`);
return outPos;
}
if (!outPos) {
outPos = { x: 0, y: 0 };
}
outPos.x = bone.worldX;
outPos.y = bone.worldY;
return outPos;
}
/**
* Will update the state based on the specified time, this will not apply the state to the skeleton
* as this is differed until the `applyState` method is called.
*
* @param time the time at which to set the state
* @internal
*/
_updateState (time: number) {
this.state.update(time);
this.skeleton.update(time);
this._stateChanged = true;
this._boundsDirty = true;
this.onViewUpdate();
}
/**
* Applies the state to this spine instance.
* - updates the state to the skeleton
* - updates its world transform (spine world transform)
* - validates the attachments - to flag if the attachments have changed this state
* - transforms the attachments - to update the vertices of the attachments based on the new positions
* - update the slot attachments - to update the position, rotation, scale, and visibility of the attached containers
* @internal
*/
_applyState () {
if (!this._stateChanged) return;
this._stateChanged = false;
const { skeleton } = this;
this.state.apply(skeleton);
this.beforeUpdateWorldTransforms(this);
skeleton.updateWorldTransform(Physics.update);
this.afterUpdateWorldTransforms(this);
this.validateAttachments();
this.transformAttachments();
this.updateSlotObjects();
}
private validateAttachments () {
const currentDrawOrder = this.skeleton.drawOrder;
const lastAttachments = this._lastAttachments;
let index = 0;
let spineAttachmentsDirty = false;
for (let i = 0; i < currentDrawOrder.length; i++) {
const slot = currentDrawOrder[i];
const attachment = slot.getAttachment();
if (attachment) {
if (attachment !== lastAttachments[index]) {
spineAttachmentsDirty = true;
lastAttachments[index] = attachment;
}
index++;
}
}
if (index !== lastAttachments.length) {
spineAttachmentsDirty = true;
lastAttachments.length = index;
}
this.spineAttachmentsDirty = spineAttachmentsDirty;
}
private updateAndSetPixiMask (slot: Slot, last: boolean) {
// assign/create the currentClippingSlot
const attachment = slot.attachment;
if (attachment && attachment instanceof ClippingAttachment) {
const clip = (this.clippingSlotToPixiMasks[slot.data.name] ||= { slot, vertices: new Array<number>() });
clip.maskComputed = false;
this.currentClippingSlot = this.clippingSlotToPixiMasks[slot.data.name];
return;
}
// assign the currentClippingSlot mask to the slot object
let currentClippingSlot = this.currentClippingSlot;
let slotObject = this._slotsObject[slot.data.name];
if (currentClippingSlot && slotObject) {
let slotClipping = currentClippingSlot.slot;
let clippingAttachment = slotClipping.attachment as ClippingAttachment;
// create the pixi mask, only the first time and if the clipped slot is the first one clipped by this currentClippingSlot
let mask = currentClippingSlot.mask as Graphics;
if (!mask) {
mask = maskPool.obtain();
currentClippingSlot.mask = mask;
this.addChild(mask);
}
// compute the pixi mask polygon, if the clipped slot is the first one clipped by this currentClippingSlot
if (!currentClippingSlot.maskComputed) {
currentClippingSlot.maskComputed = true;
const worldVerticesLength = clippingAttachment.worldVerticesLength;
const vertices = currentClippingSlot.vertices;
clippingAttachment.computeWorldVertices(slotClipping, 0, worldVerticesLength, vertices, 0, 2);
mask.clear().poly(vertices).stroke({ width: 0 }).fill({ alpha: .25 });
}
slotObject.container.mask = mask;
} else if (slotObject?.container.mask) {
// remove the mask, if slot object has a mask, but currentClippingSlot is undefined
slotObject.container.mask = null;
}
// if current slot is the ending one of the currentClippingSlot mask, set currentClippingSlot to undefined
if (currentClippingSlot && (currentClippingSlot.slot.attachment as ClippingAttachment).endSlot == slot.data) {
this.currentClippingSlot = undefined;
}
// clean up unused masks
if (last) {
for (const key in this.clippingSlotToPixiMasks) {
const clippingSlotToPixiMask = this.clippingSlotToPixiMasks[key];
if ((!(clippingSlotToPixiMask.slot.attachment instanceof ClippingAttachment) || !clippingSlotToPixiMask.maskComputed) && clippingSlotToPixiMask.mask) {
this.removeChild(clippingSlotToPixiMask.mask);
maskPool.free(clippingSlotToPixiMask.mask);
clippingSlotToPixiMask.mask = undefined;
}
}
}
}
private currentClippingSlot: SlotsToClipping | undefined;
private transformAttachments () {
const currentDrawOrder = this.skeleton.drawOrder;
for (let i = 0; i < currentDrawOrder.length; i++) {
const slot = currentDrawOrder[i];
this.updateAndSetPixiMask(slot, i === currentDrawOrder.length - 1);
const attachment = slot.getAttachment();
if (attachment) {
if (attachment instanceof MeshAttachment || attachment instanceof RegionAttachment) {
const cacheData = this._getCachedData(slot, attachment);
if (attachment instanceof RegionAttachment) {
attachment.computeWorldVertices(slot, cacheData.vertices, 0, 2);
}
else {
attachment.computeWorldVertices(
slot,
0,
attachment.worldVerticesLength,
cacheData.vertices,
0,
2,
);
}
fastCopy((attachment.uvs as Float32Array).buffer, cacheData.uvs.buffer);
const skeleton = slot.bone.skeleton;
const skeletonColor = skeleton.color;
const slotColor = slot.color;
const attachmentColor = attachment.color;
cacheData.color.set(
skeletonColor.r * slotColor.r * attachmentColor.r,
skeletonColor.g * slotColor.g * attachmentColor.g,
skeletonColor.b * slotColor.b * attachmentColor.b,
skeletonColor.a * slotColor.a * attachmentColor.a,
);
if (slot.darkColor) {
cacheData.darkColor.setFromColor(slot.darkColor);
}
cacheData.skipRender = cacheData.clipped = false;
const texture = attachment.region?.texture.texture || Texture.EMPTY;
if (cacheData.texture !== texture) {
cacheData.texture = texture;
this.spineTexturesDirty = true;
}
if (clipper.isClipping()) {
this.updateClippingData(cacheData);
}
}
else if (attachment instanceof ClippingAttachment) {
clipper.clipStart(slot, attachment);
continue;
}
}
clipper.clipEndWithSlot(slot);
}
clipper.clipEnd();
}
private updateClippingData (cacheData: AttachmentCacheData) {
cacheData.clipped = true;
clipper.clipTrianglesUnpacked(
cacheData.vertices,
cacheData.indices,
cacheData.indices.length,
cacheData.uvs,
);
const { clippedVertices, clippedUVs, clippedTriangles } = clipper;
const verticesCount = clippedVertices.length / 2;
const indicesCount = clippedTriangles.length;
if (!cacheData.clippedData) {
cacheData.clippedData = {
vertices: new Float32Array(verticesCount * 2),
uvs: new Float32Array(verticesCount * 2),
vertexCount: verticesCount,
indices: new Uint16Array(indicesCount),
indicesCount,
};
this.spineAttachmentsDirty = true;
}
const clippedData = cacheData.clippedData;
const sizeChange = clippedData.vertexCount !== verticesCount || indicesCount !== clippedData.indicesCount;
cacheData.skipRender = verticesCount === 0;
if (sizeChange) {
this.spineAttachmentsDirty = true;
if (clippedData.vertexCount < verticesCount) {
// buffer reuse!
clippedData.vertices = new Float32Array(verticesCount * 2);
clippedData.uvs = new Float32Array(verticesCount * 2);
}
if (clippedData.indices.length < indicesCount) {
clippedData.indices = new Uint16Array(indicesCount);
}
}
const { vertices, uvs, indices } = clippedData;
for (let i = 0; i < verticesCount; i++) {
vertices[i * 2] = clippedVertices[i * 2];
vertices[(i * 2) + 1] = clippedVertices[(i * 2) + 1];
uvs[i * 2] = clippedUVs[(i * 2)];
uvs[(i * 2) + 1] = clippedUVs[(i * 2) + 1];
}
clippedData.vertexCount = verticesCount;
for (let i = 0; i < indicesCount; i++) {
if (indices[i] !== clippedTriangles[i]) {
this.spineAttachmentsDirty = true;
indices[i] = clippedTriangles[i];
}
}
clippedData.indicesCount = indicesCount;
}
/**
* ensure that attached containers map correctly to their slots
* along with their position, rotation, scale, and visibility.
*/
private updateSlotObjects () {
for (const i in this._slotsObject) {
const slotAttachment = this._slotsObject[i];
if (!slotAttachment) continue;
this.updateSlotObject(slotAttachment);
}
}
private updateSlotObject (slotAttachment: { slot: Slot, container: Container }) {
const { slot, container } = slotAttachment;
container.visible = this.skeleton.drawOrder.includes(slot);
if (container.visible) {
const bone = slot.bone;
container.position.set(bone.worldX, bone.worldY);
container.scale.x = bone.getWorldScaleX();
container.scale.y = bone.getWorldScaleY();
container.rotation = bone.getWorldRotationX() * DEG_TO_RAD;
container.alpha = this.skeleton.color.a * slot.color.a;
}
}
/** @internal */
_getCachedData (slot: Slot, attachment: RegionAttachment | MeshAttachment): AttachmentCacheData {
return this.attachmentCacheData[slot.data.index][attachment.name] || this.initCachedData(slot, attachment);
}
private initCachedData (slot: Slot, attachment: RegionAttachment | MeshAttachment): AttachmentCacheData {
let vertices: Float32Array;
if (attachment instanceof RegionAttachment) {
vertices = new Float32Array(8);
this.attachmentCacheData[slot.data.index][attachment.name] = {
id: `${slot.data.index}-${attachment.name}`,
vertices,
clipped: false,
indices: [0, 1, 2, 0, 2, 3],
uvs: new Float32Array(attachment.uvs.length),
color: new Color(1, 1, 1, 1),
darkColor: new Color(0, 0, 0, 0),
darkTint: this.darkTint,
skipRender: false,
texture: attachment.region?.texture.texture,
};
}
else {
vertices = new Float32Array(attachment.worldVerticesLength);
this.attachmentCacheData[slot.data.index][attachment.name] = {
id: `${slot.data.index}-${attachment.name}`,
vertices,
clipped: false,
indices: attachment.triangles,
uvs: new Float32Array(attachment.uvs.length),
color: new Color(1, 1, 1, 1),
darkColor: new Color(0, 0, 0, 0),
darkTint: this.darkTint,
skipRender: false,
texture: attachment.region?.texture.texture,
};
}
return this.attachmentCacheData[slot.data.index][attachment.name];
}
protected onViewUpdate () {
// increment from the 12th bit!
this._didChangeId += 1 << 12;
this._boundsDirty = true;
if (this.didViewUpdate) return;
this.didViewUpdate = true;
const renderGroup = this.renderGroup || this.parentRenderGroup;
if (renderGroup) {
renderGroup.onChildViewUpdate(this);
}
this.debug?.renderDebug(this);
}
/**
* Attaches a PixiJS container to a specified slot. This will map the world transform of the slots bone
* to the attached container. A container can only be attached to one slot at a time.
*
* @param container - The container to attach to the slot
* @param slotRef - The slot id or slot to attach to
*/
public addSlotObject (slot: number | string | Slot, container: Container) {
slot = this.getSlotFromRef(slot);
// need to check in on the container too...
for (const i in this._slotsObject) {
if (this._slotsObject[i]?.container === container) {
this.removeSlotObject(this._slotsObject[i].slot);
}
}
this.removeSlotObject(slot);
container.includeInBuild = false;
// TODO only add once??
this.addChild(container);
const slotObject = { container, slot };
this._slotsObject[slot.data.name] = slotObject;
this.updateSlotObject(slotObject);
}
/**
* Removes a PixiJS container from the slot it is attached to.
*
* @param container - The container to detach from the slot
* @param slotOrContainer - The container, slot id or slot to detach from
*/
public removeSlotObject (slotOrContainer: number | string | Slot | Container) {
let containerToRemove: Container | undefined;
if (slotOrContainer instanceof Container) {
for (const i in this._slotsObject) {
if (this._slotsObject[i]?.container === slotOrContainer) {
this._slotsObject[i] = null;
containerToRemove = slotOrContainer;
break;
}
}
}
else {
const slot = this.getSlotFromRef(slotOrContainer);
containerToRemove = this._slotsObject[slot.data.name]?.container;
this._slotsObject[slot.data.name] = null;
}
if (containerToRemove) {
this.removeChild(containerToRemove);
containerToRemove.includeInBuild = true;
}
}
/**
* Returns a container attached to a slot, or undefined if no container is attached.
*
* @param slotRef - The slot id or slot to get the attachment from
* @returns - The container attached to the slot
*/
public getSlotObject (slot: number | string | Slot) {
slot = this.getSlotFromRef(slot);
return this._slotsObject[slot.data.name]?.container;
}
private updateBounds () {
this._boundsDirty = false;
this.skeletonBounds ||= new SkeletonBounds();
const skeletonBounds = this.skeletonBounds;
skeletonBounds.update(this.skeleton, true);
if (skeletonBounds.minX === Infinity) {
this._applyState();
const drawOrder = this.skeleton.drawOrder;
const bounds = this._bounds;
bounds.clear();
for (let i = 0; i < drawOrder.length; i++) {
const slot = drawOrder[i];
const attachment = slot.getAttachment();
if (attachment && (attachment instanceof RegionAttachment || attachment instanceof MeshAttachment)) {
const cacheData = this._getCachedData(slot, attachment);
bounds.addVertexData(cacheData.vertices, 0, cacheData.vertices.length);
}
}
}
else {
this._bounds.minX = skeletonBounds.minX;
this._bounds.minY = skeletonBounds.minY;
this._bounds.maxX = skeletonBounds.maxX;
this._bounds.maxY = skeletonBounds.maxY;
}
}
/** @internal */
addBounds (bounds: Bounds) {
bounds.addBounds(this.bounds);
}
/**
* Destroys this sprite renderable and optionally its texture.
* @param options - Options parameter. A boolean will act as if all options
* have been set to that value
* @param {boolean} [options.texture=false] - Should it destroy the current texture of the renderable as well
* @param {boolean} [options.textureSource=false] - Should it destroy the textureSource of the renderable as well
*/
public override destroy (options: DestroyOptions = false) {
super.destroy(options);
Ticker.shared.remove(this.internalUpdate, this);
this.state.clearListeners();
this.debug = undefined;
this.skeleton = null as any;
this.state = null as any;
(this._slotsObject as any) = null;
this._lastAttachments.length = 0;
this.attachmentCacheData = null as any;
}
/** Converts a point from the skeleton coordinate system to the Pixi world coordinate system. */
public skeletonToPixiWorldCoordinates (point: { x: number; y: number }) {
this.worldTransform.apply(point, point);
}
/** Converts a point from the Pixi world coordinate system to the skeleton coordinate system. */
public pixiWorldCoordinatesToSkeleton (point: { x: number; y: number }) {
this.worldTransform.applyInverse(point, point);
}
/** Converts a point from the Pixi world coordinate system to the bone's local coordinate system. */
public pixiWorldCoordinatesToBone (point: { x: number; y: number }, bone: Bone) {
this.pixiWorldCoordinatesToSkeleton(point);
if (bone.parent) {
bone.parent.worldToLocal(point as Vector2);
}
else {
bone.worldToLocal(point as Vector2);
}
}
/**
* Use this method to instantiate a Spine game object.
* Before instantiating a Spine game object, the skeleton (`.skel` or `.json`) and the atlas text files must be loaded into the Assets. For example:
* ```
* PIXI.Assets.add("sackData", "./assets/sack-pro.skel");
* PIXI.Assets.add("sackAtlas", "./assets/sack-pma.atlas");
* await PIXI.Assets.load(["sackData", "sackAtlas"]);
* ```
* Once a Spine game object is created, its skeleton data is cached into {@link Cache} using the key:
* `${skeletonAssetName}-${atlasAssetName}-${options?.scale ?? 1}`
*
* @param options - Options to configure the Spine game object. See {@link SpineFromOptions}
* @returns {Spine} The Spine game object instantiated
*/
static from ({ skeleton, atlas, scale = 1, darkTint, autoUpdate = true }: SpineFromOptions) {
const cacheKey = `${skeleton}-${atlas}-${scale}`;
if (Cache.has(cacheKey)) {
return new Spine(Cache.get<SkeletonData>(cacheKey));
}
const skeletonAsset = Assets.get<any | Uint8Array>(skeleton);
const atlasAsset = Assets.get<TextureAtlas>(atlas);
const attachmentLoader = new AtlasAttachmentLoader(atlasAsset);
const parser = skeletonAsset instanceof Uint8Array
? new SkeletonBinary(attachmentLoader)
: new SkeletonJson(attachmentLoader);
parser.scale = scale;
const skeletonData = parser.readSkeletonData(skeletonAsset);
Cache.set(cacheKey, skeletonData);
return new Spine({
skeletonData,
darkTint,
autoUpdate,
});
}
}

View File

@ -0,0 +1,615 @@
/** ****************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 { Container, Graphics, Text } from 'pixi.js';
import { Spine } from './Spine';
import {
ClippingAttachment,
MeshAttachment,
PathAttachment,
RegionAttachment,
SkeletonBounds
} from '@esotericsoftware/spine-core';
import type { AnimationStateListener } from '@esotericsoftware/spine-core';
/**
* Make a class that extends from this interface to create your own debug renderer.
* @public
*/
export interface ISpineDebugRenderer {
/**
* This will be called every frame, after the spine has been updated.
*/
renderDebug: (spine: Spine) => void;
/**
* This is called when the `spine.debug` object is set to null or when the spine is destroyed.
*/
unregisterSpine: (spine: Spine) => void;
/**
* This is called when the `spine.debug` object is set to a new instance of a debug renderer.
*/
registerSpine: (spine: Spine) => void;
}
type DebugDisplayObjects = {
bones: Container;
skeletonXY: Graphics;
regionAttachmentsShape: Graphics;
meshTrianglesLine: Graphics;
meshHullLine: Graphics;
clippingPolygon: Graphics;
boundingBoxesRect: Graphics;
boundingBoxesCircle: Graphics;
boundingBoxesPolygon: Graphics;
pathsCurve: Graphics;
pathsLine: Graphics;
parentDebugContainer: Container;
eventText: Container;
eventCallback: AnimationStateListener;
};
/**
* This is a debug renderer that uses PixiJS Graphics under the hood.
* @public
*/
export class SpineDebugRenderer implements ISpineDebugRenderer {
private readonly registeredSpines: Map<Spine, DebugDisplayObjects> = new Map();
public drawMeshHull = true;
public drawMeshTriangles = true;
public drawBones = true;
public drawPaths = true;
public drawBoundingBoxes = true;
public drawClipping = true;
public drawRegionAttachments = true;
public drawEvents = true;
public lineWidth = 1;
public regionAttachmentsColor = 0x0078ff;
public meshHullColor = 0x0078ff;
public meshTrianglesColor = 0xffcc00;
public clippingPolygonColor = 0xff00ff;
public boundingBoxesRectColor = 0x00ff00;
public boundingBoxesPolygonColor = 0x00ff00;
public boundingBoxesCircleColor = 0x00ff00;
public pathsCurveColor = 0xff0000;
public pathsLineColor = 0xff00ff;
public skeletonXYColor = 0xff0000;
public bonesColor = 0x00eecc;
public eventFontSize = 24;
public eventFontColor = 0x0;
/**
* The debug is attached by force to each spine object.
* So we need to create it inside the spine when we get the first update
*/
public registerSpine (spine: Spine): void {
if (this.registeredSpines.has(spine)) {
console.warn('SpineDebugRenderer.registerSpine() - this spine is already registered!', spine);
return;
}
const debugDisplayObjects: DebugDisplayObjects = {
parentDebugContainer: new Container(),
bones: new Container(),
skeletonXY: new Graphics(),
regionAttachmentsShape: new Graphics(),
meshTrianglesLine: new Graphics(),
meshHullLine: new Graphics(),
clippingPolygon: new Graphics(),
boundingBoxesRect: new Graphics(),
boundingBoxesCircle: new Graphics(),
boundingBoxesPolygon: new Graphics(),
pathsCurve: new Graphics(),
pathsLine: new Graphics(),
eventText: new Container(),
eventCallback: {
event: (_, event) => {
if (this.drawEvents) {
const scale = Math.abs(spine.scale.x || spine.scale.y || 1);
const text = new Text({
text: event.data.name,
style: {
fontSize: this.eventFontSize / scale,
fill: this.eventFontColor,
fontFamily: 'monospace'
}
});
text.scale.x = Math.sign(spine.scale.x);
text.anchor.set(0.5);
debugDisplayObjects.eventText.addChild(text);
setTimeout(() => {
if (!text.destroyed) {
text.destroy();
}
}, 250);
}
},
},
};
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.bones);
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.skeletonXY);
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.regionAttachmentsShape);
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.meshTrianglesLine);
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.meshHullLine);
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.clippingPolygon);
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.boundingBoxesRect);
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.boundingBoxesCircle);
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.boundingBoxesPolygon);
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.pathsCurve);
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.pathsLine);
debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.eventText);
(debugDisplayObjects.parentDebugContainer as any).zIndex = 9999999;
// Disable screen reader and mouse input on debug objects.
(debugDisplayObjects.parentDebugContainer as any).accessibleChildren = false;
(debugDisplayObjects.parentDebugContainer as any).eventMode = 'none';
(debugDisplayObjects.parentDebugContainer as any).interactiveChildren = false;
spine.addChild(debugDisplayObjects.parentDebugContainer);
spine.state.addListener(debugDisplayObjects.eventCallback);
this.registeredSpines.set(spine, debugDisplayObjects);
}
public renderDebug (spine: Spine): void {
if (!this.registeredSpines.has(spine)) {
// This should never happen. Spines are registered when you assign spine.debug
this.registerSpine(spine);
}
const debugDisplayObjects = this.registeredSpines.get(spine);
if (!debugDisplayObjects) {
return;
}
spine.addChild(debugDisplayObjects.parentDebugContainer);
debugDisplayObjects.skeletonXY.clear();
debugDisplayObjects.regionAttachmentsShape.clear();
debugDisplayObjects.meshTrianglesLine.clear();
debugDisplayObjects.meshHullLine.clear();
debugDisplayObjects.clippingPolygon.clear();
debugDisplayObjects.boundingBoxesRect.clear();
debugDisplayObjects.boundingBoxesCircle.clear();
debugDisplayObjects.boundingBoxesPolygon.clear();
debugDisplayObjects.pathsCurve.clear();
debugDisplayObjects.pathsLine.clear();
for (let len = debugDisplayObjects.bones.children.length; len > 0; len--) {
debugDisplayObjects.bones.children[len - 1].destroy({ children: true, texture: true, textureSource: true });
}
const scale = Math.abs(spine.scale.x || spine.scale.y || 1);
const lineWidth = this.lineWidth / scale;
if (this.drawBones) {
this.drawBonesFunc(spine, debugDisplayObjects, lineWidth, scale);
}
if (this.drawPaths) {
this.drawPathsFunc(spine, debugDisplayObjects, lineWidth);
}
if (this.drawBoundingBoxes) {
this.drawBoundingBoxesFunc(spine, debugDisplayObjects, lineWidth);
}
if (this.drawClipping) {
this.drawClippingFunc(spine, debugDisplayObjects, lineWidth);
}
if (this.drawMeshHull || this.drawMeshTriangles) {
this.drawMeshHullAndMeshTriangles(spine, debugDisplayObjects, lineWidth);
}
if (this.drawRegionAttachments) {
this.drawRegionAttachmentsFunc(spine, debugDisplayObjects, lineWidth);
}
if (this.drawEvents) {
for (const child of debugDisplayObjects.eventText.children) {
child.alpha -= 0.05;
child.y -= 2;
}
}
}
private drawBonesFunc (spine: Spine, debugDisplayObjects: DebugDisplayObjects, lineWidth: number, scale: number): void {
const skeleton = spine.skeleton;
const skeletonX = skeleton.x;
const skeletonY = skeleton.y;
const bones = skeleton.bones;
debugDisplayObjects.skeletonXY.strokeStyle = { width: lineWidth, color: this.skeletonXYColor };
for (let i = 0, len = bones.length; i < len; i++) {
const bone = bones[i];
const boneLen = bone.data.length;
const starX = skeletonX + bone.worldX;
const starY = skeletonY + bone.worldY;
const endX = skeletonX + (boneLen * bone.a) + bone.worldX;
const endY = skeletonY + (boneLen * bone.b) + bone.worldY;
if (bone.data.name === 'root' || bone.data.parent === null) {
continue;
}
const w = Math.abs(starX - endX);
const h = Math.abs(starY - endY);
// a = w, // side length a
const a2 = Math.pow(w, 2); // square root of side length a
const b = h; // side length b
const b2 = Math.pow(h, 2); // square root of side length b
const c = Math.sqrt(a2 + b2); // side length c
const c2 = Math.pow(c, 2); // square root of side length c
const rad = Math.PI / 180;
// A = Math.acos([a2 + c2 - b2] / [2 * a * c]) || 0, // Angle A
// C = Math.acos([a2 + b2 - c2] / [2 * a * b]) || 0, // C angle
const B = Math.acos((c2 + b2 - a2) / (2 * b * c)) || 0; // angle of corner B
if (c === 0) {
continue;
}
const gp = new Graphics();
debugDisplayObjects.bones.addChild(gp);
// draw bone
const refRation = c / 50 / scale;
gp.context
.poly([0, 0, 0 - refRation, c - (refRation * 3), 0, c - refRation, 0 + refRation, c - (refRation * 3)])
.fill(this.bonesColor);
gp.x = starX;
gp.y = starY;
gp.pivot.y = c;
// Calculate bone rotation angle
let rotation = 0;
if (starX < endX && starY < endY) {
// bottom right
rotation = -B + (180 * rad);
}
else if (starX > endX && starY < endY) {
// bottom left
rotation = (180 * rad) + B;
}
else if (starX > endX && starY > endY) {
// top left
rotation = -B;
}
else if (starX < endX && starY > endY) {
// bottom left
rotation = B;
}
else if (starY === endY && starX < endX) {
// To the right
rotation = 90 * rad;
}
else if (starY === endY && starX > endX) {
// go left
rotation = -90 * rad;
}
else if (starX === endX && starY < endY) {
// down
rotation = 180 * rad;
}
else if (starX === endX && starY > endY) {
// up
rotation = 0;
}
gp.rotation = rotation;
// Draw the starting rotation point of the bone
gp.circle(0, c, refRation * 1.2)
.fill({ color: 0x000000, alpha: 0.6 })
.stroke({ width: lineWidth + refRation / 2.4, color: this.bonesColor });
}
// Draw the skeleton starting point "X" form
const startDotSize = lineWidth * 3;
debugDisplayObjects.skeletonXY.context
.moveTo(skeletonX - startDotSize, skeletonY - startDotSize)
.lineTo(skeletonX + startDotSize, skeletonY + startDotSize)
.moveTo(skeletonX + startDotSize, skeletonY - startDotSize)
.lineTo(skeletonX - startDotSize, skeletonY + startDotSize)
.stroke();
}
private drawRegionAttachmentsFunc (spine: Spine, debugDisplayObjects: DebugDisplayObjects, lineWidth: number): void {
const skeleton = spine.skeleton;
const slots = skeleton.slots;
for (let i = 0, len = slots.length; i < len; i++) {
const slot = slots[i];
const attachment = slot.getAttachment();
if (attachment === null || !(attachment instanceof RegionAttachment)) {
continue;
}
const regionAttachment = attachment;
const vertices = new Float32Array(8);
regionAttachment.computeWorldVertices(slot, vertices, 0, 2);
debugDisplayObjects.regionAttachmentsShape.poly(Array.from(vertices.slice(0, 8)));
}
debugDisplayObjects.regionAttachmentsShape.stroke({
color: this.regionAttachmentsColor,
width: lineWidth
});
}
private drawMeshHullAndMeshTriangles (spine: Spine, debugDisplayObjects: DebugDisplayObjects, lineWidth: number): void {
const skeleton = spine.skeleton;
const slots = skeleton.slots;
for (let i = 0, len = slots.length; i < len; i++) {
const slot = slots[i];
if (!slot.bone.active) {
continue;
}
const attachment = slot.getAttachment();
if (attachment === null || !(attachment instanceof MeshAttachment)) {
continue;
}
const meshAttachment = attachment;
const vertices = new Float32Array(meshAttachment.worldVerticesLength);
const triangles = meshAttachment.triangles;
let hullLength = meshAttachment.hullLength;
meshAttachment.computeWorldVertices(slot, 0, meshAttachment.worldVerticesLength, vertices, 0, 2);
// draw the skinned mesh (triangle)
if (this.drawMeshTriangles) {
for (let i = 0, len = triangles.length; i < len; i += 3) {
const v1 = triangles[i] * 2;
const v2 = triangles[i + 1] * 2;
const v3 = triangles[i + 2] * 2;
debugDisplayObjects.meshTrianglesLine.context
.moveTo(vertices[v1], vertices[v1 + 1])
.lineTo(vertices[v2], vertices[v2 + 1])
.lineTo(vertices[v3], vertices[v3 + 1]);
}
}
// draw skin border
if (this.drawMeshHull && hullLength > 0) {
hullLength = (hullLength >> 1) * 2;
let lastX = vertices[hullLength - 2];
let lastY = vertices[hullLength - 1];
for (let i = 0, len = hullLength; i < len; i += 2) {
const x = vertices[i];
const y = vertices[i + 1];
debugDisplayObjects.meshHullLine.context
.moveTo(x, y)
.lineTo(lastX, lastY);
lastX = x;
lastY = y;
}
}
}
debugDisplayObjects.meshHullLine.stroke({ width: lineWidth, color: this.meshHullColor });
debugDisplayObjects.meshTrianglesLine.stroke({ width: lineWidth, color: this.meshTrianglesColor });
}
drawClippingFunc (spine: Spine, debugDisplayObjects: DebugDisplayObjects, lineWidth: number): void {
const skeleton = spine.skeleton;
const slots = skeleton.slots;
for (let i = 0, len = slots.length; i < len; i++) {
const slot = slots[i];
if (!slot.bone.active) {
continue;
}
const attachment = slot.getAttachment();
if (attachment === null || !(attachment instanceof ClippingAttachment)) {
continue;
}
const clippingAttachment = attachment;
const nn = clippingAttachment.worldVerticesLength;
const world = new Float32Array(nn);
clippingAttachment.computeWorldVertices(slot, 0, nn, world, 0, 2);
debugDisplayObjects.clippingPolygon.poly(Array.from(world));
}
debugDisplayObjects.clippingPolygon.stroke({
width: lineWidth, color: this.clippingPolygonColor, alpha: 1
});
}
drawBoundingBoxesFunc (spine: Spine, debugDisplayObjects: DebugDisplayObjects, lineWidth: number): void {
// draw the total outline of the bounding box
const bounds = new SkeletonBounds();
bounds.update(spine.skeleton, true);
if (bounds.minX !== Infinity) {
debugDisplayObjects.boundingBoxesRect
.rect(bounds.minX, bounds.minY, bounds.getWidth(), bounds.getHeight())
.stroke({ width: lineWidth, color: this.boundingBoxesRectColor });
}
const polygons = bounds.polygons;
const drawPolygon = (polygonVertices: ArrayLike<number>, _offset: unknown, count: number): void => {
if (count < 3) {
throw new Error('Polygon must contain at least 3 vertices');
}
const paths: number[] = [];
const dotSize = lineWidth * 2;
for (let i = 0, len = polygonVertices.length; i < len; i += 2) {
const x1 = polygonVertices[i];
const y1 = polygonVertices[i + 1];
// draw the bounding box node
debugDisplayObjects.boundingBoxesCircle.beginFill(this.boundingBoxesCircleColor);
debugDisplayObjects.boundingBoxesCircle.drawCircle(x1, y1, dotSize);
debugDisplayObjects.boundingBoxesCircle.fill(0);
debugDisplayObjects.boundingBoxesCircle
.circle(x1, y1, dotSize)
.fill({ color: this.boundingBoxesCircleColor })
paths.push(x1, y1);
}
// draw the bounding box area
debugDisplayObjects.boundingBoxesPolygon
.poly(paths)
.fill({
color: this.boundingBoxesPolygonColor,
alpha: 0.1
})
.stroke({
width: lineWidth,
color: this.boundingBoxesPolygonColor
});
};
for (let i = 0, len = polygons.length; i < len; i++) {
const polygon = polygons[i];
drawPolygon(polygon, 0, polygon.length);
}
}
private drawPathsFunc (spine: Spine, debugDisplayObjects: DebugDisplayObjects, lineWidth: number): void {
const skeleton = spine.skeleton;
const slots = skeleton.slots;
for (let i = 0, len = slots.length; i < len; i++) {
const slot = slots[i];
if (!slot.bone.active) {
continue;
}
const attachment = slot.getAttachment();
if (attachment === null || !(attachment instanceof PathAttachment)) {
continue;
}
const pathAttachment = attachment;
let nn = pathAttachment.worldVerticesLength;
const world = new Float32Array(nn);
pathAttachment.computeWorldVertices(slot, 0, nn, world, 0, 2);
let x1 = world[2];
let y1 = world[3];
let x2 = 0;
let y2 = 0;
if (pathAttachment.closed) {
const cx1 = world[0];
const cy1 = world[1];
const cx2 = world[nn - 2];
const cy2 = world[nn - 1];
x2 = world[nn - 4];
y2 = world[nn - 3];
// curve
debugDisplayObjects.pathsCurve.moveTo(x1, y1);
debugDisplayObjects.pathsCurve.bezierCurveTo(cx1, cy1, cx2, cy2, x2, y2);
// handle
debugDisplayObjects.pathsLine.moveTo(x1, y1);
debugDisplayObjects.pathsLine.lineTo(cx1, cy1);
debugDisplayObjects.pathsLine.moveTo(x2, y2);
debugDisplayObjects.pathsLine.lineTo(cx2, cy2);
}
nn -= 4;
for (let ii = 4; ii < nn; ii += 6) {
const cx1 = world[ii];
const cy1 = world[ii + 1];
const cx2 = world[ii + 2];
const cy2 = world[ii + 3];
x2 = world[ii + 4];
y2 = world[ii + 5];
// curve
debugDisplayObjects.pathsCurve.moveTo(x1, y1);
debugDisplayObjects.pathsCurve.bezierCurveTo(cx1, cy1, cx2, cy2, x2, y2);
// handle
debugDisplayObjects.pathsLine.moveTo(x1, y1);
debugDisplayObjects.pathsLine.lineTo(cx1, cy1);
debugDisplayObjects.pathsLine.moveTo(x2, y2);
debugDisplayObjects.pathsLine.lineTo(cx2, cy2);
x1 = x2;
y1 = y2;
}
}
debugDisplayObjects.pathsCurve.stroke({ width: lineWidth, color: this.pathsCurveColor });
debugDisplayObjects.pathsLine.stroke({ width: lineWidth, color: this.pathsLineColor });
}
public unregisterSpine (spine: Spine): void {
if (!this.registeredSpines.has(spine)) {
console.warn('SpineDebugRenderer.unregisterSpine() - spine is not registered, can\'t unregister!', spine);
}
const debugDisplayObjects = this.registeredSpines.get(spine);
if (!debugDisplayObjects) {
return;
}
spine.state.removeListener(debugDisplayObjects.eventCallback);
debugDisplayObjects.parentDebugContainer.destroy({ textureSource: true, children: true, texture: true });
this.registeredSpines.delete(spine);
}
}

View File

@ -0,0 +1,182 @@
/** ****************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 {
collectAllRenderables,
extensions, ExtensionType,
InstructionSet,
type Renderer,
type RenderPipe,
} from 'pixi.js';
import { BatchableSpineSlot } from './BatchableSpineSlot';
import { Spine } from './Spine';
import { MeshAttachment, RegionAttachment, SkeletonClipping } from '@esotericsoftware/spine-core';
const spineBlendModeMap = {
0: 'normal',
1: 'add',
2: 'multiply',
3: 'screen'
};
// eslint-disable-next-line max-len
export class SpinePipe implements RenderPipe<Spine> {
/** @ignore */
static extension = {
type: [
ExtensionType.WebGLPipes,
ExtensionType.WebGPUPipes,
ExtensionType.CanvasPipes,
],
name: 'spine',
} as const;
renderer: Renderer;
private gpuSpineData: Record<string, any> = {};
constructor (renderer: Renderer) {
this.renderer = renderer;
}
validateRenderable (spine: Spine): boolean {
spine._applyState();
// if pine attachments have changed, we need to rebuild the batch!
if (spine.spineAttachmentsDirty) {
return true;
}
// if the textures have changed, we need to rebuild the batch, but only if the texture is not already in the batch
else if (spine.spineTexturesDirty) {
// loop through and see if the textures have changed..
const drawOrder = spine.skeleton.drawOrder;
const gpuSpine = this.gpuSpineData[spine.uid];
for (let i = 0, n = drawOrder.length; i < n; i++) {
const slot = drawOrder[i];
const attachment = slot.getAttachment();
if (attachment instanceof RegionAttachment || attachment instanceof MeshAttachment) {
const cacheData = spine._getCachedData(slot, attachment);
const batchableSpineSlot = gpuSpine.slotBatches[cacheData.id];
const texture = cacheData.texture;
if (texture !== batchableSpineSlot.texture) {
if (!batchableSpineSlot._batcher.checkAndUpdateTexture(batchableSpineSlot, texture)) {
return true;
}
}
}
}
}
return false;
}
addRenderable (spine: Spine, instructionSet: InstructionSet) {
const gpuSpine = this.gpuSpineData[spine.uid] ||= { slotBatches: {} };
const batcher = this.renderer.renderPipes.batch;
const drawOrder = spine.skeleton.drawOrder;
const roundPixels = (this.renderer._roundPixels | spine._roundPixels) as 0 | 1;
spine._applyState();
for (let i = 0, n = drawOrder.length; i < n; i++) {
const slot = drawOrder[i];
const attachment = slot.getAttachment();
const blendMode = spineBlendModeMap[slot.data.blendMode];
if (attachment instanceof RegionAttachment || attachment instanceof MeshAttachment) {
const cacheData = spine._getCachedData(slot, attachment);
const batchableSpineSlot = gpuSpine.slotBatches[cacheData.id] ||= new BatchableSpineSlot();
batchableSpineSlot.setData(
spine,
cacheData,
blendMode,
roundPixels
);
if (!cacheData.skipRender) {
batcher.addToBatch(batchableSpineSlot, instructionSet);
}
}
const containerAttachment = spine._slotsObject[slot.data.name];
if (containerAttachment) {
const container = containerAttachment.container;
container.includeInBuild = true;
collectAllRenderables(container, instructionSet, this.renderer);
container.includeInBuild = false;
}
}
}
updateRenderable (spine: Spine) {
// we assume that spine will always change its verts size..
const gpuSpine = this.gpuSpineData[spine.uid];
spine._applyState();
const drawOrder = spine.skeleton.drawOrder;
for (let i = 0, n = drawOrder.length; i < n; i++) {
const slot = drawOrder[i];
const attachment = slot.getAttachment();
if (attachment instanceof RegionAttachment || attachment instanceof MeshAttachment) {
const cacheData = spine._getCachedData(slot, attachment);
if (!cacheData.skipRender) {
const batchableSpineSlot = gpuSpine.slotBatches[spine._getCachedData(slot, attachment).id];
batchableSpineSlot._batcher?.updateElement(batchableSpineSlot);
}
}
}
}
destroyRenderable (spine: Spine) {
// TODO remove the renderable from the batcher
this.gpuSpineData[spine.uid] = null as any;
}
destroy () {
this.gpuSpineData = null as any;
this.renderer = null as any;
}
}
extensions.add(SpinePipe);

View File

@ -0,0 +1,143 @@
/** ****************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 { Texture as PixiTexture } from 'pixi.js';
import { BlendMode, Texture, TextureFilter, TextureWrap } from '@esotericsoftware/spine-core';
import type { BLEND_MODES, SCALE_MODE, TextureSource, WRAP_MODE } from 'pixi.js';
export class SpineTexture extends Texture {
private static readonly textureMap: Map<TextureSource, SpineTexture> = new Map<TextureSource, SpineTexture>();
public static from (texture: TextureSource): SpineTexture {
if (SpineTexture.textureMap.has(texture)) {
return SpineTexture.textureMap.get(texture) as SpineTexture;
}
return new SpineTexture(texture);
}
public readonly texture: PixiTexture;
private constructor (image: TextureSource) {
// Todo: maybe add error handling if you feed a video texture to spine?
super(image.resource);
this.texture = PixiTexture.from(image);
}
public setFilters (minFilter: TextureFilter, magFilter: TextureFilter): void {
const style = this.texture.source.style;
style.minFilter = SpineTexture.toPixiTextureFilter(minFilter);
style.magFilter = SpineTexture.toPixiTextureFilter(magFilter);
this.texture.source.autoGenerateMipmaps = SpineTexture.toPixiMipMap(minFilter);
this.texture.source.updateMipmaps();
}
public setWraps (uWrap: TextureWrap, vWrap: TextureWrap): void {
const style = this.texture.source.style;
style.addressModeU = SpineTexture.toPixiTextureWrap(uWrap);
style.addressModeV = SpineTexture.toPixiTextureWrap(vWrap);
}
public dispose (): void {
// I am not entirely sure about this...
this.texture.destroy();
}
private static toPixiMipMap (filter: TextureFilter): boolean {
switch (filter) {
case TextureFilter.Nearest:
case TextureFilter.Linear:
return false;
case TextureFilter.MipMapNearestLinear:
case TextureFilter.MipMapNearestNearest:
case TextureFilter.MipMapLinearLinear: // TextureFilter.MipMapLinearLinear == TextureFilter.MipMap
case TextureFilter.MipMapLinearNearest:
return true;
default:
throw new Error(`Unknown texture filter: ${String(filter)}`);
}
}
private static toPixiTextureFilter (filter: TextureFilter): SCALE_MODE {
switch (filter) {
case TextureFilter.Nearest:
case TextureFilter.MipMapNearestLinear:
case TextureFilter.MipMapNearestNearest:
return 'nearest';
case TextureFilter.Linear:
case TextureFilter.MipMapLinearLinear: // TextureFilter.MipMapLinearLinear == TextureFilter.MipMap
case TextureFilter.MipMapLinearNearest:
return 'linear';
default:
throw new Error(`Unknown texture filter: ${String(filter)}`);
}
}
private static toPixiTextureWrap (wrap: TextureWrap): WRAP_MODE {
switch (wrap) {
case TextureWrap.ClampToEdge:
return 'clamp-to-edge';
case TextureWrap.MirroredRepeat:
return 'mirror-repeat';
case TextureWrap.Repeat:
return 'repeat';
default:
throw new Error(`Unknown texture wrap: ${String(wrap)}`);
}
}
public static toPixiBlending (blend: BlendMode): BLEND_MODES {
switch (blend) {
case BlendMode.Normal:
return 'normal';
case BlendMode.Additive:
return 'add';
case BlendMode.Multiply:
return 'multiply';
case BlendMode.Screen:
return 'screen';
default:
throw new Error(`Unknown blendMode: ${String(blend)}`);
}
}
}

View File

@ -0,0 +1,158 @@
/** ****************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 {
checkExtension,
DOMAdapter,
extensions,
ExtensionType,
LoaderParserPriority,
path,
Resolver,
TextureSource
} from 'pixi.js';
import { SpineTexture } from '../SpineTexture';
import { TextureAtlas } from '@esotericsoftware/spine-core';
import type { AssetExtension, Loader, ResolvedAsset, Texture, UnresolvedAsset } from 'pixi.js';
type RawAtlas = string;
const spineTextureAtlasLoader: AssetExtension<RawAtlas | TextureAtlas, ISpineAtlasMetadata> = {
extension: ExtensionType.Asset,
resolver: {
test: (value: string): boolean => checkExtension(value, ".atlas"),
parse: (value: string): UnresolvedAsset => {
const split = value.split('.');
return {
resolution: parseFloat(Resolver.RETINA_PREFIX?.exec(value)?.[1] ?? '1'),
format: split[split.length - 2],
src: value,
};
},
},
loader: {
extension: {
type: ExtensionType.LoadParser,
priority: LoaderParserPriority.Normal,
name: 'spineTextureAtlasLoader',
},
test (url: string): boolean {
return checkExtension(url, '.atlas');
},
async load (url: string): Promise<RawAtlas> {
const response = await DOMAdapter.get().fetch(url);
const txt = await response.text();
return txt;
},
testParse (asset: unknown, options: ResolvedAsset): Promise<boolean> {
const isExtensionRight = checkExtension(options.src as string, '.atlas');
const isString = typeof asset === 'string';
return Promise.resolve(isExtensionRight && isString);
},
unload (atlas: TextureAtlas) {
atlas.dispose();
},
async parse (asset: RawAtlas, options: ResolvedAsset, loader: Loader): Promise<TextureAtlas> {
const metadata: ISpineAtlasMetadata = options.data || {};
let basePath = path.dirname(options.src as string);
if (basePath && basePath.lastIndexOf('/') !== basePath.length - 1) {
basePath += '/';
}
// Retval is going to be a texture atlas. However we need to wait for it's callback to resolve this promise.
const retval = new TextureAtlas(asset);
// If the user gave me only one texture, that one is assumed to be the "first" texture in the atlas
if (metadata.images instanceof TextureSource || typeof metadata.images === 'string') {
const pixiTexture = metadata.images;
metadata.images = {} as Record<string, TextureSource | string>;
metadata.images[retval.pages[0].name] = pixiTexture;
}
// we will wait for all promises for the textures at the same time at the end.
const textureLoadingPromises: Promise<any>[] = [];
// fill the pages
for (const page of retval.pages) {
const pageName = page.name;
const providedPage = metadata?.images ? metadata.images[pageName] : undefined;
if (providedPage instanceof TextureSource) {
page.setTexture(SpineTexture.from(providedPage));
}
else {
// eslint-disable-next-line max-len
const url: string = providedPage ?? path.normalize([...basePath.split(path.sep), pageName].join(path.sep));
const assetsToLoadIn = {
src: url,
data: {
...metadata.imageMetadata,
alphaMode: page.pma ? 'premultiplied-alpha' : 'premultiply-alpha-on-upload'
}
};
const pixiPromise = loader.load<Texture>(assetsToLoadIn).then((texture) => {
page.setTexture(SpineTexture.from(texture.source));
});
textureLoadingPromises.push(pixiPromise);
}
}
await Promise.all(textureLoadingPromises);
return retval;
},
},
} as AssetExtension<RawAtlas | TextureAtlas, ISpineAtlasMetadata>;
extensions.add(spineTextureAtlasLoader);
export interface ISpineAtlasMetadata {
// If you are downloading an .atlas file, this metadata will go to the Texture loader
imageMetadata?: any;
// If you already have atlas pages loaded as pixi textures
// and want to use that to create the atlas, you can pass them here
images?: TextureSource | string | Record<string, TextureSource | string>;
}

View File

@ -0,0 +1,81 @@
/** ****************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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 {
type AssetExtension,
checkExtension,
DOMAdapter,
extensions,
ExtensionType,
LoaderParserPriority,
ResolvedAsset
} from 'pixi.js';
type SkeletonJsonAsset = any;
type SkeletonBinaryAsset = Uint8Array;
function isJson (resource: any): resource is SkeletonJsonAsset {
return Object.prototype.hasOwnProperty.call(resource, 'bones');
}
function isBuffer (resource: any): resource is SkeletonBinaryAsset {
return resource instanceof Uint8Array;
}
const spineLoaderExtension: AssetExtension<SkeletonJsonAsset | SkeletonBinaryAsset> = {
extension: ExtensionType.Asset,
loader: {
extension: {
type: ExtensionType.LoadParser,
priority: LoaderParserPriority.Normal,
name: 'spineSkeletonLoader',
},
test (url) {
return checkExtension(url, '.skel');
},
async load (url: string): Promise<SkeletonBinaryAsset> {
const response = await DOMAdapter.get().fetch(url);
const buffer = new Uint8Array(await response.arrayBuffer());
return buffer;
},
testParse (asset: unknown, options: ResolvedAsset): Promise<boolean> {
const isJsonSpineModel = checkExtension(options.src!, '.json') && isJson(asset);
const isBinarySpineModel = checkExtension(options.src!, '.skel') && isBuffer(asset);
return Promise.resolve(isJsonSpineModel || isBinarySpineModel);
},
},
} as AssetExtension<SkeletonJsonAsset | SkeletonBinaryAsset>;
extensions.add(spineLoaderExtension);

View File

@ -0,0 +1,63 @@
import { Buffer, BufferUsage, Geometry } from 'pixi.js';
const placeHolderBufferData = new Float32Array(1);
const placeHolderIndexData = new Uint32Array(1);
export class DarkTintBatchGeometry extends Geometry {
constructor () {
const vertexSize = 7;
const attributeBuffer = new Buffer({
data: placeHolderBufferData,
label: 'attribute-batch-buffer',
usage: BufferUsage.VERTEX | BufferUsage.COPY_DST,
shrinkToFit: false,
});
const indexBuffer = new Buffer({
data: placeHolderIndexData,
label: 'index-batch-buffer',
usage: BufferUsage.INDEX | BufferUsage.COPY_DST, // | BufferUsage.STATIC,
shrinkToFit: false,
});
const stride = vertexSize * 4;
super({
attributes: {
aPosition: {
buffer: attributeBuffer,
format: 'float32x2',
stride,
offset: 0,
},
aUV: {
buffer: attributeBuffer,
format: 'float32x2',
stride,
offset: 2 * 4,
},
aColor: {
buffer: attributeBuffer,
format: 'unorm8x4',
stride,
offset: 4 * 4,
},
aDarkColor: {
buffer: attributeBuffer,
format: 'unorm8x4',
stride,
offset: 5 * 4,
},
aTextureIdAndRound: {
buffer: attributeBuffer,
format: 'uint16x2',
stride,
offset: 6 * 4,
},
},
indexBuffer
});
}
}

View File

@ -0,0 +1,157 @@
import {
Batcher,
Color,
DefaultBatchableMeshElement,
DefaultBatchableQuadElement,
extensions,
ExtensionType,
Shader
} from 'pixi.js';
import { DarkTintBatchGeometry } from './DarkTintBatchGeometry';
import { DarkTintShader } from './DarkTintShader';
let defaultShader: Shader | null = null;
/** The default batcher is used to batch quads and meshes. */
export class DarkTintBatcher extends Batcher {
/** @ignore */
public static extension = {
type: [
ExtensionType.Batcher,
],
name: 'darkTint',
} as const;
public geometry = new DarkTintBatchGeometry();
public shader = defaultShader || (defaultShader = new DarkTintShader(this.maxTextures));
public name = DarkTintBatcher.extension.name;
/** The size of one attribute. 1 = 32 bit. x, y, u, v, color, darkColor, textureIdAndRound -> total = 7 */
public vertexSize = 7;
public packAttributes (
element: DefaultBatchableMeshElement & { darkColor: number },
float32View: Float32Array,
uint32View: Uint32Array,
index: number,
textureId: number
) {
const textureIdAndRound = (textureId << 16) | (element.roundPixels & 0xFFFF);
const wt = element.transform;
const a = wt.a;
const b = wt.b;
const c = wt.c;
const d = wt.d;
const tx = wt.tx;
const ty = wt.ty;
const { positions, uvs } = element;
const argb = element.color;
const worldAlpha = ((argb >> 24) & 0xFF) / 255;
const darkColor = Color.shared.setValue(element.darkColor).premultiply(worldAlpha, true).toPremultiplied(1, false);
const offset = element.attributeOffset;
const end = offset + element.attributeSize;
for (let i = offset; i < end; i++) {
const i2 = i * 2;
const x = positions[i2];
const y = positions[(i2) + 1];
float32View[index++] = (a * x) + (c * y) + tx;
float32View[index++] = (d * y) + (b * x) + ty;
float32View[index++] = uvs[i2];
float32View[index++] = uvs[(i2) + 1];
uint32View[index++] = argb;
uint32View[index++] = darkColor;
uint32View[index++] = textureIdAndRound;
}
}
public packQuadAttributes (
element: DefaultBatchableQuadElement & { darkColor: number },
float32View: Float32Array,
uint32View: Uint32Array,
index: number,
textureId: number
) {
const texture = element.texture;
const wt = element.transform;
const a = wt.a;
const b = wt.b;
const c = wt.c;
const d = wt.d;
const tx = wt.tx;
const ty = wt.ty;
const bounds = element.bounds;
const w0 = bounds.maxX;
const w1 = bounds.minX;
const h0 = bounds.maxY;
const h1 = bounds.minY;
const uvs = texture.uvs;
// _ _ _ _
// a b g r
const argb = element.color;
const darkColor = element.darkColor;
const textureIdAndRound = (textureId << 16) | (element.roundPixels & 0xFFFF);
float32View[index + 0] = (a * w1) + (c * h1) + tx;
float32View[index + 1] = (d * h1) + (b * w1) + ty;
float32View[index + 2] = uvs.x0;
float32View[index + 3] = uvs.y0;
uint32View[index + 4] = argb;
uint32View[index + 5] = darkColor;
uint32View[index + 6] = textureIdAndRound;
// xy
float32View[index + 7] = (a * w0) + (c * h1) + tx;
float32View[index + 8] = (d * h1) + (b * w0) + ty;
float32View[index + 9] = uvs.x1;
float32View[index + 10] = uvs.y1;
uint32View[index + 11] = argb;
uint32View[index + 12] = darkColor;
uint32View[index + 13] = textureIdAndRound;
// xy
float32View[index + 14] = (a * w0) + (c * h0) + tx;
float32View[index + 15] = (d * h0) + (b * w0) + ty;
float32View[index + 16] = uvs.x2;
float32View[index + 17] = uvs.y2;
uint32View[index + 18] = argb;
uint32View[index + 19] = darkColor;
uint32View[index + 20] = textureIdAndRound;
// xy
float32View[index + 21] = (a * w1) + (c * h0) + tx;
float32View[index + 22] = (d * h0) + (b * w1) + ty;
float32View[index + 23] = uvs.x3;
float32View[index + 24] = uvs.y3;
uint32View[index + 25] = argb;
uint32View[index + 26] = darkColor;
uint32View[index + 27] = textureIdAndRound;
}
}
extensions.add(DarkTintBatcher);

View File

@ -0,0 +1,45 @@
import {
colorBit,
colorBitGl,
compileHighShaderGlProgram,
compileHighShaderGpuProgram,
generateTextureBatchBit,
generateTextureBatchBitGl,
getBatchSamplersUniformGroup,
roundPixelsBit,
roundPixelsBitGl,
Shader
} from 'pixi.js';
import { darkTintBit, darkTintBitGl } from './darkTintBit';
export class DarkTintShader extends Shader {
constructor (maxTextures: number) {
const glProgram = compileHighShaderGlProgram({
name: 'dark-tint-batch',
bits: [
colorBitGl,
darkTintBitGl,
generateTextureBatchBitGl(maxTextures),
roundPixelsBitGl,
]
});
const gpuProgram = compileHighShaderGpuProgram({
name: 'dark-tint-batch',
bits: [
colorBit,
darkTintBit,
generateTextureBatchBit(maxTextures),
roundPixelsBit,
]
});
super({
glProgram,
gpuProgram,
resources: {
batchSamplers: getBatchSamplersUniformGroup(maxTextures),
}
});
}
}

View File

@ -0,0 +1,49 @@
/* eslint-disable max-len */
export const darkTintBit = {
name: 'color-bit',
vertex: {
header: /* wgsl */`
@in aDarkColor: vec4<f32>;
@out vDarkColor: vec4<f32>;
`,
main: /* wgsl */`
vDarkColor = aDarkColor;
`
},
fragment: {
header: /* wgsl */`
@in vDarkColor: vec4<f32>;
`,
end: /* wgsl */`
let alpha = outColor.a * vColor.a;
let rgb = ((outColor.a - 1.0) * vDarkColor.a + 1.0 - outColor.rgb) * vDarkColor.rgb + outColor.rgb * vColor.rgb;
finalColor = vec4<f32>(rgb, alpha);
`
}
};
export const darkTintBitGl = {
name: 'color-bit',
vertex: {
header: /* glsl */`
in vec4 aDarkColor;
out vec4 vDarkColor;
`,
main: /* glsl */`
vDarkColor = aDarkColor;
`
},
fragment: {
header: /* glsl */`
in vec4 vDarkColor;
`,
end: /* glsl */`
finalColor.a = outColor.a * vColor.a;
finalColor.rgb = ((outColor.a - 1.0) * vDarkColor.a + 1.0 - outColor.rgb) * vDarkColor.rgb + outColor.rgb * vColor.rgb;
`
}
};

View File

@ -0,0 +1,43 @@
/** ****************************************************************************
* 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 './require-shim.js'; // Side effects add require pixi.js to global scope
import './assets/atlasLoader.js'; // Side effects install the loaders into pixi
import './assets/skeletonLoader.js'; // Side effects install the loaders into pixi
import './darktint/DarkTintBatcher.js'; // Side effects install the batcher into pixi
import './SpinePipe.js';
export * from './assets/atlasLoader.js';
export * from './assets/skeletonLoader.js';
export * from './require-shim.js';
export * from './Spine.js';
export * from './SpineDebugRenderer.js';
export * from './SpinePipe.js';
export * from './SpineTexture.js';
export * from '@esotericsoftware/spine-core';

View File

@ -0,0 +1,43 @@
/** ****************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2023, 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.
*****************************************************************************/
declare global {
var require: any;
var PIXI: any;
}
if (typeof window !== 'undefined' && window.PIXI) {
const prevRequire = window.require;
(window as any).require = (x: string) => {
if (prevRequire) return prevRequire(x);
else if (x.startsWith("@pixi/") || x.startsWith("pixi.js")) return window.PIXI;
};
}
export { };

View File

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

View File

@ -1,3 +1,3 @@
# spine-ts Pixi.js
# spine-ts Pixi.js v7
Please see the top-level [README.md](../README.md) for more information.

View File

@ -45,9 +45,9 @@
await PIXI.Assets.load(["stretchymanData", "stretchymanAtlas"]);
// Create the spine display object
const stretchyman = spine.Spine.from("stretchymanData", "stretchymanAtlas", {
const stretchyman = spine.Spine.from({skeleton: "stretchymanData", atlas: "stretchymanAtlas",
scale: 0.75,
});
});
// Set the default mix time to use when transitioning
// from one animation to the next.

View File

@ -39,7 +39,7 @@
await PIXI.Assets.load(["spineboyData", "spineboyAtlas"]);
// Create the Spine display object
const spineboy = spine.Spine.from("spineboyData", "spineboyAtlas", {
const spineboy = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas",
scale: 0.5,
});
@ -62,7 +62,7 @@
event: (entry, event) =>
log(`Custom event for ${entry.animation.name}: ${event.data.name}`),
};
spineboy.state.addAnimation(0, "run", true, 0);
// Set the default mix time to use when transitioning

View File

@ -28,7 +28,7 @@
await PIXI.Assets.load(["spineboyData", "spineboyAtlas"]);
// Create the spine display object
const spineboy = spine.Spine.from("spineboyData", "spineboyAtlas", {
const spineboy = spine.Spine.from({skeleton: "spineboyData", atlas: "spineboyAtlas",
scale: 0.5,
});

View File

@ -37,7 +37,7 @@
const skeletonData = binaryLoader.readSkeletonData(
PIXI.Assets.get("spineboyData")
);
const spineboy = new spine.Spine(skeletonData);
const spineboy = new spine.Spine({ skeletonData });
// Set the default mix time to use when transitioning
// from one animation to the next.

View File

@ -28,7 +28,7 @@
await PIXI.Assets.load(["mixAndMatchData", "mixAndMatchAtlas"]);
// Create the Spine display object
const mixAndMatch = spine.Spine.from("mixAndMatchData", "mixAndMatchAtlas", {
const mixAndMatch = spine.Spine.from({skeleton: "mixAndMatchData", atlas: "mixAndMatchAtlas",
scale: 0.5,
});
@ -36,10 +36,6 @@
// from one animation to the next.
mixAndMatch.state.data.defaultMix = 0.2;
// Center the spine object on screen.
mixAndMatch.x = window.innerWidth / 2;
mixAndMatch.y = window.innerHeight / 2 + mixAndMatch.getBounds().height / 2;
// Add animations.
mixAndMatch.state.setAnimation(0, "walk", true);
mixAndMatch.state.addAnimation(0, "dance", true, 1.0);
@ -60,6 +56,10 @@
mixAndMatch.skeleton.setSkin(skin);
mixAndMatch.skeleton.setSlotsToSetupPose();
// Center the spine object on screen.
mixAndMatch.x = window.innerWidth / 2;
mixAndMatch.y = window.innerHeight / 2 + mixAndMatch.getBounds().height / 2;
// Add the display object to the stage.
app.stage.addChild(mixAndMatch);
})();

Some files were not shown because too many files have changed in this diff Show More