├── .eslintrc.json ├── .github ├── actions │ └── setup │ │ └── action.yml └── workflows │ ├── build.yml │ ├── deploy-build.yaml │ └── publish-branch.yaml ├── .gitignore ├── LICENSE ├── README.md ├── examples ├── assets │ ├── celestial-circus-pma.atlas │ ├── celestial-circus-pma.png │ ├── celestial-circus-pro.json │ ├── celestial-circus-pro.skel │ ├── celestial-circus.atlas │ ├── celestial-circus.png │ ├── cloud-pot-pma.atlas │ ├── cloud-pot-pma.png │ ├── cloud-pot.json │ ├── cloud-pot.skel │ ├── coin-pma.atlas │ ├── coin-pma.png │ ├── coin-pro.skel │ ├── mix-and-match-pma.atlas │ ├── mix-and-match-pma.png │ ├── mix-and-match-pro.skel │ ├── raptor-pma.atlas │ ├── raptor-pma.png │ ├── raptor-pro.json │ ├── raptor.atlas │ ├── raptor.png │ ├── sack-pma.atlas │ ├── sack-pma.png │ ├── sack-pro.json │ ├── sack-pro.skel │ ├── snowglobe-pma.atlas │ ├── snowglobe-pma.png │ ├── snowglobe-pma_2.png │ ├── snowglobe-pma_3.png │ ├── snowglobe-pma_4.png │ ├── snowglobe-pma_5.png │ ├── snowglobe-pro.json │ ├── snowglobe-pro.skel │ ├── spine_logo.png │ ├── spineboy-pma.atlas │ ├── spineboy-pma.png │ ├── spineboy-pro.json │ ├── spineboy-pro.skel │ ├── spineboy.atlas │ ├── spineboy.png │ ├── stretchyman-pma.atlas │ ├── stretchyman-pma.png │ └── stretchyman-pro.skel ├── basic.html ├── control-bones-example.html ├── events-example.html ├── index.css ├── index.html ├── manual-loading.html ├── mix-and-match-example.html ├── mouse-following.html ├── physics.html ├── physics2.html ├── physics3.html ├── physics4.html ├── simple-input.html └── slot-objects.html ├── package-lock.json ├── package.json ├── src ├── BatchableSpineSlot.ts ├── Spine.ts ├── SpineDebugRenderer.ts ├── SpinePipe.ts ├── SpineTexture.ts ├── assets │ ├── atlasLoader.ts │ └── skeletonLoader.ts ├── darktint │ ├── DarkTintBatchGeometry.ts │ ├── DarkTintBatcher.ts │ ├── DarkTintShader.ts │ └── darkTintBit.ts ├── index.ts └── require-shim.ts ├── tsconfig.build.json └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@pixi/eslint-config"], 3 | "rules": { 4 | "camelcase": ["off"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.github/actions/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: "Install Project" 2 | description: "Installs node, npm, and dependencies" 3 | inputs: 4 | node-version: 5 | description: "Node.js version" 6 | required: true 7 | default: "20" 8 | npm-version: 9 | description: "npm version" 10 | required: true 11 | default: "8" 12 | runs: 13 | using: "composite" 14 | steps: 15 | - name: Use Node.js 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: ${{ inputs.node-version }} 19 | registry-url: "https://registry.npmjs.org" 20 | - name: Install npm 21 | shell: bash 22 | run: npm install -g npm@${{ inputs.npm-version }} 23 | - name: Cache Dependencies 24 | id: node-modules-cache 25 | uses: actions/cache@v4 26 | with: 27 | path: node_modules 28 | key: ${{ runner.os }}-node-modules-${{ hashFiles('**/package-lock.json') }} 29 | restore-keys: | 30 | ${{ runner.os }}-node-modules- 31 | - name: Install Dependencies 32 | if: steps.node-modules-cache.outputs.cache-hit != 'true' 33 | shell: bash 34 | run: npm ci 35 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build CI 2 | on: 3 | push: 4 | branches: [ '**' ] 5 | tags: [ '**' ] 6 | release: 7 | types: [ 'created' ] 8 | pull_request: 9 | branches: [ '**' ] 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Use Node.js 16.x 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: 16 19 | - name: Install npm 20 | run: npm install -g npm@8 21 | - name: Install dependencies 22 | run: npm ci 23 | - name: Build 24 | run: npm run build 25 | - name: Test 26 | uses: GabrielBB/xvfb-action@v1 27 | with: 28 | run: npm test -------------------------------------------------------------------------------- /.github/workflows/deploy-build.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy Examples 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref || github.run_id }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | deploy: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | ref: ${{ github.head_ref }} 19 | 20 | - name: Setup Project 21 | uses: ./.github/actions/setup 22 | 23 | - name: Build Project 24 | run: | 25 | git config --global user.name "${{ github.actor }}" 26 | git config --global user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com" 27 | git remote set-url origin https://git:${GITHUB_TOKEN}@github.com/pixijs/spine-v8.git 28 | npm run build 29 | npm run docs 30 | npm run upload -- -u "github-actions-bot " 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | -------------------------------------------------------------------------------- /.github/workflows/publish-branch.yaml: -------------------------------------------------------------------------------- 1 | name: Publish Branch 2 | 3 | on: 4 | push: 5 | branches: 6 | - dev 7 | 8 | jobs: 9 | release_candidate: 10 | runs-on: ubuntu-latest 11 | env: 12 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 13 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 14 | BRANCH_NAME: ${{ github.head_ref || github.ref_name }} 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | ref: ${{ github.head_ref }} 19 | 20 | - name: Setup Project 21 | uses: ./.github/actions/setup 22 | 23 | - name: Build Project 24 | run: npm run build 25 | 26 | - name: Get current package.json version 27 | run: echo "PACKAGE_VERSION=$(npm pkg get version | tr -d '"')" >> $GITHUB_ENV 28 | 29 | # get the sort SHA and add it into the environment variables 30 | - name: Setup Branch Release Candidate Version 31 | run: echo "BRANCH_VERSION=$PACKAGE_VERSION-$BRANCH_NAME.${GITHUB_SHA::7}" >> $GITHUB_ENV 32 | 33 | - name: Bump version 34 | run: npm version $BRANCH_VERSION --no-git-tag-version --force 35 | 36 | - name: Publish a new branch release candidate version 37 | run: npm publish --tag $BRANCH_NAME 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /node_modules 3 | /docs 4 | /dist 5 | /lib 6 | example.api.json* 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 [Author's name] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Project Status 2 | 3 | Hey everyone, we have now completed the migration of this repo to the official Esoteric Software repo for them to maintain. You can find the new repo here: 4 | https://github.com/EsotericSoftware/spine-runtimes/tree/4.2/spine-ts 5 | 6 | If you are still using this repo, the migration should be as simple as swapping out the imports for `@esotericsoftware/spine-pixi-v8` 7 | -------------------------------------------------------------------------------- /examples/assets/celestial-circus-pma.atlas: -------------------------------------------------------------------------------- 1 | celestial-circus-pma.png 2 | size: 1024, 1024 3 | filter: Linear, Linear 4 | pma: true 5 | scale: 0.4 6 | arm-back-down 7 | bounds: 324, 401, 38, 82 8 | rotate: 90 9 | arm-back-up 10 | bounds: 290, 44, 83, 116 11 | rotate: 90 12 | arm-front-down 13 | bounds: 706, 2, 36, 78 14 | rotate: 90 15 | arm-front-up 16 | bounds: 860, 138, 77, 116 17 | bench 18 | bounds: 725, 256, 189, 48 19 | body-bottom 20 | bounds: 879, 868, 154, 124 21 | rotate: 90 22 | body-top 23 | bounds: 725, 128, 126, 133 24 | rotate: 90 25 | chest 26 | bounds: 408, 26, 104, 93 27 | cloud-back 28 | bounds: 752, 378, 202, 165 29 | cloud-front 30 | bounds: 2, 2, 325, 196 31 | rotate: 90 32 | collar 33 | bounds: 786, 13, 47, 26 34 | ear 35 | bounds: 1002, 643, 20, 28 36 | eye-back-shadow 37 | bounds: 428, 395, 14, 10 38 | eye-front-shadow 39 | bounds: 704, 529, 24, 14 40 | eye-reflex-back 41 | bounds: 860, 128, 8, 7 42 | rotate: 90 43 | eye-reflex-front 44 | bounds: 726, 386, 10, 7 45 | eye-white-back 46 | bounds: 835, 23, 13, 16 47 | eye-white-front 48 | bounds: 1005, 1000, 22, 17 49 | rotate: 90 50 | eyelashes-down-back 51 | bounds: 232, 329, 11, 6 52 | rotate: 90 53 | eyelashes-down-front 54 | bounds: 913, 851, 15, 6 55 | rotate: 90 56 | eyelashes-top-back 57 | bounds: 408, 395, 18, 10 58 | eyelashes-top-front 59 | bounds: 702, 179, 30, 16 60 | rotate: 90 61 | face 62 | bounds: 514, 26, 93, 102 63 | rotate: 90 64 | feathers-back 65 | bounds: 954, 625, 46, 46 66 | feathers-front 67 | bounds: 706, 40, 72, 86 68 | fringe-middle-back 69 | bounds: 200, 6, 33, 52 70 | rotate: 90 71 | fringe-middle-front 72 | bounds: 878, 76, 60, 50 73 | rotate: 90 74 | fringe-side-back 75 | bounds: 780, 41, 27, 94 76 | rotate: 90 77 | fringe-side-front 78 | bounds: 939, 161, 26, 93 79 | glove-bottom-back 80 | bounds: 954, 572, 51, 41 81 | rotate: 90 82 | glove-bottom-front 83 | bounds: 916, 256, 47, 48 84 | hair-back-1 85 | bounds: 444, 395, 132, 306 86 | rotate: 90 87 | hair-back-2 88 | bounds: 438, 211, 80, 285 89 | rotate: 90 90 | hair-back-3 91 | bounds: 719, 306, 70, 268 92 | rotate: 90 93 | hair-back-4 94 | bounds: 438, 121, 88, 262 95 | rotate: 90 96 | hair-back-5 97 | bounds: 438, 293, 88, 279 98 | rotate: 90 99 | hair-back-6 100 | bounds: 200, 41, 88, 286 101 | hair-hat-shadow 102 | bounds: 232, 398, 90, 41 103 | hand-back 104 | bounds: 954, 673, 60, 47 105 | rotate: 90 106 | hand-front 107 | bounds: 967, 172, 53, 60 108 | hat-back 109 | bounds: 954, 802, 64, 45 110 | rotate: 90 111 | hat-front 112 | bounds: 780, 70, 96, 56 113 | head-back 114 | bounds: 618, 17, 102, 86 115 | rotate: 90 116 | jabot 117 | bounds: 967, 234, 70, 55 118 | rotate: 90 119 | leg-back 120 | bounds: 232, 441, 210, 333 121 | leg-front 122 | bounds: 444, 529, 258, 320 123 | logo-brooch 124 | bounds: 954, 545, 16, 25 125 | mouth 126 | bounds: 408, 121, 22, 6 127 | neck 128 | bounds: 232, 342, 39, 56 129 | rotate: 90 130 | nose 131 | bounds: 742, 529, 6, 7 132 | rotate: 90 133 | nose-highlight 134 | bounds: 719, 300, 4, 4 135 | nose-shadow 136 | bounds: 869, 128, 7, 8 137 | pupil-back 138 | bounds: 730, 529, 10, 14 139 | pupil-front 140 | bounds: 254, 21, 12, 18 141 | rope-back 142 | bounds: 232, 383, 10, 492 143 | rotate: 90 144 | rope-front 145 | bounds: 232, 383, 10, 492 146 | rotate: 90 147 | rope-front-bottom 148 | bounds: 954, 735, 42, 65 149 | skirt 150 | bounds: 2, 776, 440, 246 151 | sock-bow 152 | bounds: 408, 407, 33, 32 153 | spine-logo-body 154 | bounds: 879, 853, 13, 32 155 | rotate: 90 156 | star-big 157 | bounds: 939, 141, 18, 24 158 | rotate: 90 159 | star-medium 160 | bounds: 742, 537, 6, 8 161 | rotate: 90 162 | star-small 163 | bounds: 719, 378, 3, 4 164 | rotate: 90 165 | underskirt 166 | bounds: 2, 329, 445, 228 167 | rotate: 90 168 | underskirt-back 169 | bounds: 444, 851, 433, 171 170 | wing-back 171 | bounds: 290, 129, 146, 252 172 | wing-front 173 | bounds: 704, 545, 304, 248 174 | rotate: 90 175 | -------------------------------------------------------------------------------- /examples/assets/celestial-circus-pma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/celestial-circus-pma.png -------------------------------------------------------------------------------- /examples/assets/celestial-circus-pro.skel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/celestial-circus-pro.skel -------------------------------------------------------------------------------- /examples/assets/celestial-circus.atlas: -------------------------------------------------------------------------------- 1 | celestial-circus.png 2 | size: 1024, 1024 3 | filter: Linear, Linear 4 | scale: 0.4 5 | arm-back-down 6 | bounds: 324, 401, 38, 82 7 | rotate: 90 8 | arm-back-up 9 | bounds: 290, 44, 83, 116 10 | rotate: 90 11 | arm-front-down 12 | bounds: 706, 2, 36, 78 13 | rotate: 90 14 | arm-front-up 15 | bounds: 860, 138, 77, 116 16 | bench 17 | bounds: 725, 256, 189, 48 18 | body-bottom 19 | bounds: 879, 868, 154, 124 20 | rotate: 90 21 | body-top 22 | bounds: 725, 128, 126, 133 23 | rotate: 90 24 | chest 25 | bounds: 408, 26, 104, 93 26 | cloud-back 27 | bounds: 752, 378, 202, 165 28 | cloud-front 29 | bounds: 2, 2, 325, 196 30 | rotate: 90 31 | collar 32 | bounds: 786, 13, 47, 26 33 | ear 34 | bounds: 1002, 643, 20, 28 35 | eye-back-shadow 36 | bounds: 428, 395, 14, 10 37 | eye-front-shadow 38 | bounds: 704, 529, 24, 14 39 | eye-reflex-back 40 | bounds: 860, 128, 8, 7 41 | rotate: 90 42 | eye-reflex-front 43 | bounds: 726, 386, 10, 7 44 | eye-white-back 45 | bounds: 835, 23, 13, 16 46 | eye-white-front 47 | bounds: 1005, 1000, 22, 17 48 | rotate: 90 49 | eyelashes-down-back 50 | bounds: 232, 329, 11, 6 51 | rotate: 90 52 | eyelashes-down-front 53 | bounds: 913, 851, 15, 6 54 | rotate: 90 55 | eyelashes-top-back 56 | bounds: 408, 395, 18, 10 57 | eyelashes-top-front 58 | bounds: 702, 179, 30, 16 59 | rotate: 90 60 | face 61 | bounds: 514, 26, 93, 102 62 | rotate: 90 63 | feathers-back 64 | bounds: 954, 625, 46, 46 65 | feathers-front 66 | bounds: 706, 40, 72, 86 67 | fringe-middle-back 68 | bounds: 200, 6, 33, 52 69 | rotate: 90 70 | fringe-middle-front 71 | bounds: 878, 76, 60, 50 72 | rotate: 90 73 | fringe-side-back 74 | bounds: 780, 41, 27, 94 75 | rotate: 90 76 | fringe-side-front 77 | bounds: 939, 161, 26, 93 78 | glove-bottom-back 79 | bounds: 954, 572, 51, 41 80 | rotate: 90 81 | glove-bottom-front 82 | bounds: 916, 256, 47, 48 83 | hair-back-1 84 | bounds: 444, 395, 132, 306 85 | rotate: 90 86 | hair-back-2 87 | bounds: 438, 211, 80, 285 88 | rotate: 90 89 | hair-back-3 90 | bounds: 719, 306, 70, 268 91 | rotate: 90 92 | hair-back-4 93 | bounds: 438, 121, 88, 262 94 | rotate: 90 95 | hair-back-5 96 | bounds: 438, 293, 88, 279 97 | rotate: 90 98 | hair-back-6 99 | bounds: 200, 41, 88, 286 100 | hair-hat-shadow 101 | bounds: 232, 398, 90, 41 102 | hand-back 103 | bounds: 954, 673, 60, 47 104 | rotate: 90 105 | hand-front 106 | bounds: 967, 172, 53, 60 107 | hat-back 108 | bounds: 954, 802, 64, 45 109 | rotate: 90 110 | hat-front 111 | bounds: 780, 70, 96, 56 112 | head-back 113 | bounds: 618, 17, 102, 86 114 | rotate: 90 115 | jabot 116 | bounds: 967, 234, 70, 55 117 | rotate: 90 118 | leg-back 119 | bounds: 232, 441, 210, 333 120 | leg-front 121 | bounds: 444, 529, 258, 320 122 | logo-brooch 123 | bounds: 954, 545, 16, 25 124 | mouth 125 | bounds: 408, 121, 22, 6 126 | neck 127 | bounds: 232, 342, 39, 56 128 | rotate: 90 129 | nose 130 | bounds: 742, 529, 6, 7 131 | rotate: 90 132 | nose-highlight 133 | bounds: 719, 300, 4, 4 134 | nose-shadow 135 | bounds: 869, 128, 7, 8 136 | pupil-back 137 | bounds: 730, 529, 10, 14 138 | pupil-front 139 | bounds: 254, 21, 12, 18 140 | rope-back 141 | bounds: 232, 383, 10, 492 142 | rotate: 90 143 | rope-front 144 | bounds: 232, 383, 10, 492 145 | rotate: 90 146 | rope-front-bottom 147 | bounds: 954, 735, 42, 65 148 | skirt 149 | bounds: 2, 776, 440, 246 150 | sock-bow 151 | bounds: 408, 407, 33, 32 152 | spine-logo-body 153 | bounds: 879, 853, 13, 32 154 | rotate: 90 155 | star-big 156 | bounds: 939, 141, 18, 24 157 | rotate: 90 158 | star-medium 159 | bounds: 742, 537, 6, 8 160 | rotate: 90 161 | star-small 162 | bounds: 719, 378, 3, 4 163 | rotate: 90 164 | underskirt 165 | bounds: 2, 329, 445, 228 166 | rotate: 90 167 | underskirt-back 168 | bounds: 444, 851, 433, 171 169 | wing-back 170 | bounds: 290, 129, 146, 252 171 | wing-front 172 | bounds: 704, 545, 304, 248 173 | rotate: 90 174 | -------------------------------------------------------------------------------- /examples/assets/celestial-circus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/celestial-circus.png -------------------------------------------------------------------------------- /examples/assets/cloud-pot-pma.atlas: -------------------------------------------------------------------------------- 1 | cloud-pot-pma.png 2 | size: 1024, 512 3 | filter: Linear, Linear 4 | pma: true 5 | scale: 0.5 6 | cloud-base-1 7 | bounds: 2, 300, 233, 210 8 | cloud-base-10 9 | bounds: 214, 113, 97, 101 10 | cloud-base-2 11 | bounds: 2, 90, 210, 208 12 | cloud-base-3 13 | bounds: 237, 346, 175, 164 14 | cloud-base-4 15 | bounds: 414, 347, 176, 163 16 | cloud-base-5 17 | bounds: 313, 89, 145, 125 18 | cloud-base-6 19 | bounds: 744, 374, 161, 136 20 | cloud-base-7 21 | bounds: 592, 361, 150, 149 22 | cloud-base-8 23 | bounds: 237, 216, 154, 128 24 | cloud-base-9 25 | bounds: 907, 402, 107, 108 26 | cloud-cheeks 27 | bounds: 2, 9, 218, 79 28 | cloud-eyes-closed 29 | bounds: 744, 350, 132, 22 30 | cloud-eyes-open 31 | bounds: 592, 333, 133, 26 32 | cloud-eyes-reflex 33 | bounds: 393, 224, 120, 17 34 | rotate: 90 35 | cloud-mouth-closed 36 | bounds: 907, 374, 49, 16 37 | cloud-mouth-open 38 | bounds: 222, 15, 59, 35 39 | leaf-big 40 | bounds: 214, 218, 20, 49 41 | leaf-small 42 | bounds: 958, 373, 17, 30 43 | rotate: 90 44 | petal-1 45 | bounds: 283, 2, 26, 18 46 | petal-2 47 | bounds: 283, 22, 28, 17 48 | rotate: 90 49 | petal-3 50 | bounds: 214, 269, 29, 21 51 | rotate: 90 52 | pot-base 53 | bounds: 222, 52, 76, 59 54 | pot-eyes-closed 55 | bounds: 878, 363, 46, 9 56 | pot-eyes-open 57 | bounds: 222, 2, 40, 11 58 | pot-mouth-open 59 | bounds: 990, 374, 14, 16 60 | pot-mouth-pouty 61 | bounds: 300, 93, 18, 10 62 | rotate: 90 63 | pot-mouth-smile 64 | bounds: 300, 77, 14, 10 65 | rotate: 90 66 | pot-mouth-smile-big 67 | bounds: 878, 352, 20, 9 68 | rain-blue 69 | bounds: 926, 360, 12, 18 70 | rotate: 90 71 | rain-color 72 | bounds: 264, 4, 9, 17 73 | rotate: 90 74 | rain-green 75 | bounds: 900, 349, 12, 18 76 | rotate: 90 77 | rain-white 78 | bounds: 727, 337, 12, 22 79 | rain-white-reflex 80 | bounds: 2, 2, 5, 10 81 | rotate: 90 82 | stem 83 | bounds: 907, 392, 8, 105 84 | rotate: 90 85 | stem-end 86 | bounds: 300, 62, 13, 13 87 | -------------------------------------------------------------------------------- /examples/assets/cloud-pot-pma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/cloud-pot-pma.png -------------------------------------------------------------------------------- /examples/assets/cloud-pot.json: -------------------------------------------------------------------------------- 1 | { 2 | "skeleton": { 3 | "hash": "CKnF82un6n8", 4 | "spine": "4.2.22", 5 | "x": -345, 6 | "y": -272846.84, 7 | "width": 756, 8 | "height": 273927.84, 9 | "images": "./images/", 10 | "audio": "" 11 | }, 12 | "bones": [ 13 | { "name": "root" }, 14 | { "name": "pot-control", "parent": "root", "x": 5, "y": 42, "color": "8828ffff", "icon": "arrowsB" }, 15 | { "name": "cloud", "parent": "pot-control", "x": 26.5, "y": 772, "color": "1ee8c0ff", "icon": "circle" }, 16 | { "name": "cloud-base-1", "parent": "cloud", "x": -4, "y": 57, "color": "b0d5eaff" }, 17 | { "name": "cloud-base-2", "parent": "cloud-base-1", "x": 148.5, "y": -18.5, "color": "b0d5eaff" }, 18 | { "name": "cloud-base-3", "parent": "cloud-base-1", "x": -182, "y": -87.5, "color": "b0d5eaff" }, 19 | { "name": "cloud-base-4", "parent": "cloud", "x": -31.5, "y": -77, "color": "b0d5eaff" }, 20 | { "name": "cloud-base-5", "parent": "cloud-base-4", "x": 177.5, "y": 8, "color": "b0d5eaff" }, 21 | { "name": "cloud-base-6", "parent": "cloud-base-1", "x": -150.5, "y": 40, "color": "b0d5eaff" }, 22 | { "name": "cloud-base-7", "parent": "cloud-base-1", "x": 8.5, "y": 36.5, "color": "b0d5eaff" }, 23 | { "name": "cloud-base-8", "parent": "cloud-base-2", "x": 3.5, "y": 68.5, "color": "b0d5eaff" }, 24 | { "name": "cloud-base-9", "parent": "cloud-base-3", "x": -83.5, "y": 30.5, "color": "b0d5eaff" }, 25 | { "name": "cloud-base-10", "parent": "cloud-base-5", "x": 137, "y": 54.5, "color": "b0d5eaff" }, 26 | { "name": "rain-blue", "parent": "cloud", "x": 102.49, "y": -26, "color": "2360e3ff", "icon": "diamond" }, 27 | { "name": "rain-color", "parent": "cloud", "x": -39.42, "y": -26, "color": "2360e3ff", "icon": "diamond" }, 28 | { "name": "rain-green", "parent": "cloud", "x": 35.08, "y": -26, "color": "2360e3ff", "icon": "diamond" }, 29 | { "name": "rain-white", "parent": "cloud", "x": -103.92, "y": -26, "color": "2360e3ff", "icon": "diamond" }, 30 | { "name": "pot", "parent": "pot-control", "x": -5, "y": -42, "color": "8828ffff" }, 31 | { "name": "pot-face", "parent": "pot", "x": -1.06, "y": 28.16, "color": "f38383ff", "icon": "gear" }, 32 | { 33 | "name": "leaf-big", 34 | "parent": "pot", 35 | "length": 46.73, 36 | "rotation": 119.24, 37 | "x": 4.04, 38 | "y": 95.05, 39 | "color": "abe323ff" 40 | }, 41 | { "name": "leaf-big-tip", "parent": "leaf-big", "length": 46.73, "x": 46.73, "color": "abe323ff" }, 42 | { 43 | "name": "leaf-small", 44 | "parent": "pot", 45 | "length": 51.32, 46 | "rotation": 50.93, 47 | "x": 10.16, 48 | "y": 96.81, 49 | "color": "abe323ff" 50 | }, 51 | { 52 | "name": "stem", 53 | "parent": "pot", 54 | "length": 104.76, 55 | "rotation": 90, 56 | "x": 7.24, 57 | "y": 92.61, 58 | "color": "abe323ff" 59 | }, 60 | { "name": "stem2", "parent": "stem", "length": 69.84, "x": 104.76, "color": "abe323ff" }, 61 | { "name": "stem3", "parent": "stem2", "length": 34.92, "x": 69.84, "color": "abe323ff" }, 62 | { 63 | "name": "petal-3", 64 | "parent": "stem3", 65 | "length": 37.74, 66 | "rotation": 1.03, 67 | "x": 30.73, 68 | "y": 0.64, 69 | "color": "2381e3ff" 70 | }, 71 | { 72 | "name": "petal-1", 73 | "parent": "stem3", 74 | "length": 40.11, 75 | "rotation": 70.18, 76 | "x": 34.13, 77 | "y": 3.02, 78 | "color": "2381e3ff" 79 | }, 80 | { 81 | "name": "petal-2", 82 | "parent": "stem3", 83 | "length": 48.62, 84 | "rotation": -80.34, 85 | "x": 32.09, 86 | "y": -4.46, 87 | "color": "2381e3ff" 88 | }, 89 | { "name": "cloud-face", "parent": "cloud", "y": 14.93, "color": "9e82ffff", "icon": "arrowsB" } 90 | ], 91 | "slots": [ 92 | { "name": "rain/rain-green", "bone": "rain-green", "attachment": "rain-green" }, 93 | { "name": "rain/rain-blue", "bone": "rain-blue", "attachment": "rain-blue" }, 94 | { "name": "rain/rain-color", "bone": "rain-color", "attachment": "rain-color" }, 95 | { "name": "rain/rain-white", "bone": "rain-white", "attachment": "rain-white" }, 96 | { "name": "rain/rain-white-reflex", "bone": "rain-white", "attachment": "rain-white-reflex" }, 97 | { "name": "flower/petal-1", "bone": "petal-1", "attachment": "petal-1" }, 98 | { "name": "flower/petal-2", "bone": "petal-2", "attachment": "petal-2" }, 99 | { "name": "flower/petal-3", "bone": "petal-3", "attachment": "petal-3" }, 100 | { "name": "flower/stem", "bone": "stem", "attachment": "stem" }, 101 | { "name": "flower/leaf-big", "bone": "leaf-big", "attachment": "leaf-big" }, 102 | { "name": "flower/leaf-small", "bone": "leaf-small", "attachment": "leaf-small" }, 103 | { "name": "flower/stem-end", "bone": "stem3", "attachment": "stem-end" }, 104 | { "name": "pot/pot-base", "bone": "pot", "attachment": "pot-base" }, 105 | { "name": "pot/pot-mouth", "bone": "pot-face", "attachment": "pot-mouth-smile-big" }, 106 | { "name": "pot/pot-eyes", "bone": "pot-face", "attachment": "pot-eyes-open" }, 107 | { "name": "cloud/cloud-base/cloud-base-1", "bone": "cloud-base-1", "attachment": "cloud-base-1" }, 108 | { "name": "cloud/cloud-base/cloud-base-2", "bone": "cloud-base-2", "attachment": "cloud-base-2" }, 109 | { "name": "cloud/cloud-base/cloud-base-3", "bone": "cloud-base-3", "attachment": "cloud-base-3" }, 110 | { "name": "cloud/cloud-base/cloud-base-4", "bone": "cloud-base-4", "attachment": "cloud-base-4" }, 111 | { "name": "cloud/cloud-base/cloud-base-5", "bone": "cloud-base-5", "attachment": "cloud-base-5" }, 112 | { "name": "cloud/cloud-base/cloud-base-6", "bone": "cloud-base-6", "attachment": "cloud-base-6" }, 113 | { "name": "cloud/cloud-base/cloud-base-7", "bone": "cloud-base-7", "attachment": "cloud-base-7" }, 114 | { "name": "cloud/cloud-base/cloud-base-8", "bone": "cloud-base-8", "attachment": "cloud-base-8" }, 115 | { "name": "cloud/cloud-base/cloud-base-9", "bone": "cloud-base-9", "attachment": "cloud-base-9" }, 116 | { "name": "cloud/cloud-base/cloud-base-10", "bone": "cloud-base-10", "attachment": "cloud-base-10" }, 117 | { "name": "cloud/cloud-cheeks", "bone": "cloud-face", "attachment": "cloud-cheeks" }, 118 | { "name": "cloud/cloud-eyes", "bone": "cloud-face", "attachment": "cloud-eyes-open" }, 119 | { "name": "cloud/cloud-eyes-reflex", "bone": "cloud-face", "attachment": "cloud-eyes-reflex" }, 120 | { "name": "cloud/cloud-mouth", "bone": "cloud-face", "attachment": "cloud-mouth-closed" } 121 | ], 122 | "physics": [ 123 | { 124 | "name": "cloud", 125 | "order": 25, 126 | "bone": "cloud", 127 | "x": 1, 128 | "y": 1, 129 | "inertia": 0.5, 130 | "strength": 172.8, 131 | "damping": 0.8571, 132 | "mass": 3 133 | }, 134 | { 135 | "name": "cloud-face", 136 | "order": 24, 137 | "bone": "cloud-face", 138 | "x": 0.1923, 139 | "y": 0.141, 140 | "limit": 500, 141 | "inertia": 0.5, 142 | "damping": 0.15 143 | }, 144 | { 145 | "name": "pot-face", 146 | "order": 23, 147 | "bone": "pot-face", 148 | "x": 0.1667, 149 | "y": 0.1026, 150 | "limit": 500, 151 | "inertia": 0.5, 152 | "strength": 137.3, 153 | "damping": 0.6078 154 | }, 155 | { 156 | "name": "cloud-base/cloud-base-1", 157 | "order": 4, 158 | "bone": "cloud-base-1", 159 | "x": 1, 160 | "y": 1, 161 | "limit": 500, 162 | "inertia": 0.3741, 163 | "strength": 134.7, 164 | "damping": 0.8163, 165 | "mass": 2.8 166 | }, 167 | { 168 | "name": "cloud-base/cloud-base-2", 169 | "order": 5, 170 | "bone": "cloud-base-2", 171 | "x": 1, 172 | "y": 1, 173 | "limit": 300, 174 | "inertia": 0.3741, 175 | "strength": 134.7, 176 | "damping": 0.8163, 177 | "mass": 2.8 178 | }, 179 | { 180 | "name": "cloud-base/cloud-base-3", 181 | "order": 6, 182 | "bone": "cloud-base-3", 183 | "x": 1, 184 | "y": 1, 185 | "limit": 300, 186 | "inertia": 0.3741, 187 | "strength": 134.7, 188 | "damping": 0.8163, 189 | "mass": 2.8 190 | }, 191 | { 192 | "name": "cloud-base/cloud-base-4", 193 | "order": 7, 194 | "bone": "cloud-base-4", 195 | "x": 1, 196 | "y": 1, 197 | "limit": 500, 198 | "inertia": 0.3741, 199 | "strength": 134.7, 200 | "damping": 0.8163, 201 | "mass": 2.8 202 | }, 203 | { 204 | "name": "cloud-base/cloud-base-5", 205 | "order": 8, 206 | "bone": "cloud-base-5", 207 | "x": 1, 208 | "y": 1, 209 | "limit": 300, 210 | "inertia": 0.3741, 211 | "strength": 134.7, 212 | "damping": 0.8163, 213 | "mass": 2.8 214 | }, 215 | { 216 | "name": "cloud-base/cloud-base-6", 217 | "order": 9, 218 | "bone": "cloud-base-6", 219 | "x": 1, 220 | "y": 1, 221 | "limit": 300, 222 | "inertia": 0.3741, 223 | "strength": 134.7, 224 | "damping": 0.8163, 225 | "mass": 2.8 226 | }, 227 | { 228 | "name": "cloud-base/cloud-base-7", 229 | "order": 10, 230 | "bone": "cloud-base-7", 231 | "x": 1, 232 | "y": 1, 233 | "limit": 300, 234 | "inertia": 0.3741, 235 | "strength": 134.7, 236 | "damping": 0.8163, 237 | "mass": 2.8 238 | }, 239 | { 240 | "name": "cloud-base/cloud-base-8", 241 | "order": 11, 242 | "bone": "cloud-base-8", 243 | "x": 1, 244 | "y": 1, 245 | "limit": 300, 246 | "inertia": 0.3741, 247 | "strength": 134.7, 248 | "damping": 0.8163, 249 | "mass": 2.8 250 | }, 251 | { 252 | "name": "cloud-base/cloud-base-9", 253 | "order": 12, 254 | "bone": "cloud-base-9", 255 | "x": 1, 256 | "y": 1, 257 | "limit": 300, 258 | "inertia": 0.3741, 259 | "strength": 134.7, 260 | "damping": 0.8163, 261 | "mass": 2.8 262 | }, 263 | { 264 | "name": "cloud-base/cloud-base-10", 265 | "order": 13, 266 | "bone": "cloud-base-10", 267 | "x": 1, 268 | "y": 1, 269 | "limit": 300, 270 | "inertia": 0.3741, 271 | "strength": 134.7, 272 | "damping": 0.8163, 273 | "mass": 2.8 274 | }, 275 | { 276 | "name": "plant/leaf-big", 277 | "order": 14, 278 | "bone": "leaf-big", 279 | "rotate": 0.7532, 280 | "shearX": 0.2468, 281 | "limit": 500, 282 | "inertia": 0.5, 283 | "strength": 160.5, 284 | "damping": 0.8367, 285 | "mass": 4 286 | }, 287 | { 288 | "name": "plant/leaf-big-tip", 289 | "order": 22, 290 | "bone": "leaf-big-tip", 291 | "rotate": 1, 292 | "limit": 500, 293 | "inertia": 0.5, 294 | "strength": 160.5, 295 | "damping": 0.8367, 296 | "mass": 4 297 | }, 298 | { 299 | "name": "plant/leaf-small", 300 | "order": 15, 301 | "bone": "leaf-small", 302 | "rotate": 0.6026, 303 | "limit": 500, 304 | "inertia": 0.5, 305 | "strength": 160.5, 306 | "damping": 0.8367, 307 | "mass": 4 308 | }, 309 | { 310 | "name": "plant/petal-1", 311 | "order": 19, 312 | "bone": "petal-1", 313 | "rotate": 1, 314 | "limit": 500, 315 | "inertia": 0.5, 316 | "strength": 164.6, 317 | "damping": 0.6531, 318 | "mass": 2.6 319 | }, 320 | { 321 | "name": "plant/petal-2", 322 | "order": 21, 323 | "bone": "petal-2", 324 | "rotate": 1, 325 | "limit": 500, 326 | "inertia": 0.5, 327 | "strength": 164.6, 328 | "damping": 0.6531, 329 | "mass": 2.6 330 | }, 331 | { 332 | "name": "plant/petal-3", 333 | "order": 20, 334 | "bone": "petal-3", 335 | "rotate": 1, 336 | "limit": 500, 337 | "inertia": 0.5, 338 | "strength": 164.6, 339 | "damping": 0.7823, 340 | "mass": 3.83 341 | }, 342 | { 343 | "name": "plant/stem", 344 | "order": 16, 345 | "bone": "stem", 346 | "rotate": 0.8205, 347 | "limit": 700, 348 | "inertia": 0.5, 349 | "strength": 152.4, 350 | "damping": 0.9388, 351 | "mass": 2.6 352 | }, 353 | { 354 | "name": "plant/stem2", 355 | "order": 17, 356 | "bone": "stem2", 357 | "rotate": 0.8205, 358 | "limit": 700, 359 | "inertia": 0.5, 360 | "strength": 152.4, 361 | "damping": 0.9388, 362 | "mass": 2.6 363 | }, 364 | { 365 | "name": "plant/stem3", 366 | "order": 18, 367 | "bone": "stem3", 368 | "rotate": 0.8205, 369 | "limit": 700, 370 | "inertia": 0.5, 371 | "strength": 152.4, 372 | "damping": 0.9388, 373 | "mass": 2.6 374 | }, 375 | { 376 | "name": "rain/rain-blue", 377 | "order": 3, 378 | "bone": "rain-blue", 379 | "x": 1, 380 | "y": 1, 381 | "strength": 0, 382 | "gravity": 70 383 | }, 384 | { 385 | "name": "rain/rain-color", 386 | "order": 2, 387 | "bone": "rain-color", 388 | "x": 1, 389 | "y": 1, 390 | "strength": 0, 391 | "gravity": 70 392 | }, 393 | { 394 | "name": "rain/rain-green", 395 | "order": 1, 396 | "bone": "rain-green", 397 | "x": 1, 398 | "y": 1, 399 | "strength": 0, 400 | "gravity": 70 401 | }, 402 | { "name": "rain/rain-white", "bone": "rain-white", "x": 1, "y": 1, "strength": 0, "gravity": 70 } 403 | ], 404 | "skins": [ 405 | { 406 | "name": "default", 407 | "attachments": { 408 | "cloud/cloud-base/cloud-base-1": { 409 | "cloud-base-1": { "width": 465, "height": 420 } 410 | }, 411 | "cloud/cloud-base/cloud-base-2": { 412 | "cloud-base-2": { "width": 420, "height": 415 } 413 | }, 414 | "cloud/cloud-base/cloud-base-3": { 415 | "cloud-base-3": { "width": 349, "height": 327 } 416 | }, 417 | "cloud/cloud-base/cloud-base-4": { 418 | "cloud-base-4": { "width": 352, "height": 326 } 419 | }, 420 | "cloud/cloud-base/cloud-base-5": { 421 | "cloud-base-5": { "width": 289, "height": 250 } 422 | }, 423 | "cloud/cloud-base/cloud-base-6": { 424 | "cloud-base-6": { "width": 322, "height": 272 } 425 | }, 426 | "cloud/cloud-base/cloud-base-7": { 427 | "cloud-base-7": { "width": 300, "height": 297 } 428 | }, 429 | "cloud/cloud-base/cloud-base-8": { 430 | "cloud-base-8": { "width": 307, "height": 256 } 431 | }, 432 | "cloud/cloud-base/cloud-base-9": { 433 | "cloud-base-9": { "width": 214, "height": 216 } 434 | }, 435 | "cloud/cloud-base/cloud-base-10": { 436 | "cloud-base-10": { "width": 193, "height": 201 } 437 | }, 438 | "cloud/cloud-cheeks": { 439 | "cloud-cheeks": { "x": -19, "y": -53.93, "width": 435, "height": 158 } 440 | }, 441 | "cloud/cloud-eyes": { 442 | "cloud-eyes-closed": { "x": -10, "y": -5.43, "width": 263, "height": 43 }, 443 | "cloud-eyes-open": { "x": -8, "y": -4.43, "width": 265, "height": 51 } 444 | }, 445 | "cloud/cloud-eyes-reflex": { 446 | "cloud-eyes-reflex": { "x": -10, "y": 2.07, "width": 239, "height": 34 } 447 | }, 448 | "cloud/cloud-mouth": { 449 | "cloud-mouth-closed": { "y": -14.93, "width": 97, "height": 32 }, 450 | "cloud-mouth-open": { "x": -0.5, "y": -27.93, "width": 118, "height": 70 } 451 | }, 452 | "flower/leaf-big": { 453 | "leaf-big": { 454 | "type": "mesh", 455 | "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 ], 456 | "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 ], 457 | "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 ], 458 | "hull": 10, 459 | "edges": [ 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14, 16, 16, 18, 18, 0 ], 460 | "width": 40, 461 | "height": 98 462 | } 463 | }, 464 | "flower/leaf-small": { 465 | "leaf-small": { "x": 25.02, "y": 0.4, "rotation": -91.36, "width": 34, "height": 59 } 466 | }, 467 | "flower/petal-1": { 468 | "petal-1": { "x": 18.88, "y": -4.54, "rotation": -160.18, "width": 52, "height": 36 } 469 | }, 470 | "flower/petal-2": { 471 | "petal-2": { "x": 21.96, "y": 2.06, "rotation": -9.66, "width": 56, "height": 34 } 472 | }, 473 | "flower/petal-3": { 474 | "petal-3": { "x": 16.97, "y": -5.71, "rotation": -91.03, "width": 58, "height": 42 } 475 | }, 476 | "flower/stem": { 477 | "stem": { 478 | "type": "mesh", 479 | "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 ], 480 | "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 ], 481 | "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 ], 482 | "hull": 24, 483 | "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 ], 484 | "width": 16, 485 | "height": 210 486 | } 487 | }, 488 | "flower/stem-end": { 489 | "stem-end": { "x": 25.8, "y": -0.26, "rotation": -90, "width": 25, "height": 26 } 490 | }, 491 | "pot/pot-base": { 492 | "pot-base": { "x": 5, "y": 42, "width": 152, "height": 118 } 493 | }, 494 | "pot/pot-eyes": { 495 | "pot-eyes-closed": { "x": -0.94, "y": 2.34, "width": 92, "height": 17 }, 496 | "pot-eyes-open": { "x": 0.06, "y": 3.84, "width": 80, "height": 22 } 497 | }, 498 | "pot/pot-mouth": { 499 | "pot-mouth-open": { "x": -1.44, "y": -13.66, "width": 27, "height": 31 }, 500 | "pot-mouth-pouty": { "x": 0.56, "y": -12.66, "width": 35, "height": 19 }, 501 | "pot-mouth-smile": { "x": 0.56, "y": -12.16, "width": 27, "height": 20 }, 502 | "pot-mouth-smile-big": { "x": 1.56, "y": -9.16, "width": 39, "height": 18 } 503 | }, 504 | "rain/rain-blue": { 505 | "rain-blue": { "width": 23, "height": 36 } 506 | }, 507 | "rain/rain-color": { 508 | "rain-color": { "width": 18, "height": 34 } 509 | }, 510 | "rain/rain-green": { 511 | "rain-green": { "width": 23, "height": 36 } 512 | }, 513 | "rain/rain-white": { 514 | "rain-white": { "width": 23, "height": 44 } 515 | }, 516 | "rain/rain-white-reflex": { 517 | "rain-white-reflex": { "x": -0.5, "y": 3.5, "width": 10, "height": 19 } 518 | } 519 | } 520 | } 521 | ], 522 | "animations": { 523 | "playing-in-the-rain": { 524 | "slots": { 525 | "cloud/cloud-eyes": { 526 | "attachment": [ 527 | { "time": 0.2, "name": "cloud-eyes-closed" }, 528 | { "time": 0.9, "name": "cloud-eyes-open" }, 529 | { "time": 1.7667, "name": "cloud-eyes-closed" }, 530 | { "time": 1.9333, "name": "cloud-eyes-open" }, 531 | { "time": 2.4333, "name": "cloud-eyes-closed" }, 532 | { "time": 2.6, "name": "cloud-eyes-open" }, 533 | { "time": 3.9333, "name": "cloud-eyes-closed" }, 534 | { "time": 4.1, "name": "cloud-eyes-open" } 535 | ] 536 | }, 537 | "cloud/cloud-mouth": { 538 | "attachment": [ 539 | { "time": 0.2, "name": "cloud-mouth-open" }, 540 | { "time": 0.9, "name": "cloud-mouth-closed" } 541 | ] 542 | }, 543 | "pot/pot-eyes": { 544 | "attachment": [ 545 | { "time": 0.1333, "name": "pot-eyes-closed" }, 546 | { "time": 0.3, "name": "pot-eyes-open" }, 547 | { "time": 1.0667, "name": "pot-eyes-closed" }, 548 | { "time": 1.5, "name": "pot-eyes-open" }, 549 | { "time": 3.0333, "name": "pot-eyes-closed" }, 550 | { "time": 3.2333, "name": "pot-eyes-open" }, 551 | { "time": 3.4667, "name": "pot-eyes-closed" }, 552 | { "time": 3.6667, "name": "pot-eyes-open" } 553 | ] 554 | }, 555 | "pot/pot-mouth": { 556 | "attachment": [ 557 | { "time": 0.1333, "name": "pot-mouth-open" }, 558 | { "time": 0.3, "name": "pot-mouth-smile-big" }, 559 | { "time": 1.0667, "name": "pot-mouth-pouty" }, 560 | { "time": 2.4, "name": "pot-mouth-smile" }, 561 | { "time": 3.0333, "name": "pot-mouth-smile-big" } 562 | ] 563 | } 564 | }, 565 | "bones": { 566 | "pot": { 567 | "rotate": [ 568 | { "time": 1.1 }, 569 | { "time": 1.2, "value": -12.76 }, 570 | { "time": 1.5333, "curve": "stepped" }, 571 | { "time": 3.7667 }, 572 | { "time": 3.9, "value": 8.28 }, 573 | { "time": 4.2333, "value": -4.34 }, 574 | { "time": 4.4333 } 575 | ], 576 | "scale": [ 577 | {}, 578 | { "time": 0.2, "y": 0.752 }, 579 | { "time": 0.4, "x": 0.845, "y": 1.068 }, 580 | { "time": 0.6333 } 581 | ] 582 | }, 583 | "pot-control": { 584 | "translatex": [ 585 | { 586 | "time": 1.0667, 587 | "curve": [ 1.222, -203.48, 1.378, -610.44 ] 588 | }, 589 | { "time": 1.5333, "value": -610.44, "curve": "stepped" }, 590 | { 591 | "time": 2.2333, 592 | "value": -610.44, 593 | "curve": [ 2.389, -610.44, 2.544, -478.45 ] 594 | }, 595 | { "time": 2.7, "value": -478.45, "curve": "stepped" }, 596 | { 597 | "time": 3.8333, 598 | "value": -478.45, 599 | "curve": [ 3.971, -478.45, 4.095, -135.56 ] 600 | }, 601 | { "time": 4.2333 } 602 | ], 603 | "translatey": [ 604 | { 605 | "time": 1.0333, 606 | "curve": [ 1.089, 10.56, 1.144, 44.34 ] 607 | }, 608 | { 609 | "time": 1.2, 610 | "value": 44.34, 611 | "curve": [ 1.256, 44.34, 1.311, 0 ] 612 | }, 613 | { "time": 1.3667, "curve": "stepped" }, 614 | { 615 | "time": 2.2333, 616 | "curve": [ 2.408, 0, 2.392, 44.34 ] 617 | }, 618 | { 619 | "time": 2.4333, 620 | "value": 44.34, 621 | "curve": [ 2.455, 44.34, 2.51, 0 ] 622 | }, 623 | { "time": 2.6, "curve": "stepped" }, 624 | { 625 | "time": 3.8, 626 | "curve": [ 3.841, 14.78, 3.893, 44.34 ] 627 | }, 628 | { 629 | "time": 3.9333, 630 | "value": 44.34, 631 | "curve": [ 4.023, 44.34, 4.111, 14.78 ] 632 | }, 633 | { "time": 4.2 } 634 | ] 635 | }, 636 | "cloud-base-1": { 637 | "rotate": [ 638 | { 639 | "curve": [ 0.144, -9.36, 0.289, -17.29 ] 640 | }, 641 | { 642 | "time": 0.4333, 643 | "value": -17.29, 644 | "curve": [ 0.5, -17.29, 0.567, -4.32 ] 645 | }, 646 | { "time": 0.6333 } 647 | ], 648 | "scale": [ 649 | { 650 | "curve": [ 0.089, 1, 0.178, 1.064, 0.089, 1, 0.178, 1.064 ] 651 | }, 652 | { 653 | "time": 0.2667, 654 | "x": 1.064, 655 | "y": 1.064, 656 | "curve": [ 0.411, 1.064, 0.556, 1.021, 0.411, 1.064, 0.556, 1.021 ] 657 | }, 658 | { "time": 0.7 } 659 | ] 660 | }, 661 | "cloud-base-4": { 662 | "rotate": [ 663 | { 664 | "curve": [ 0.1, 5.55, 0.2, 14.81 ] 665 | }, 666 | { 667 | "time": 0.3, 668 | "value": 14.81, 669 | "curve": [ 0.467, 14.81, 0.633, 9.25 ] 670 | }, 671 | { "time": 0.8 } 672 | ], 673 | "scale": [ 674 | { 675 | "curve": [ 0.089, 1, 0.178, 1.064, 0.089, 1, 0.178, 1.064 ] 676 | }, 677 | { 678 | "time": 0.2667, 679 | "x": 1.064, 680 | "y": 1.064, 681 | "curve": [ 0.411, 1.064, 0.556, 1.021, 0.411, 1.064, 0.556, 1.021 ] 682 | }, 683 | { "time": 0.7 } 684 | ] 685 | }, 686 | "cloud": { 687 | "translate": [ 688 | { "time": 0.2333 }, 689 | { "time": 0.3333, "y": 30.43 }, 690 | { "time": 0.4667 }, 691 | { "time": 0.5667, "y": 30.43 }, 692 | { "time": 0.6667 }, 693 | { "time": 0.7667, "y": 30.43 }, 694 | { "time": 0.9333 } 695 | ] 696 | } 697 | }, 698 | "physics": { 699 | "rain/rain-blue": { 700 | "reset": [ 701 | { "time": 0.4667 }, 702 | { "time": 0.9333 }, 703 | { "time": 1.4 }, 704 | { "time": 1.8667 }, 705 | { "time": 2.3333 }, 706 | { "time": 2.8 }, 707 | { "time": 3.2667 }, 708 | { "time": 3.7333 }, 709 | { "time": 4.2 }, 710 | { "time": 4.6667 } 711 | ] 712 | }, 713 | "rain/rain-color": { 714 | "reset": [ 715 | { "time": 0.3 }, 716 | { "time": 0.7667 }, 717 | { "time": 1.2333 }, 718 | { "time": 1.7 }, 719 | { "time": 2.1667 }, 720 | { "time": 2.6333 }, 721 | { "time": 3.1 }, 722 | { "time": 3.5667 }, 723 | { "time": 4.0333 }, 724 | { "time": 4.5 } 725 | ] 726 | }, 727 | "rain/rain-green": { 728 | "reset": [ 729 | { "time": 0.1333 }, 730 | { "time": 0.6 }, 731 | { "time": 1.0667 }, 732 | { "time": 1.5333 }, 733 | { "time": 2 }, 734 | { "time": 2.4667 }, 735 | { "time": 2.9333 }, 736 | { "time": 3.4 }, 737 | { "time": 3.8667 }, 738 | { "time": 4.3333 } 739 | ] 740 | }, 741 | "rain/rain-white": { 742 | "reset": [ 743 | {}, 744 | { "time": 0.4667 }, 745 | { "time": 0.9333 }, 746 | { "time": 1.4 }, 747 | { "time": 1.8667 }, 748 | { "time": 2.3333 }, 749 | { "time": 2.8 }, 750 | { "time": 3.2667 }, 751 | { "time": 3.7333 }, 752 | { "time": 4.2 } 753 | ] 754 | } 755 | } 756 | }, 757 | "pot-moving-followed-by-rain": { 758 | "bones": { 759 | "pot-control": { 760 | "translate": [ 761 | {}, 762 | { "time": 0.5667, "x": -389.34, "curve": "stepped" }, 763 | { "time": 1.1667, "x": -389.34 }, 764 | { "time": 2.2, "x": 463.88, "curve": "stepped" }, 765 | { "time": 2.4667, "x": 463.88 }, 766 | { "time": 3 } 767 | ] 768 | } 769 | }, 770 | "physics": { 771 | "rain/rain-blue": { 772 | "reset": [ 773 | { "time": 0.4667 }, 774 | { "time": 0.9333 }, 775 | { "time": 1.4 }, 776 | { "time": 1.8667 }, 777 | { "time": 2.3333 }, 778 | { "time": 2.8 }, 779 | { "time": 3.2667 } 780 | ] 781 | }, 782 | "rain/rain-color": { 783 | "reset": [ 784 | { "time": 0.3 }, 785 | { "time": 0.7667 }, 786 | { "time": 1.2333 }, 787 | { "time": 1.7 }, 788 | { "time": 2.1667 }, 789 | { "time": 2.6333 }, 790 | { "time": 3.1 } 791 | ] 792 | }, 793 | "rain/rain-green": { 794 | "reset": [ 795 | { "time": 0.1333 }, 796 | { "time": 0.6 }, 797 | { "time": 1.0667 }, 798 | { "time": 1.5333 }, 799 | { "time": 2 }, 800 | { "time": 2.4667 }, 801 | { "time": 2.9333 } 802 | ] 803 | }, 804 | "rain/rain-white": { 805 | "reset": [ 806 | {}, 807 | { "time": 0.4667 }, 808 | { "time": 0.9333 }, 809 | { "time": 1.4 }, 810 | { "time": 1.8667 }, 811 | { "time": 2.3333 }, 812 | { "time": 2.8 } 813 | ] 814 | } 815 | } 816 | }, 817 | "rain": { 818 | "physics": { 819 | "rain/rain-blue": { 820 | "reset": [ 821 | { "time": 0.4667 } 822 | ] 823 | }, 824 | "rain/rain-color": { 825 | "reset": [ 826 | { "time": 0.3 } 827 | ] 828 | }, 829 | "rain/rain-green": { 830 | "reset": [ 831 | { "time": 0.1333 } 832 | ] 833 | }, 834 | "rain/rain-white": { 835 | "reset": [ 836 | {} 837 | ] 838 | } 839 | } 840 | } 841 | } 842 | } -------------------------------------------------------------------------------- /examples/assets/cloud-pot.skel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/cloud-pot.skel -------------------------------------------------------------------------------- /examples/assets/coin-pma.atlas: -------------------------------------------------------------------------------- 1 | coin-pma.png 2 | size: 1024, 1024 3 | filter: Linear, Linear 4 | pma: true 5 | coin-front-logo 6 | bounds: 2, 609, 305, 302 7 | coin-front-shine-logo 8 | bounds: 309, 629, 282, 282 9 | coin-front-shine-spineboy 10 | bounds: 2, 21, 282, 282 11 | coin-front-spineboy 12 | bounds: 2, 305, 305, 302 13 | coin-side-round 14 | bounds: 309, 345, 144, 282 15 | coin-side-straight 16 | bounds: 2, 2, 17, 282 17 | rotate: 90 18 | shine 19 | bounds: 593, 666, 72, 245 20 | -------------------------------------------------------------------------------- /examples/assets/coin-pma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/coin-pma.png -------------------------------------------------------------------------------- /examples/assets/coin-pro.skel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/coin-pro.skel -------------------------------------------------------------------------------- /examples/assets/mix-and-match-pma.atlas: -------------------------------------------------------------------------------- 1 | mix-and-match-pma.png 2 | size: 1024, 512 3 | filter: Linear, Linear 4 | pma: true 5 | scale: 0.5 6 | base-head 7 | bounds: 118, 70, 95, 73 8 | boy/arm-front 9 | bounds: 831, 311, 36, 115 10 | rotate: 90 11 | boy/backpack 12 | bounds: 249, 357, 119, 153 13 | boy/backpack-pocket 14 | bounds: 628, 193, 34, 62 15 | rotate: 90 16 | boy/backpack-strap-front 17 | bounds: 330, 263, 38, 88 18 | rotate: 90 19 | boy/backpack-up 20 | bounds: 482, 171, 21, 70 21 | boy/body 22 | bounds: 845, 413, 97, 132 23 | rotate: 90 24 | boy/boot-ribbon-front 25 | bounds: 234, 304, 9, 11 26 | boy/collar 27 | bounds: 471, 243, 73, 29 28 | rotate: 90 29 | boy/ear 30 | bounds: 991, 352, 19, 23 31 | rotate: 90 32 | boy/eye-back-low-eyelid 33 | bounds: 66, 72, 17, 6 34 | boy/eye-back-pupil 35 | bounds: 694, 279, 8, 9 36 | rotate: 90 37 | boy/eye-back-up-eyelid 38 | bounds: 460, 101, 23, 5 39 | rotate: 90 40 | boy/eye-back-up-eyelid-back 41 | bounds: 979, 414, 19, 10 42 | rotate: 90 43 | boy/eye-front-low-eyelid 44 | bounds: 1015, 203, 22, 7 45 | rotate: 90 46 | boy/eye-front-pupil 47 | bounds: 309, 50, 9, 9 48 | boy/eye-front-up-eyelid 49 | bounds: 991, 373, 31, 6 50 | boy/eye-front-up-eyelid-back 51 | bounds: 107, 76, 26, 9 52 | rotate: 90 53 | boy/eye-iris-back 54 | bounds: 810, 260, 17, 17 55 | boy/eye-iris-front 56 | bounds: 902, 230, 18, 18 57 | boy/eye-white-back 58 | bounds: 599, 179, 20, 12 59 | boy/eye-white-front 60 | bounds: 544, 183, 27, 13 61 | boy/eyebrow-back 62 | bounds: 1002, 225, 20, 11 63 | rotate: 90 64 | boy/eyebrow-front 65 | bounds: 975, 234, 25, 11 66 | boy/hair-back 67 | bounds: 629, 289, 122, 81 68 | rotate: 90 69 | boy/hair-bangs 70 | bounds: 505, 180, 70, 37 71 | rotate: 90 72 | boy/hair-side 73 | bounds: 979, 435, 25, 43 74 | rotate: 90 75 | boy/hand-backfingers 76 | bounds: 858, 183, 19, 21 77 | boy/hand-front-fingers 78 | bounds: 879, 183, 19, 21 79 | boy/hat 80 | bounds: 218, 121, 93, 56 81 | boy/leg-front 82 | bounds: 85, 104, 31, 158 83 | boy/mouth-close 84 | bounds: 467, 100, 21, 5 85 | girl-blue-cape/mouth-close 86 | bounds: 467, 100, 21, 5 87 | girl-spring-dress/mouth-close 88 | bounds: 467, 100, 21, 5 89 | girl/mouth-close 90 | bounds: 467, 100, 21, 5 91 | boy/mouth-smile 92 | bounds: 1015, 258, 29, 7 93 | rotate: 90 94 | boy/nose 95 | bounds: 323, 79, 17, 10 96 | boy/pompom 97 | bounds: 979, 462, 48, 43 98 | rotate: 90 99 | boy/zip 100 | bounds: 922, 231, 14, 23 101 | rotate: 90 102 | girl-blue-cape/back-eyebrow 103 | bounds: 527, 106, 18, 12 104 | rotate: 90 105 | girl-blue-cape/body-dress 106 | bounds: 2, 264, 109, 246 107 | girl-blue-cape/body-ribbon 108 | bounds: 576, 193, 50, 38 109 | girl-blue-cape/cape-back 110 | bounds: 113, 317, 134, 193 111 | girl-blue-cape/cape-back-up 112 | bounds: 504, 305, 123, 106 113 | girl-blue-cape/cape-ribbon 114 | bounds: 396, 118, 50, 18 115 | rotate: 90 116 | girl-blue-cape/cape-shoulder-back 117 | bounds: 420, 243, 49, 59 118 | girl-blue-cape/cape-shoulder-front 119 | bounds: 2, 2, 62, 76 120 | girl-blue-cape/cape-up-front 121 | bounds: 118, 145, 98, 117 122 | girl-blue-cape/ear 123 | bounds: 837, 181, 19, 23 124 | girl-spring-dress/ear 125 | bounds: 837, 181, 19, 23 126 | girl/ear 127 | bounds: 837, 181, 19, 23 128 | girl-blue-cape/eye-back-low-eyelid 129 | bounds: 810, 252, 17, 6 130 | girl-spring-dress/eye-back-low-eyelid 131 | bounds: 810, 252, 17, 6 132 | girl/eye-back-low-eyelid 133 | bounds: 810, 252, 17, 6 134 | girl-blue-cape/eye-back-pupil 135 | bounds: 309, 40, 8, 9 136 | rotate: 90 137 | girl-spring-dress/eye-back-pupil 138 | bounds: 309, 40, 8, 9 139 | rotate: 90 140 | girl/eye-back-pupil 141 | bounds: 309, 40, 8, 9 142 | rotate: 90 143 | girl-blue-cape/eye-back-up-eyelid 144 | bounds: 573, 179, 24, 12 145 | girl-spring-dress/eye-back-up-eyelid 146 | bounds: 573, 179, 24, 12 147 | girl/eye-back-up-eyelid 148 | bounds: 573, 179, 24, 12 149 | girl-blue-cape/eye-back-up-eyelid-back 150 | bounds: 380, 105, 17, 11 151 | rotate: 90 152 | girl-spring-dress/eye-back-up-eyelid-back 153 | bounds: 380, 105, 17, 11 154 | rotate: 90 155 | girl/eye-back-up-eyelid-back 156 | bounds: 380, 105, 17, 11 157 | rotate: 90 158 | girl-blue-cape/eye-front-low-eyelid 159 | bounds: 1016, 353, 18, 6 160 | rotate: 90 161 | girl-spring-dress/eye-front-low-eyelid 162 | bounds: 1016, 353, 18, 6 163 | rotate: 90 164 | girl/eye-front-low-eyelid 165 | bounds: 1016, 353, 18, 6 166 | rotate: 90 167 | girl-blue-cape/eye-front-pupil 168 | bounds: 363, 94, 9, 9 169 | girl-spring-dress/eye-front-pupil 170 | bounds: 363, 94, 9, 9 171 | girl/eye-front-pupil 172 | bounds: 363, 94, 9, 9 173 | girl-blue-cape/eye-front-up-eyelid 174 | bounds: 679, 413, 30, 14 175 | rotate: 90 176 | girl-spring-dress/eye-front-up-eyelid 177 | bounds: 679, 413, 30, 14 178 | rotate: 90 179 | girl/eye-front-up-eyelid 180 | bounds: 679, 413, 30, 14 181 | rotate: 90 182 | girl-blue-cape/eye-front-up-eyelid-back 183 | bounds: 947, 234, 26, 11 184 | girl-spring-dress/eye-front-up-eyelid-back 185 | bounds: 947, 234, 26, 11 186 | girl/eye-front-up-eyelid-back 187 | bounds: 947, 234, 26, 11 188 | girl-blue-cape/eye-iris-back 189 | bounds: 323, 105, 17, 17 190 | girl-blue-cape/eye-iris-front 191 | bounds: 467, 107, 18, 18 192 | girl-blue-cape/eye-white-back 193 | bounds: 621, 175, 20, 16 194 | girl-spring-dress/eye-white-back 195 | bounds: 621, 175, 20, 16 196 | girl-blue-cape/eye-white-front 197 | bounds: 643, 175, 20, 16 198 | girl-spring-dress/eye-white-front 199 | bounds: 643, 175, 20, 16 200 | girl/eye-white-front 201 | bounds: 643, 175, 20, 16 202 | girl-blue-cape/front-eyebrow 203 | bounds: 309, 101, 18, 12 204 | rotate: 90 205 | girl-blue-cape/hair-back 206 | bounds: 712, 317, 117, 98 207 | girl-blue-cape/hair-bangs 208 | bounds: 313, 170, 91, 40 209 | rotate: 90 210 | girl-blue-cape/hair-head-side-back 211 | bounds: 544, 198, 30, 52 212 | girl-blue-cape/hair-head-side-front 213 | bounds: 466, 127, 41, 42 214 | girl-blue-cape/hair-side 215 | bounds: 175, 2, 36, 71 216 | rotate: 90 217 | girl-blue-cape/hand-front-fingers 218 | bounds: 902, 207, 19, 21 219 | girl-spring-dress/hand-front-fingers 220 | bounds: 902, 207, 19, 21 221 | girl-blue-cape/leg-front 222 | bounds: 519, 413, 30, 158 223 | rotate: 90 224 | girl-blue-cape/mouth-smile 225 | bounds: 1015, 227, 29, 7 226 | rotate: 90 227 | girl-spring-dress/mouth-smile 228 | bounds: 1015, 227, 29, 7 229 | rotate: 90 230 | girl/mouth-smile 231 | bounds: 1015, 227, 29, 7 232 | rotate: 90 233 | girl-blue-cape/nose 234 | bounds: 342, 82, 11, 7 235 | girl-spring-dress/nose 236 | bounds: 342, 82, 11, 7 237 | girl/nose 238 | bounds: 342, 82, 11, 7 239 | girl-blue-cape/sleeve-back 240 | bounds: 416, 95, 42, 29 241 | girl-blue-cape/sleeve-front 242 | bounds: 249, 303, 52, 119 243 | rotate: 90 244 | girl-spring-dress/arm-front 245 | bounds: 829, 292, 17, 111 246 | rotate: 90 247 | girl-spring-dress/back-eyebrow 248 | bounds: 309, 81, 18, 12 249 | rotate: 90 250 | girl-spring-dress/body-up 251 | bounds: 66, 2, 64, 66 252 | girl-spring-dress/cloak-down 253 | bounds: 758, 227, 50, 50 254 | girl-spring-dress/cloak-up 255 | bounds: 628, 229, 64, 58 256 | girl-spring-dress/eye-iris-back 257 | bounds: 342, 105, 17, 17 258 | girl-spring-dress/eye-iris-front 259 | bounds: 487, 107, 18, 18 260 | girl-spring-dress/front-eyebrow 261 | bounds: 323, 91, 18, 12 262 | girl-spring-dress/hair-back 263 | bounds: 370, 417, 147, 93 264 | girl-spring-dress/hair-bangs 265 | bounds: 829, 250, 91, 40 266 | girl-spring-dress/hair-head-side-back 267 | bounds: 509, 126, 30, 52 268 | girl-spring-dress/hair-head-side-front 269 | bounds: 816, 206, 41, 42 270 | girl-spring-dress/hair-side 271 | bounds: 248, 2, 36, 71 272 | rotate: 90 273 | girl-spring-dress/leg-front 274 | bounds: 831, 381, 30, 158 275 | rotate: 90 276 | girl-spring-dress/neck 277 | bounds: 85, 70, 20, 32 278 | girl-spring-dress/shoulder-ribbon 279 | bounds: 175, 44, 36, 24 280 | girl-spring-dress/skirt 281 | bounds: 2, 80, 182, 81 282 | rotate: 90 283 | girl-spring-dress/underskirt 284 | bounds: 519, 445, 175, 65 285 | girl/arm-front 286 | bounds: 712, 279, 36, 115 287 | rotate: 90 288 | girl/back-eyebrow 289 | bounds: 309, 61, 18, 12 290 | rotate: 90 291 | girl/bag-base 292 | bounds: 694, 219, 62, 58 293 | girl/bag-strap-front 294 | bounds: 370, 304, 12, 96 295 | rotate: 90 296 | girl/bag-top 297 | bounds: 765, 175, 49, 50 298 | girl/body 299 | bounds: 370, 318, 97, 132 300 | rotate: 90 301 | girl/boot-ribbon-front 302 | bounds: 323, 64, 13, 13 303 | girl/eye-iris-back 304 | bounds: 361, 105, 17, 17 305 | girl/eye-iris-front 306 | bounds: 507, 106, 18, 18 307 | girl/eye-white-back 308 | bounds: 665, 175, 20, 16 309 | girl/front-eyebrow 310 | bounds: 343, 91, 18, 12 311 | girl/hair-back 312 | bounds: 696, 417, 147, 93 313 | girl/hair-bangs 314 | bounds: 922, 247, 91, 40 315 | girl/hair-flap-down-front 316 | bounds: 415, 171, 70, 65 317 | rotate: 90 318 | girl/hair-head-side-back 319 | bounds: 991, 381, 30, 52 320 | girl/hair-head-side-front 321 | bounds: 859, 206, 41, 42 322 | girl/hair-patch 323 | bounds: 132, 2, 66, 41 324 | rotate: 90 325 | girl/hair-side 326 | bounds: 692, 181, 36, 71 327 | rotate: 90 328 | girl/hair-strand-back-1 329 | bounds: 948, 289, 58, 74 330 | rotate: 90 331 | girl/hair-strand-back-2 332 | bounds: 355, 170, 91, 58 333 | rotate: 90 334 | girl/hair-strand-back-3 335 | bounds: 215, 40, 92, 79 336 | girl/hair-strand-front-1 337 | bounds: 234, 263, 38, 94 338 | rotate: 90 339 | girl/hair-strand-front-2 340 | bounds: 576, 233, 70, 50 341 | rotate: 90 342 | girl/hair-strand-front-3 343 | bounds: 313, 124, 44, 81 344 | rotate: 90 345 | girl/hand-front-fingers 346 | bounds: 923, 208, 19, 21 347 | girl/hat 348 | bounds: 218, 179, 93, 82 349 | girl/leg-front 350 | bounds: 831, 349, 30, 158 351 | rotate: 90 352 | girl/pompom 353 | bounds: 416, 126, 48, 43 354 | girl/scarf 355 | bounds: 113, 264, 119, 51 356 | girl/scarf-back 357 | bounds: 502, 252, 72, 51 358 | girl/zip 359 | bounds: 816, 179, 19, 25 360 | -------------------------------------------------------------------------------- /examples/assets/mix-and-match-pma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/mix-and-match-pma.png -------------------------------------------------------------------------------- /examples/assets/mix-and-match-pro.skel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/mix-and-match-pro.skel -------------------------------------------------------------------------------- /examples/assets/raptor-pma.atlas: -------------------------------------------------------------------------------- 1 | raptor-pma.png 2 | size: 1024, 512 3 | filter: Linear, Linear 4 | pma: true 5 | scale: 0.5 6 | back-arm 7 | bounds: 829, 88, 46, 25 8 | rotate: 90 9 | back-bracer 10 | bounds: 195, 238, 39, 28 11 | rotate: 90 12 | back-hand 13 | bounds: 724, 140, 36, 34 14 | rotate: 90 15 | back-knee 16 | bounds: 760, 131, 49, 67 17 | rotate: 90 18 | back-thigh 19 | bounds: 225, 238, 39, 24 20 | rotate: 90 21 | eyes-open 22 | bounds: 975, 204, 47, 45 23 | front-arm 24 | bounds: 969, 112, 48, 26 25 | front-bracer 26 | bounds: 724, 97, 41, 29 27 | rotate: 90 28 | front-hand 29 | bounds: 251, 239, 41, 38 30 | front-open-hand 31 | bounds: 856, 76, 43, 44 32 | rotate: 90 33 | front-thigh 34 | bounds: 729, 178, 57, 29 35 | rotate: 90 36 | gun 37 | bounds: 894, 251, 107, 103 38 | gun-nohand 39 | bounds: 764, 241, 105, 102 40 | head 41 | bounds: 756, 345, 136, 149 42 | lower-leg 43 | bounds: 475, 237, 73, 98 44 | rotate: 90 45 | mouth-grind 46 | bounds: 975, 172, 47, 30 47 | mouth-smile 48 | bounds: 975, 140, 47, 30 49 | neck 50 | bounds: 366, 282, 18, 21 51 | raptor-back-arm 52 | bounds: 636, 97, 82, 86 53 | rotate: 90 54 | raptor-body 55 | bounds: 2, 2, 632, 233 56 | raptor-front-arm 57 | bounds: 871, 168, 81, 102 58 | rotate: 90 59 | raptor-front-leg 60 | bounds: 2, 237, 191, 257 61 | raptor-hindleg-back 62 | bounds: 195, 279, 169, 215 63 | raptor-horn 64 | bounds: 431, 312, 182, 80 65 | rotate: 90 66 | raptor-horn-back 67 | bounds: 513, 318, 176, 77 68 | rotate: 90 69 | raptor-jaw 70 | bounds: 894, 356, 126, 138 71 | raptor-jaw-tooth 72 | bounds: 294, 240, 37, 48 73 | rotate: 90 74 | raptor-mouth-inside 75 | bounds: 344, 241, 36, 41 76 | rotate: 90 77 | raptor-saddle-strap-back 78 | bounds: 575, 242, 54, 74 79 | raptor-saddle-strap-front 80 | bounds: 764, 182, 57, 95 81 | rotate: 90 82 | raptor-saddle-w-shadow 83 | bounds: 592, 323, 162, 171 84 | raptor-tail-shadow 85 | bounds: 366, 305, 189, 63 86 | rotate: 90 87 | raptor-tongue 88 | bounds: 387, 239, 86, 64 89 | stirrup-back 90 | bounds: 829, 136, 44, 35 91 | rotate: 90 92 | stirrup-front 93 | bounds: 866, 121, 45, 50 94 | rotate: 90 95 | stirrup-strap 96 | bounds: 918, 120, 49, 46 97 | torso 98 | bounds: 636, 181, 54, 91 99 | rotate: 90 100 | visor 101 | bounds: 631, 237, 131, 84 102 | -------------------------------------------------------------------------------- /examples/assets/raptor-pma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/raptor-pma.png -------------------------------------------------------------------------------- /examples/assets/raptor.atlas: -------------------------------------------------------------------------------- 1 | raptor.png 2 | size: 1024, 512 3 | filter: Linear, Linear 4 | scale: 0.5 5 | back-arm 6 | bounds: 895, 295, 46, 25 7 | back-bracer 8 | bounds: 992, 216, 39, 28 9 | rotate: 90 10 | back-hand 11 | bounds: 594, 58, 36, 34 12 | back-knee 13 | bounds: 729, 86, 49, 67 14 | rotate: 90 15 | back-thigh 16 | bounds: 379, 2, 39, 24 17 | eyes-open 18 | bounds: 902, 194, 47, 45 19 | rotate: 90 20 | front-arm 21 | bounds: 945, 306, 48, 26 22 | front-bracer 23 | bounds: 949, 197, 41, 29 24 | front-hand 25 | bounds: 949, 266, 41, 38 26 | front-open-hand 27 | bounds: 875, 148, 43, 44 28 | front-thigh 29 | bounds: 793, 171, 57, 29 30 | rotate: 90 31 | gun 32 | bounds: 379, 28, 107, 103 33 | rotate: 90 34 | gun-nohand 35 | bounds: 487, 87, 105, 102 36 | head 37 | bounds: 807, 361, 136, 149 38 | lower-leg 39 | bounds: 827, 195, 73, 98 40 | mouth-grind 41 | bounds: 920, 145, 47, 30 42 | rotate: 90 43 | mouth-smile 44 | bounds: 992, 257, 47, 30 45 | rotate: 90 46 | neck 47 | bounds: 359, 114, 18, 21 48 | raptor-back-arm 49 | bounds: 653, 142, 82, 86 50 | raptor-body 51 | bounds: 2, 277, 632, 233 52 | raptor-front-arm 53 | bounds: 484, 4, 81, 102 54 | rotate: 90 55 | raptor-front-leg 56 | bounds: 2, 18, 191, 257 57 | raptor-hindleg-back 58 | bounds: 636, 295, 169, 215 59 | raptor-horn 60 | bounds: 195, 22, 182, 80 61 | raptor-horn-back 62 | bounds: 945, 334, 176, 77 63 | rotate: 90 64 | raptor-jaw 65 | bounds: 359, 137, 126, 138 66 | raptor-jaw-tooth 67 | bounds: 895, 322, 37, 48 68 | rotate: 90 69 | raptor-mouth-inside 70 | bounds: 949, 228, 36, 41 71 | rotate: 90 72 | raptor-saddle-strap-back 73 | bounds: 653, 86, 54, 74 74 | rotate: 90 75 | raptor-saddle-strap-front 76 | bounds: 594, 94, 57, 95 77 | raptor-saddle-w-shadow 78 | bounds: 195, 104, 162, 171 79 | raptor-tail-shadow 80 | bounds: 636, 230, 189, 63 81 | raptor-tongue 82 | bounds: 807, 295, 86, 64 83 | stirrup-back 84 | bounds: 952, 151, 44, 35 85 | rotate: 90 86 | stirrup-front 87 | bounds: 902, 243, 45, 50 88 | stirrup-strap 89 | bounds: 824, 147, 49, 46 90 | torso 91 | bounds: 737, 137, 54, 91 92 | visor 93 | bounds: 487, 191, 131, 84 94 | -------------------------------------------------------------------------------- /examples/assets/raptor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/raptor.png -------------------------------------------------------------------------------- /examples/assets/sack-pma.atlas: -------------------------------------------------------------------------------- 1 | sack-pma.png 2 | size: 512, 512 3 | filter: Linear, Linear 4 | pma: true 5 | scale: 0.5 6 | cape-back 7 | bounds: 237, 149, 260, 260 8 | cape-front 9 | bounds: 237, 43, 200, 104 10 | sack 11 | bounds: 2, 2, 233, 407 12 | -------------------------------------------------------------------------------- /examples/assets/sack-pma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/sack-pma.png -------------------------------------------------------------------------------- /examples/assets/sack-pro.skel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/sack-pro.skel -------------------------------------------------------------------------------- /examples/assets/snowglobe-pma.atlas: -------------------------------------------------------------------------------- 1 | snowglobe-pma.png 2 | size: 1024, 1024 3 | filter: Linear, Linear 4 | pma: true 5 | scale: 0.5 6 | arm-down-r 7 | bounds: 884, 129, 76, 53 8 | arm-up-l 9 | bounds: 718, 23, 49, 114 10 | rotate: 90 11 | arm-up-r 12 | bounds: 867, 69, 58, 104 13 | rotate: 90 14 | blue-present-base 15 | bounds: 884, 883, 126, 139 16 | eye-reflex-l 17 | bounds: 991, 347, 12, 13 18 | eye-reflex-r 19 | bounds: 867, 129, 10, 12 20 | rotate: 90 21 | eye-white-l 22 | bounds: 987, 697, 35, 43 23 | eye-white-r 24 | bounds: 560, 2, 34, 48 25 | eyelashes-l 26 | bounds: 982, 2, 32, 40 27 | gift-base 28 | bounds: 884, 335, 125, 105 29 | rotate: 90 30 | gift-decoration 31 | bounds: 518, 2, 48, 40 32 | rotate: 90 33 | globe-borders 34 | bounds: 2, 141, 880, 881 35 | glove-l 36 | bounds: 982, 44, 40, 61 37 | glove-shadow-l 38 | bounds: 991, 403, 28, 57 39 | glove-shadow-r 40 | bounds: 960, 204, 38, 62 41 | rotate: 90 42 | green-present-base 43 | bounds: 138, 13, 126, 139 44 | rotate: 90 45 | hair-front 46 | bounds: 884, 590, 150, 101 47 | rotate: 90 48 | hair-side 49 | bounds: 995, 574, 27, 53 50 | hair-strand-2 51 | bounds: 987, 629, 26, 66 52 | hair-strand-5 53 | bounds: 690, 7, 25, 47 54 | hair-strand-6 55 | bounds: 995, 507, 14, 35 56 | head-base 57 | bounds: 2, 4, 134, 135 58 | leg-down-l 59 | bounds: 596, 3, 92, 51 60 | leg-up-l 61 | bounds: 718, 74, 65, 147 62 | rotate: 90 63 | leg-up-l-fuzzy 64 | bounds: 834, 2, 73, 65 65 | leg-up-r 66 | bounds: 576, 56, 83, 140 67 | rotate: 90 68 | leg-up-r-fuzzy 69 | bounds: 909, 2, 65, 71 70 | rotate: 90 71 | mouth 72 | bounds: 991, 362, 39, 13 73 | rotate: 90 74 | neck-scarf 75 | bounds: 279, 25, 142, 114 76 | nose 77 | bounds: 995, 488, 17, 14 78 | rotate: 90 79 | nose-shadow 80 | bounds: 299, 8, 15, 15 81 | red-present-base 82 | bounds: 884, 742, 126, 139 83 | scarf-end-l 84 | bounds: 884, 462, 126, 109 85 | rotate: 90 86 | scarf-end-r 87 | bounds: 423, 52, 151, 87 88 | scarf-ribbon-middle-r 89 | bounds: 960, 244, 62, 89 90 | scarf-shadow 91 | bounds: 884, 184, 149, 74 92 | rotate: 90 93 | shoe-l 94 | bounds: 973, 107, 49, 95 95 | shoe-r 96 | bounds: 423, 6, 44, 93 97 | rotate: 90 98 | shoelace 99 | bounds: 279, 2, 21, 18 100 | rotate: 90 101 | snow 102 | bounds: 995, 544, 27, 28 103 | string 104 | bounds: 138, 6, 5, 53 105 | rotate: 90 106 | 107 | snowglobe-pma_2.png 108 | size: 1024, 1024 109 | filter: Linear, Linear 110 | pma: true 111 | scale: 0.5 112 | arm-down-l 113 | bounds: 884, 579, 56, 54 114 | arm-down-l-fuzzy 115 | bounds: 884, 635, 57, 59 116 | arm-down-r-fuzzy 117 | bounds: 884, 696, 61, 66 118 | blue-present-decoration 119 | bounds: 884, 216, 41, 40 120 | green-present-decoration 121 | bounds: 884, 216, 41, 40 122 | ear-l 123 | bounds: 884, 527, 55, 50 124 | ear-r 125 | bounds: 291, 94, 45, 66 126 | rotate: 90 127 | eyelashes-r 128 | bounds: 2, 2, 32, 47 129 | rotate: 90 130 | globe-texture-strong 131 | bounds: 2, 141, 880, 881 132 | glove-fingers-l 133 | bounds: 884, 361, 39, 51 134 | glove-fingers-r 135 | bounds: 884, 469, 41, 56 136 | glove-r 137 | bounds: 76, 36, 44, 65 138 | rotate: 90 139 | hair-strand-1 140 | bounds: 359, 102, 37, 65 141 | rotate: 90 142 | hair-strand-3 143 | bounds: 884, 414, 40, 53 144 | hair-strand-4 145 | bounds: 939, 893, 37, 69 146 | iris-l 147 | bounds: 884, 173, 40, 41 148 | iris-r 149 | bounds: 143, 39, 40, 41 150 | leg-down-r 151 | bounds: 2, 36, 72, 103 152 | pupil-l 153 | bounds: 51, 2, 32, 32 154 | pupil-r 155 | bounds: 85, 2, 32, 32 156 | red-present-decoration 157 | bounds: 426, 99, 41, 40 158 | scarf-pompom-l 159 | bounds: 884, 309, 50, 46 160 | rotate: 90 161 | scarf-pompom-r 162 | bounds: 884, 258, 49, 47 163 | rotate: 90 164 | scarf-ribbon-bottom-l 165 | bounds: 884, 856, 106, 53 166 | rotate: 90 167 | scarf-ribbon-bottom-r 168 | bounds: 76, 82, 105, 57 169 | scarf-ribbon-middle-l 170 | bounds: 884, 764, 63, 90 171 | scarf-ribbon-top-l 172 | bounds: 884, 964, 105, 58 173 | scarf-ribbon-top-r 174 | bounds: 183, 86, 106, 53 175 | 176 | snowglobe-pma_3.png 177 | size: 1024, 1024 178 | filter: Linear, Linear 179 | pma: true 180 | scale: 0.5 181 | globe-texture 182 | bounds: 2, 2, 880, 881 183 | 184 | snowglobe-pma_4.png 185 | size: 1024, 1024 186 | filter: Linear, Linear 187 | pma: true 188 | scale: 0.5 189 | elf-shadow 190 | bounds: 2, 2, 395, 158 191 | globe-reflections 192 | bounds: 2, 162, 646, 835 193 | globe-shadow 194 | bounds: 650, 77, 920, 366 195 | rotate: 90 196 | hat 197 | bounds: 399, 7, 153, 221 198 | rotate: 90 199 | 200 | snowglobe-pma_5.png 201 | size: 1024, 1024 202 | filter: Linear, Linear 203 | pma: true 204 | scale: 0.5 205 | body 206 | bounds: 710, 569, 139, 151 207 | globe-base-back 208 | bounds: 2, 2, 606, 258 209 | globe-base-front 210 | bounds: 2, 262, 706, 458 211 | -------------------------------------------------------------------------------- /examples/assets/snowglobe-pma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/snowglobe-pma.png -------------------------------------------------------------------------------- /examples/assets/snowglobe-pma_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/snowglobe-pma_2.png -------------------------------------------------------------------------------- /examples/assets/snowglobe-pma_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/snowglobe-pma_3.png -------------------------------------------------------------------------------- /examples/assets/snowglobe-pma_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/snowglobe-pma_4.png -------------------------------------------------------------------------------- /examples/assets/snowglobe-pma_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/snowglobe-pma_5.png -------------------------------------------------------------------------------- /examples/assets/snowglobe-pro.skel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/snowglobe-pro.skel -------------------------------------------------------------------------------- /examples/assets/spine_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/spine_logo.png -------------------------------------------------------------------------------- /examples/assets/spineboy-pma.atlas: -------------------------------------------------------------------------------- 1 | spineboy-pma.png 2 | size: 1024, 256 3 | filter: Linear, Linear 4 | pma: true 5 | scale: 0.5 6 | crosshair 7 | bounds: 352, 7, 45, 45 8 | eye-indifferent 9 | bounds: 862, 105, 47, 45 10 | eye-surprised 11 | bounds: 505, 79, 47, 45 12 | front-bracer 13 | bounds: 826, 66, 29, 40 14 | front-fist-closed 15 | bounds: 786, 65, 38, 41 16 | front-fist-open 17 | bounds: 710, 51, 43, 44 18 | rotate: 90 19 | front-foot 20 | bounds: 210, 6, 63, 35 21 | front-shin 22 | bounds: 665, 128, 41, 92 23 | rotate: 90 24 | front-thigh 25 | bounds: 2, 2, 23, 56 26 | rotate: 90 27 | front-upper-arm 28 | bounds: 250, 205, 23, 49 29 | goggles 30 | bounds: 665, 171, 131, 83 31 | gun 32 | bounds: 798, 152, 105, 102 33 | head 34 | bounds: 2, 27, 136, 149 35 | hoverboard-board 36 | bounds: 2, 178, 246, 76 37 | hoverboard-thruster 38 | bounds: 722, 96, 30, 32 39 | rotate: 90 40 | hoverglow-small 41 | bounds: 275, 81, 137, 38 42 | mouth-grind 43 | bounds: 614, 97, 47, 30 44 | mouth-oooo 45 | bounds: 612, 65, 47, 30 46 | mouth-smile 47 | bounds: 661, 64, 47, 30 48 | muzzle-glow 49 | bounds: 382, 54, 25, 25 50 | muzzle-ring 51 | bounds: 275, 54, 25, 105 52 | rotate: 90 53 | muzzle01 54 | bounds: 911, 95, 67, 40 55 | rotate: 90 56 | muzzle02 57 | bounds: 792, 108, 68, 42 58 | muzzle03 59 | bounds: 956, 171, 83, 53 60 | rotate: 90 61 | muzzle04 62 | bounds: 275, 7, 75, 45 63 | muzzle05 64 | bounds: 140, 3, 68, 38 65 | neck 66 | bounds: 250, 182, 18, 21 67 | portal-bg 68 | bounds: 140, 43, 133, 133 69 | portal-flare1 70 | bounds: 554, 65, 56, 30 71 | portal-flare2 72 | bounds: 759, 112, 57, 31 73 | rotate: 90 74 | portal-flare3 75 | bounds: 554, 97, 58, 30 76 | portal-shade 77 | bounds: 275, 121, 133, 133 78 | portal-streaks1 79 | bounds: 410, 126, 126, 128 80 | portal-streaks2 81 | bounds: 538, 129, 125, 125 82 | rear-bracer 83 | bounds: 857, 67, 28, 36 84 | rear-foot 85 | bounds: 663, 96, 57, 30 86 | rear-shin 87 | bounds: 414, 86, 38, 89 88 | rotate: 90 89 | rear-thigh 90 | bounds: 756, 63, 28, 47 91 | rear-upper-arm 92 | bounds: 60, 5, 20, 44 93 | rotate: 90 94 | torso 95 | bounds: 905, 164, 49, 90 96 | -------------------------------------------------------------------------------- /examples/assets/spineboy-pma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/spineboy-pma.png -------------------------------------------------------------------------------- /examples/assets/spineboy-pro.skel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/spineboy-pro.skel -------------------------------------------------------------------------------- /examples/assets/spineboy.atlas: -------------------------------------------------------------------------------- 1 | spineboy.png 2 | size: 1024, 256 3 | filter: Linear, Linear 4 | scale: 0.5 5 | crosshair 6 | bounds: 352, 7, 45, 45 7 | eye-indifferent 8 | bounds: 862, 105, 47, 45 9 | eye-surprised 10 | bounds: 505, 79, 47, 45 11 | front-bracer 12 | bounds: 826, 66, 29, 40 13 | front-fist-closed 14 | bounds: 786, 65, 38, 41 15 | front-fist-open 16 | bounds: 710, 51, 43, 44 17 | rotate: 90 18 | front-foot 19 | bounds: 210, 6, 63, 35 20 | front-shin 21 | bounds: 665, 128, 41, 92 22 | rotate: 90 23 | front-thigh 24 | bounds: 2, 2, 23, 56 25 | rotate: 90 26 | front-upper-arm 27 | bounds: 250, 205, 23, 49 28 | goggles 29 | bounds: 665, 171, 131, 83 30 | gun 31 | bounds: 798, 152, 105, 102 32 | head 33 | bounds: 2, 27, 136, 149 34 | hoverboard-board 35 | bounds: 2, 178, 246, 76 36 | hoverboard-thruster 37 | bounds: 722, 96, 30, 32 38 | rotate: 90 39 | hoverglow-small 40 | bounds: 275, 81, 137, 38 41 | mouth-grind 42 | bounds: 614, 97, 47, 30 43 | mouth-oooo 44 | bounds: 612, 65, 47, 30 45 | mouth-smile 46 | bounds: 661, 64, 47, 30 47 | muzzle-glow 48 | bounds: 382, 54, 25, 25 49 | muzzle-ring 50 | bounds: 275, 54, 25, 105 51 | rotate: 90 52 | muzzle01 53 | bounds: 911, 95, 67, 40 54 | rotate: 90 55 | muzzle02 56 | bounds: 792, 108, 68, 42 57 | muzzle03 58 | bounds: 956, 171, 83, 53 59 | rotate: 90 60 | muzzle04 61 | bounds: 275, 7, 75, 45 62 | muzzle05 63 | bounds: 140, 3, 68, 38 64 | neck 65 | bounds: 250, 182, 18, 21 66 | portal-bg 67 | bounds: 140, 43, 133, 133 68 | portal-flare1 69 | bounds: 554, 65, 56, 30 70 | portal-flare2 71 | bounds: 759, 112, 57, 31 72 | rotate: 90 73 | portal-flare3 74 | bounds: 554, 97, 58, 30 75 | portal-shade 76 | bounds: 275, 121, 133, 133 77 | portal-streaks1 78 | bounds: 410, 126, 126, 128 79 | portal-streaks2 80 | bounds: 538, 129, 125, 125 81 | rear-bracer 82 | bounds: 857, 67, 28, 36 83 | rear-foot 84 | bounds: 663, 96, 57, 30 85 | rear-shin 86 | bounds: 414, 86, 38, 89 87 | rotate: 90 88 | rear-thigh 89 | bounds: 756, 63, 28, 47 90 | rear-upper-arm 91 | bounds: 60, 5, 20, 44 92 | rotate: 90 93 | torso 94 | bounds: 905, 164, 49, 90 95 | -------------------------------------------------------------------------------- /examples/assets/spineboy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/spineboy.png -------------------------------------------------------------------------------- /examples/assets/stretchyman-pma.atlas: -------------------------------------------------------------------------------- 1 | stretchyman-pma.png 2 | size: 1024, 256 3 | filter: Linear, Linear 4 | pma: true 5 | back-arm 6 | bounds: 679, 173, 72, 202 7 | rotate: 90 8 | back-leg 9 | bounds: 2, 2, 100, 318 10 | rotate: 90 11 | body 12 | bounds: 2, 104, 141, 452 13 | rotate: 90 14 | front-arm 15 | bounds: 456, 100, 145, 221 16 | rotate: 90 17 | head 18 | bounds: 322, 15, 87, 102 19 | rotate: 90 20 | -------------------------------------------------------------------------------- /examples/assets/stretchyman-pma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/stretchyman-pma.png -------------------------------------------------------------------------------- /examples/assets/stretchyman-pro.skel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pixijs/spine-v8/7236ae91010457ef8ac39627a415e81890978bac/examples/assets/stretchyman-pro.skel -------------------------------------------------------------------------------- /examples/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | spine-pixi 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 51 | 52 | -------------------------------------------------------------------------------- /examples/control-bones-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | spine-pixi 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 110 | 111 | -------------------------------------------------------------------------------- /examples/events-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | spine-pixi 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 16 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /examples/index.css: -------------------------------------------------------------------------------- 1 | /*** 2 | The new CSS reset - version 1.11.2 (last updated 15.11.2023) 3 | GitHub page: https://github.com/elad2412/the-new-css-reset 4 | ***/ 5 | 6 | /* 7 | Remove all the styles of the "User-Agent-Stylesheet", except for the 'display' property 8 | - The "symbol *" part is to solve Firefox SVG sprite bug 9 | - The "html" element is excluded, otherwise a bug in Chrome breaks the CSS hyphens property (https://github.com/elad2412/the-new-css-reset/issues/36) 10 | */ 11 | *:where( 12 | :not(html, iframe, canvas, img, svg, video, audio):not(svg *, symbol *) 13 | ) { 14 | all: unset; 15 | display: revert; 16 | } 17 | 18 | /* Preferred box-sizing value */ 19 | *, 20 | *::before, 21 | *::after { 22 | box-sizing: border-box; 23 | } 24 | 25 | /* Fix mobile Safari increase font-size on landscape mode */ 26 | html { 27 | -moz-text-size-adjust: none; 28 | -webkit-text-size-adjust: none; 29 | text-size-adjust: none; 30 | } 31 | 32 | /* Reapply the pointer cursor for anchor tags */ 33 | a, 34 | button { 35 | cursor: revert; 36 | } 37 | 38 | /* Remove list styles (bullets/numbers) */ 39 | ol, 40 | ul, 41 | menu, 42 | summary { 43 | list-style: none; 44 | } 45 | 46 | /* For images to not be able to exceed their container */ 47 | img { 48 | max-inline-size: 100%; 49 | max-block-size: 100%; 50 | } 51 | 52 | /* removes spacing between cells in tables */ 53 | table { 54 | border-collapse: collapse; 55 | } 56 | 57 | /* Safari - solving issue when using user-select:none on the text input doesn't working */ 58 | input, 59 | textarea { 60 | -webkit-user-select: auto; 61 | } 62 | 63 | /* revert the 'white-space' property for textarea elements on Safari */ 64 | textarea { 65 | white-space: revert; 66 | } 67 | 68 | /* minimum style to allow to style meter element */ 69 | meter { 70 | -webkit-appearance: revert; 71 | appearance: revert; 72 | } 73 | 74 | /* preformatted text - use only for this feature */ 75 | :where(pre) { 76 | all: revert; 77 | box-sizing: border-box; 78 | } 79 | 80 | /* reset default text opacity of input placeholder */ 81 | ::placeholder { 82 | color: unset; 83 | } 84 | 85 | /* fix the feature of 'hidden' attribute. 86 | display:revert; revert to element instead of attribute */ 87 | :where([hidden]) { 88 | display: none; 89 | } 90 | 91 | /* revert for bug in Chromium browsers 92 | - fix for the content editable attribute will work properly. 93 | - webkit-user-select: auto; added for Safari in case of using user-select:none on wrapper element*/ 94 | :where([contenteditable]:not([contenteditable="false"])) { 95 | -moz-user-modify: read-write; 96 | -webkit-user-modify: read-write; 97 | overflow-wrap: break-word; 98 | -webkit-line-break: after-white-space; 99 | -webkit-user-select: auto; 100 | } 101 | 102 | /* apply back the draggable feature - exist only in Chromium and Safari */ 103 | :where([draggable="true"]) { 104 | -webkit-user-drag: element; 105 | } 106 | 107 | /* Revert Modal native behavior */ 108 | :where(dialog:modal) { 109 | all: revert; 110 | box-sizing: border-box; 111 | } 112 | 113 | /* Remove details summary webkit styles */ 114 | ::-webkit-details-marker { 115 | display: none; 116 | } 117 | 118 | html, 119 | body { 120 | margin: 0; 121 | padding: 0; 122 | font-family: "Lucida Sans", "Lucida Sans Regular", "Lucida Grande", 123 | "Lucida Sans Unicode", Geneva, Verdana, sans-serif; 124 | } 125 | 126 | * { 127 | box-sizing: border-box; 128 | } 129 | 130 | button { 131 | display: inline-block; /* Aligns like a button */ 132 | padding: 0.125em 0.5em; /* Button size */ 133 | color: #333; /* Text color */ 134 | background-color: #ccc; 135 | border: none; /* No border */ 136 | border-radius: 5px; /* Rounded corners */ 137 | text-align: center; /* Center text */ 138 | text-decoration: none; /* No underline */ 139 | cursor: pointer; /* Cursor changes to hand symbol */ 140 | transition: background-color 0.3s, box-shadow 0.3s; /* Smooth transition for hover effect */ 141 | 142 | /* Slight shadow for depth (optional) */ 143 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); 144 | } 145 | 146 | /* Hover state */ 147 | button:hover { 148 | background-color: #fff; /* Darker shade when hovered */ 149 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* Larger shadow on hover */ 150 | } 151 | 152 | .overlay { 153 | display: flex; 154 | flex-direction: column; 155 | position: fixed; 156 | z-index: 10; 157 | top: 0; 158 | left: 0; 159 | gap: 0.5em; 160 | padding: 1em; 161 | background-color: rgba(0, 0, 0, 0.7); 162 | color: #ddd; 163 | border-radius: 5px; 164 | } 165 | 166 | h1, 167 | h2, 168 | h3, 169 | h4, 170 | h5, 171 | h5 { 172 | font-weight: 600; 173 | margin: 1rem 0; 174 | } 175 | body { 176 | background-color: #222; 177 | color: #ccc; 178 | } 179 | h1 { 180 | font-size: 1.5em; 181 | } 182 | h2 { 183 | font-size: 1em; 184 | } 185 | a { 186 | color: darkorange; 187 | } 188 | ul, 189 | ol { 190 | padding: 1em; 191 | line-height: 1.25em; 192 | } 193 | 194 | .flex { 195 | display: flex; 196 | } 197 | 198 | .flex-col { 199 | flex-direction: column; 200 | } 201 | 202 | .flex-grow { 203 | flex-grow: 1; 204 | } 205 | 206 | .items-center { 207 | align-items: center; 208 | } 209 | 210 | .justify-center { 211 | justify-content: center; 212 | } 213 | 214 | .w-full { 215 | width: 100%; 216 | } 217 | 218 | .h-full { 219 | height: 100%; 220 | } 221 | 222 | .h-screen { 223 | height: 100vh; 224 | } 225 | 226 | .p-4 { 227 | padding: 1em; 228 | } 229 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | PixiJS Examples 8 | 9 | 10 | 11 | 12 | 13 |
14 |

PixiJS Examples

15 | 41 |
42 | 43 | 44 | -------------------------------------------------------------------------------- /examples/manual-loading.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | spine-pixi 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /examples/mix-and-match-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | spine-pixi 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /examples/mouse-following.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Spine Pixi Example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 91 | 92 | -------------------------------------------------------------------------------- /examples/physics.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | spine-pixi 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 47 | 48 | -------------------------------------------------------------------------------- /examples/physics2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | spine-pixi 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 142 | 143 | -------------------------------------------------------------------------------- /examples/physics3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | spine-pixi 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 47 | 48 | -------------------------------------------------------------------------------- /examples/physics4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | spine-pixi 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 47 | 48 | -------------------------------------------------------------------------------- /examples/simple-input.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | spine-pixi 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /examples/slot-objects.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | spine-pixi 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pixi/spine-pixi", 3 | "version": "2.1.1", 4 | "description": "A port of the @esotericsoftware/spine-pixi runtime to PixiJS v8.", 5 | "homepage": "https://pixijs.com/", 6 | "bugs": "https://github.com/pixijs/spine-v8/issues", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/pixijs/spine-v8.git" 10 | }, 11 | "license": "MIT", 12 | "author": "PixiJS Team", 13 | "exports": { 14 | ".": { 15 | "import": "./lib/index.mjs", 16 | "require": "./lib/index.js", 17 | "types": "./lib/index.d.ts" 18 | } 19 | }, 20 | "main": "./lib/index.js", 21 | "module": "./lib/index.mjs", 22 | "types": "./lib/index.d.ts", 23 | "files": [ 24 | "dist/", 25 | "lib/" 26 | ], 27 | "scripts": { 28 | "build": "xs build && npm run build:iife", 29 | "build:iife": "npx esbuild --bundle ./src/index.ts --tsconfig=./tsconfig.json --sourcemap --outfile=./dist/spine-pixi.js --external:pixi.js --format=iife --global-name=spine", 30 | "docs": "xs docs", 31 | "lint": "xs lint --max-warnings 0", 32 | "lint:fix": "xs lint", 33 | "release": "npm run build && xs bump,docs,publish,git-push", 34 | "start": "xs serve", 35 | "test": "xs test", 36 | "types": "xs types", 37 | "watch": "xs watch", 38 | "upload": "xs upload" 39 | }, 40 | "dependencies": { 41 | "@esotericsoftware/spine-core": "~4.2.45" 42 | }, 43 | "devDependencies": { 44 | "@pixi/extension-scripts": "^2.4.1", 45 | "pixi.js": "8.4.0", 46 | "typescript": "^5.4.2" 47 | }, 48 | "peerDependencies": { 49 | "pixi.js": "^8.4.0" 50 | }, 51 | "engines": { 52 | "node": ">=16", 53 | "npm": ">=8" 54 | }, 55 | "publishConfig": { 56 | "access": "public" 57 | }, 58 | "extensionConfig": { 59 | "lint": [ 60 | "src", 61 | "test" 62 | ], 63 | "docsName": "PixiJS Spine", 64 | "docsTitle": "PixiJS Spine", 65 | "tsconfig": "./tsconfig.build.json", 66 | "environments": [ 67 | "node" 68 | ] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/BatchableSpineSlot.ts: -------------------------------------------------------------------------------- 1 | /** **************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated July 28, 2023. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2023, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software or 13 | * otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY 19 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY 22 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, 24 | * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND 25 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE 27 | * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | import { AttachmentCacheData, Spine } from './Spine'; 31 | 32 | import type { Batch, Batcher, BLEND_MODES, DefaultBatchableMeshElement, Matrix, Texture } from 'pixi.js'; 33 | 34 | export class BatchableSpineSlot implements DefaultBatchableMeshElement 35 | { 36 | indexOffset = 0; 37 | attributeOffset = 0; 38 | 39 | indexSize: number; 40 | attributeSize: number; 41 | 42 | batcherName = 'darkTint'; 43 | 44 | readonly packAsQuad = false; 45 | 46 | renderable: Spine; 47 | 48 | positions: Float32Array; 49 | indices: number[] | Uint16Array; 50 | uvs: Float32Array; 51 | 52 | roundPixels: 0 | 1; 53 | data: AttachmentCacheData; 54 | blendMode: BLEND_MODES; 55 | 56 | darkTint: number; 57 | 58 | texture: Texture; 59 | 60 | transform: Matrix; 61 | 62 | // used internally by batcher specific.. 63 | // stored for efficient updating.. 64 | _textureId: number; 65 | _attributeStart: number; 66 | _indexStart: number; 67 | _batcher: Batcher; 68 | _batch: Batch; 69 | 70 | get color() 71 | { 72 | const slotColor = this.data.color; 73 | 74 | const parentColor:number = this.renderable.groupColor; 75 | const parentAlpha:number = this.renderable.groupAlpha; 76 | let abgr:number; 77 | 78 | const mixedA = (slotColor.a * parentAlpha) * 255; 79 | 80 | if (parentColor !== 0xFFFFFF) 81 | { 82 | const parentB = (parentColor >> 16) & 0xFF; 83 | const parentG = (parentColor >> 8) & 0xFF; 84 | const parentR = parentColor & 0xFF; 85 | 86 | const mixedR = (slotColor.r * parentR); 87 | const mixedG = (slotColor.g * parentG); 88 | const mixedB = (slotColor.b * parentB); 89 | 90 | abgr = ((mixedA) << 24) | (mixedB << 16) | (mixedG << 8) | mixedR; 91 | } 92 | else 93 | { 94 | abgr = ((mixedA) << 24) | ((slotColor.b * 255) << 16) | ((slotColor.g * 255) << 8) | (slotColor.r * 255); 95 | } 96 | 97 | return abgr; 98 | } 99 | 100 | get darkColor() 101 | { 102 | const darkColor = this.data.darkColor; 103 | 104 | return ((darkColor.b * 255) << 16) | ((darkColor.g * 255) << 8) | (darkColor.r * 255); 105 | } 106 | 107 | get groupTransform() { return this.renderable.groupTransform; } 108 | 109 | setData( 110 | renderable:Spine, 111 | data:AttachmentCacheData, 112 | blendMode:BLEND_MODES, 113 | roundPixels: 0 | 1) 114 | { 115 | this.renderable = renderable; 116 | this.transform = renderable.groupTransform; 117 | this.data = data; 118 | 119 | if (data.clipped) 120 | { 121 | const clippedData = data.clippedData; 122 | 123 | this.indexSize = clippedData.indicesCount; 124 | this.attributeSize = clippedData.vertexCount; 125 | this.positions = clippedData.vertices; 126 | this.indices = clippedData.indices; 127 | this.uvs = clippedData.uvs; 128 | } 129 | else 130 | { 131 | this.indexSize = data.indices.length; 132 | this.attributeSize = data.vertices.length / 2; 133 | this.positions = data.vertices; 134 | this.indices = data.indices; 135 | this.uvs = data.uvs; 136 | } 137 | 138 | this.texture = data.texture; 139 | this.roundPixels = roundPixels; 140 | 141 | this.blendMode = blendMode; 142 | 143 | this.batcherName = data.darkTint ? 'darkTint' : 'default'; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/Spine.ts: -------------------------------------------------------------------------------- 1 | /** **************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated July 28, 2023. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2023, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software or 13 | * otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY 19 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY 22 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, 24 | * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND 25 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE 27 | * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | import { 31 | Assets, 32 | Bounds, 33 | Cache, 34 | Container, 35 | ContainerOptions, 36 | DEG_TO_RAD, 37 | DestroyOptions, 38 | PointData, 39 | Texture, 40 | Ticker, 41 | ViewContainer, 42 | } from 'pixi.js'; 43 | import { ISpineDebugRenderer } from './SpineDebugRenderer'; 44 | import { 45 | AnimationState, 46 | AnimationStateData, 47 | AtlasAttachmentLoader, 48 | Attachment, 49 | Bone, 50 | ClippingAttachment, 51 | Color, 52 | MeshAttachment, 53 | Physics, 54 | RegionAttachment, 55 | Skeleton, 56 | SkeletonBinary, 57 | SkeletonBounds, 58 | SkeletonClipping, 59 | SkeletonData, 60 | SkeletonJson, 61 | Slot, 62 | type TextureAtlas, 63 | TrackEntry, 64 | Vector2, 65 | } from '@esotericsoftware/spine-core'; 66 | 67 | export type SpineFromOptions = { 68 | skeleton: string; 69 | atlas: string; 70 | scale?: number; 71 | }; 72 | 73 | const vectorAux = new Vector2(); 74 | const lightColor = new Color(); 75 | const darkColor = new Color(); 76 | 77 | Skeleton.yDown = true; 78 | 79 | const clipper = new SkeletonClipping(); 80 | 81 | export interface SpineOptions extends ContainerOptions 82 | { 83 | skeletonData: SkeletonData; 84 | autoUpdate?: boolean; 85 | } 86 | 87 | export interface SpineEvents 88 | { 89 | complete: [trackEntry: TrackEntry]; 90 | dispose: [trackEntry: TrackEntry]; 91 | end: [trackEntry: TrackEntry]; 92 | event: [trackEntry: TrackEntry, event: Event]; 93 | interrupt: [trackEntry: TrackEntry]; 94 | start: [trackEntry: TrackEntry]; 95 | } 96 | 97 | export interface AttachmentCacheData 98 | { 99 | id: string; 100 | clipped: boolean; 101 | vertices: Float32Array; 102 | uvs: Float32Array; 103 | indices: number[]; 104 | color: Color; 105 | darkColor: Color | null; 106 | darkTint: boolean; 107 | skipRender: boolean; 108 | texture: Texture; 109 | clippedData?: { 110 | vertices: Float32Array; 111 | uvs: Float32Array; 112 | indices: Uint16Array; 113 | vertexCount: number; 114 | indicesCount: number; 115 | }; 116 | } 117 | 118 | export class Spine extends ViewContainer 119 | { 120 | // Pixi properties 121 | public batched = true; 122 | public buildId = 0; 123 | public override readonly renderPipeId = 'spine'; 124 | public _didSpineUpdate = false; 125 | 126 | public beforeUpdateWorldTransforms: (object: Spine) => void = () => { /** */ }; 127 | public afterUpdateWorldTransforms: (object: Spine) => void = () => { /** */ }; 128 | 129 | // Spine properties 130 | public skeleton: Skeleton; 131 | public state: AnimationState; 132 | public skeletonBounds: SkeletonBounds; 133 | private _debug?: ISpineDebugRenderer | undefined = undefined; 134 | 135 | readonly _slotsObject: Record = Object.create(null); 136 | 137 | private getSlotFromRef(slotRef: number | string | Slot): Slot 138 | { 139 | let slot: Slot | null; 140 | 141 | if (typeof slotRef === 'number') slot = this.skeleton.slots[slotRef]; 142 | else if (typeof slotRef === 'string') slot = this.skeleton.findSlot(slotRef); 143 | else slot = slotRef; 144 | 145 | if (!slot) throw new Error(`No slot found with the given slot reference: ${slotRef}`); 146 | 147 | return slot; 148 | } 149 | 150 | public spineAttachmentsDirty = true; 151 | public spineTexturesDirty = true; 152 | 153 | private _lastAttachments: Attachment[]; 154 | 155 | private _stateChanged = true; 156 | private attachmentCacheData: Record[] = []; 157 | 158 | public get debug(): ISpineDebugRenderer | undefined 159 | { 160 | return this._debug; 161 | } 162 | 163 | public set debug(value: ISpineDebugRenderer | undefined) 164 | { 165 | if (this._debug) 166 | { 167 | this._debug.unregisterSpine(this); 168 | } 169 | if (value) 170 | { 171 | value.registerSpine(this); 172 | } 173 | this._debug = value; 174 | } 175 | 176 | private autoUpdateWarned = false; 177 | private _autoUpdate = true; 178 | 179 | public get autoUpdate(): boolean 180 | { 181 | return this._autoUpdate; 182 | } 183 | 184 | public set autoUpdate(value: boolean) 185 | { 186 | if (value) 187 | { 188 | Ticker.shared.add(this.internalUpdate, this); 189 | this.autoUpdateWarned = false; 190 | } 191 | else 192 | { 193 | Ticker.shared.remove(this.internalUpdate, this); 194 | } 195 | 196 | this._autoUpdate = value; 197 | } 198 | 199 | constructor(options: SpineOptions | SkeletonData) 200 | { 201 | if (options instanceof SkeletonData) 202 | { 203 | options = { 204 | skeletonData: options, 205 | }; 206 | } 207 | 208 | super(); 209 | 210 | const skeletonData = options instanceof SkeletonData ? options : options.skeletonData; 211 | 212 | this.skeleton = new Skeleton(skeletonData); 213 | this.state = new AnimationState(new AnimationStateData(skeletonData)); 214 | this.autoUpdate = options?.autoUpdate ?? true; 215 | 216 | const slots = this.skeleton.slots; 217 | 218 | for (let i = 0; i < slots.length; i++) 219 | { 220 | this.attachmentCacheData[i] = Object.create(null); 221 | } 222 | 223 | this._updateState(0); 224 | } 225 | 226 | public update(dt: number): void 227 | { 228 | if (this.autoUpdate && !this.autoUpdateWarned) 229 | { 230 | console.warn( 231 | // eslint-disable-next-line max-len 232 | 'You are calling update on a Spine instance that has autoUpdate set to true. This is probably not what you want.', 233 | ); 234 | this.autoUpdateWarned = true; 235 | } 236 | 237 | this.internalUpdate(0, dt); 238 | } 239 | 240 | protected internalUpdate(_deltaFrame: any, deltaSeconds?: number): void 241 | { 242 | // Because reasons, pixi uses deltaFrames at 60fps. 243 | // We ignore the default deltaFrames and use the deltaSeconds from pixi ticker. 244 | this._updateState(deltaSeconds ?? Ticker.shared.deltaMS / 1000); 245 | } 246 | 247 | get bounds() 248 | { 249 | if (this._boundsDirty) 250 | { 251 | this.updateBounds(); 252 | } 253 | 254 | return this._bounds; 255 | } 256 | 257 | public setBonePosition(bone: string | Bone, position: PointData): void 258 | { 259 | const boneAux = bone; 260 | 261 | if (typeof bone === 'string') 262 | { 263 | bone = this.skeleton.findBone(bone) as Bone; 264 | } 265 | 266 | if (!bone) throw Error(`Cant set bone position, bone ${String(boneAux)} not found`); 267 | vectorAux.set(position.x, position.y); 268 | 269 | if (bone.parent) 270 | { 271 | const aux = bone.parent.worldToLocal(vectorAux); 272 | 273 | bone.x = aux.x; 274 | bone.y = -aux.y; 275 | } 276 | else 277 | { 278 | bone.x = vectorAux.x; 279 | bone.y = vectorAux.y; 280 | } 281 | } 282 | 283 | public getBonePosition(bone: string | Bone, outPos?: PointData): PointData | undefined 284 | { 285 | const boneAux = bone; 286 | 287 | if (typeof bone === 'string') 288 | { 289 | bone = this.skeleton.findBone(bone) as Bone; 290 | } 291 | 292 | if (!bone) 293 | { 294 | console.error(`Cant set bone position! Bone ${String(boneAux)} not found`); 295 | 296 | return outPos; 297 | } 298 | 299 | if (!outPos) 300 | { 301 | outPos = { x: 0, y: 0 }; 302 | } 303 | 304 | outPos.x = bone.worldX; 305 | outPos.y = bone.worldY; 306 | 307 | return outPos; 308 | } 309 | 310 | /** 311 | * Will update the state based on the specified time, this will not apply the state to the skeleton 312 | * as this is differed until the `applyState` method is called. 313 | * 314 | * @param time the time at which to set the state 315 | * @internal 316 | */ 317 | _updateState(time: number) 318 | { 319 | this.state.update(time); 320 | this.skeleton.update(time); 321 | 322 | this._stateChanged = true; 323 | 324 | this._boundsDirty = true; 325 | 326 | this.onViewUpdate(); 327 | } 328 | 329 | /** 330 | * Applies the state to this spine instance. 331 | * - updates the state to the skeleton 332 | * - updates its world transform (spine world transform) 333 | * - validates the attachments - to flag if the attachments have changed this state 334 | * - transforms the attachments - to update the vertices of the attachments based on the new positions 335 | * - update the slot attachments - to update the position, rotation, scale, and visibility of the attached containers 336 | * @internal 337 | */ 338 | _applyState() 339 | { 340 | if (!this._stateChanged) return; 341 | this._stateChanged = false; 342 | 343 | const { skeleton } = this; 344 | 345 | this.state.apply(skeleton); 346 | 347 | this.beforeUpdateWorldTransforms(this); 348 | skeleton.updateWorldTransform(Physics.update); 349 | this.afterUpdateWorldTransforms(this); 350 | 351 | this.validateAttachments(); 352 | 353 | this.transformAttachments(); 354 | 355 | this.updateSlotObjects(); 356 | } 357 | 358 | private validateAttachments() 359 | { 360 | const currentDrawOrder = this.skeleton.drawOrder; 361 | 362 | const lastAttachments = (this._lastAttachments ||= []); 363 | 364 | let index = 0; 365 | 366 | let spineAttachmentsDirty = false; 367 | 368 | for (let i = 0; i < currentDrawOrder.length; i++) 369 | { 370 | const slot = currentDrawOrder[i]; 371 | const attachment = slot.getAttachment(); 372 | 373 | if (attachment) 374 | { 375 | if (attachment !== lastAttachments[index]) 376 | { 377 | spineAttachmentsDirty = true; 378 | lastAttachments[index] = attachment; 379 | } 380 | 381 | index++; 382 | } 383 | } 384 | 385 | if (index !== lastAttachments.length) 386 | { 387 | spineAttachmentsDirty = true; 388 | lastAttachments.length = index; 389 | } 390 | 391 | this.spineAttachmentsDirty = spineAttachmentsDirty; 392 | } 393 | 394 | private transformAttachments() 395 | { 396 | const currentDrawOrder = this.skeleton.drawOrder; 397 | 398 | for (let i = 0; i < currentDrawOrder.length; i++) 399 | { 400 | const slot = currentDrawOrder[i]; 401 | 402 | const attachment = slot.getAttachment(); 403 | 404 | if (attachment) 405 | { 406 | if (attachment instanceof MeshAttachment || attachment instanceof RegionAttachment) 407 | { 408 | const cacheData = this._getCachedData(slot, attachment); 409 | 410 | if (attachment instanceof RegionAttachment) 411 | { 412 | attachment.computeWorldVertices(slot, cacheData.vertices, 0, 2); 413 | } 414 | else 415 | { 416 | attachment.computeWorldVertices( 417 | slot, 418 | 0, 419 | attachment.worldVerticesLength, 420 | cacheData.vertices, 421 | 0, 422 | 2, 423 | ); 424 | } 425 | 426 | cacheData.uvs = attachment.uvs as Float32Array; 427 | 428 | const skeleton = slot.bone.skeleton; 429 | const skeletonColor = skeleton.color; 430 | const slotColor = slot.color; 431 | 432 | const attachmentColor = attachment.color; 433 | 434 | cacheData.color.set( 435 | skeletonColor.r * slotColor.r * attachmentColor.r, 436 | skeletonColor.g * slotColor.g * attachmentColor.g, 437 | skeletonColor.b * slotColor.b * attachmentColor.b, 438 | skeletonColor.a * slotColor.a * attachmentColor.a, 439 | ); 440 | 441 | cacheData.darkTint = !!slot.darkColor; 442 | 443 | if (slot.darkColor) 444 | { 445 | cacheData.darkColor.setFromColor(slot.darkColor); 446 | } 447 | 448 | cacheData.skipRender = cacheData.clipped = false; 449 | 450 | const texture = attachment.region?.texture.texture || Texture.EMPTY; 451 | 452 | if (cacheData.texture !== texture) 453 | { 454 | cacheData.texture = texture; 455 | this.spineTexturesDirty = true; 456 | } 457 | 458 | if (clipper.isClipping()) 459 | { 460 | this.updateClippingData(cacheData); 461 | } 462 | } 463 | else if (attachment instanceof ClippingAttachment) 464 | { 465 | clipper.clipStart(slot, attachment); 466 | continue; 467 | } 468 | } 469 | clipper.clipEndWithSlot(slot); 470 | } 471 | clipper.clipEnd(); 472 | } 473 | 474 | private updateClippingData(cacheData: AttachmentCacheData) 475 | { 476 | cacheData.clipped = true; 477 | 478 | clipper.clipTriangles( 479 | cacheData.vertices, 480 | cacheData.vertices.length, 481 | cacheData.indices, 482 | cacheData.indices.length, 483 | cacheData.uvs, 484 | lightColor, 485 | darkColor, 486 | false, 487 | ); 488 | 489 | const { clippedVertices, clippedTriangles } = clipper; 490 | 491 | const verticesCount = clippedVertices.length / 8; 492 | const indicesCount = clippedTriangles.length; 493 | 494 | if (!cacheData.clippedData) 495 | { 496 | cacheData.clippedData = { 497 | vertices: new Float32Array(verticesCount * 2), 498 | uvs: new Float32Array(verticesCount * 2), 499 | vertexCount: verticesCount, 500 | indices: new Uint16Array(indicesCount), 501 | indicesCount, 502 | }; 503 | 504 | this.spineAttachmentsDirty = true; 505 | } 506 | 507 | const clippedData = cacheData.clippedData; 508 | 509 | const sizeChange = clippedData.vertexCount !== verticesCount || indicesCount !== clippedData.indicesCount; 510 | 511 | cacheData.skipRender = verticesCount === 0; 512 | 513 | if (sizeChange) 514 | { 515 | this.spineAttachmentsDirty = true; 516 | 517 | if (clippedData.vertexCount < verticesCount) 518 | { 519 | // buffer reuse! 520 | clippedData.vertices = new Float32Array(verticesCount * 2); 521 | clippedData.uvs = new Float32Array(verticesCount * 2); 522 | } 523 | 524 | if (clippedData.indices.length < indicesCount) 525 | { 526 | clippedData.indices = new Uint16Array(indicesCount); 527 | } 528 | } 529 | 530 | const { vertices, uvs, indices } = clippedData; 531 | 532 | for (let i = 0; i < verticesCount; i++) 533 | { 534 | vertices[i * 2] = clippedVertices[i * 8]; 535 | vertices[(i * 2) + 1] = clippedVertices[(i * 8) + 1]; 536 | 537 | uvs[i * 2] = clippedVertices[(i * 8) + 6]; 538 | uvs[(i * 2) + 1] = clippedVertices[(i * 8) + 7]; 539 | } 540 | 541 | clippedData.vertexCount = verticesCount; 542 | 543 | for (let i = 0; i < indices.length; i++) 544 | { 545 | indices[i] = clippedTriangles[i]; 546 | } 547 | 548 | clippedData.indicesCount = indicesCount; 549 | } 550 | 551 | /** 552 | * ensure that attached containers map correctly to their slots 553 | * along with their position, rotation, scale, and visibility. 554 | */ 555 | private updateSlotObjects() 556 | { 557 | for (const i in this._slotsObject) 558 | { 559 | const slotAttachment = this._slotsObject[i]; 560 | 561 | if (!slotAttachment) continue; 562 | 563 | this.updateSlotObject(slotAttachment); 564 | } 565 | } 566 | 567 | private updateSlotObject(slotAttachment: {slot:Slot, container:Container}) 568 | { 569 | const { slot, container } = slotAttachment; 570 | 571 | container.visible = this.skeleton.drawOrder.includes(slot); 572 | 573 | if (container.visible) 574 | { 575 | const bone = slot.bone; 576 | 577 | container.position.set(bone.worldX, bone.worldY); 578 | 579 | container.scale.x = bone.getWorldScaleX(); 580 | container.scale.y = bone.getWorldScaleY(); 581 | 582 | container.rotation = bone.getWorldRotationX() * DEG_TO_RAD; 583 | } 584 | } 585 | 586 | /** @internal */ 587 | _getCachedData(slot: Slot, attachment: RegionAttachment | MeshAttachment): AttachmentCacheData 588 | { 589 | return this.attachmentCacheData[slot.data.index][attachment.name] || this.initCachedData(slot, attachment); 590 | } 591 | 592 | private initCachedData(slot: Slot, attachment: RegionAttachment | MeshAttachment): AttachmentCacheData 593 | { 594 | let vertices: Float32Array; 595 | 596 | if (attachment instanceof RegionAttachment) 597 | { 598 | vertices = new Float32Array(8); 599 | 600 | this.attachmentCacheData[slot.data.index][attachment.name] = { 601 | id: `${slot.data.index}-${attachment.name}`, 602 | vertices, 603 | clipped: false, 604 | indices: [0, 1, 2, 0, 2, 3], 605 | uvs: attachment.uvs as Float32Array, 606 | color: new Color(1, 1, 1, 1), 607 | darkColor: new Color(0, 0, 0, 0), 608 | darkTint: false, 609 | skipRender: false, 610 | texture: attachment.region?.texture.texture, 611 | }; 612 | } 613 | else 614 | { 615 | vertices = new Float32Array(attachment.worldVerticesLength); 616 | 617 | this.attachmentCacheData[slot.data.index][attachment.name] = { 618 | id: `${slot.data.index}-${attachment.name}`, 619 | vertices, 620 | clipped: false, 621 | indices: attachment.triangles, 622 | uvs: attachment.uvs as Float32Array, 623 | color: new Color(1, 1, 1, 1), 624 | darkColor: new Color(0, 0, 0, 0), 625 | darkTint: false, 626 | skipRender: false, 627 | texture: attachment.region?.texture.texture, 628 | }; 629 | } 630 | 631 | return this.attachmentCacheData[slot.data.index][attachment.name]; 632 | } 633 | 634 | protected onViewUpdate() 635 | { 636 | // increment from the 12th bit! 637 | this._didChangeId += 1 << 12; 638 | 639 | this._boundsDirty = true; 640 | 641 | if (this.didViewUpdate) return; 642 | this.didViewUpdate = true; 643 | 644 | const renderGroup = this.renderGroup || this.parentRenderGroup; 645 | 646 | if (renderGroup) 647 | { 648 | renderGroup.onChildViewUpdate(this); 649 | } 650 | 651 | this.debug?.renderDebug(this); 652 | } 653 | 654 | /** 655 | * Attaches a PixiJS container to a specified slot. This will map the world transform of the slots bone 656 | * to the attached container. A container can only be attached to one slot at a time. 657 | * 658 | * @param container - The container to attach to the slot 659 | * @param slotRef - The slot id or slot to attach to 660 | */ 661 | public addSlotObject(slot: number | string | Slot, container: Container) 662 | { 663 | slot = this.getSlotFromRef(slot); 664 | 665 | // need to check in on the container too... 666 | for (const i in this._slotsObject) 667 | { 668 | if (this._slotsObject[i]?.container === container) 669 | { 670 | this.removeSlotObject(this._slotsObject[i].slot); 671 | } 672 | } 673 | 674 | this.removeSlotObject(slot); 675 | 676 | container.includeInBuild = false; 677 | 678 | // TODO only add once?? 679 | this.addChild(container); 680 | 681 | this._slotsObject[slot.data.name] = { 682 | container, 683 | slot 684 | }; 685 | 686 | this.updateSlotObject(this._slotsObject[slot.data.name]); 687 | } 688 | 689 | /** 690 | * Removes a PixiJS container from the slot it is attached to. 691 | * 692 | * @param container - The container to detach from the slot 693 | * @param slotOrContainer - The container, slot id or slot to detach from 694 | */ 695 | public removeSlotObject(slotOrContainer: number | string | Slot | Container) 696 | { 697 | let containerToRemove: Container | undefined; 698 | 699 | if (slotOrContainer instanceof Container) 700 | { 701 | for (const i in this._slotsObject) 702 | { 703 | if (this._slotsObject[i]?.container === slotOrContainer) 704 | { 705 | this._slotsObject[i] = null; 706 | 707 | containerToRemove = slotOrContainer; 708 | break; 709 | } 710 | } 711 | } 712 | else 713 | { 714 | const slot = this.getSlotFromRef(slotOrContainer); 715 | 716 | containerToRemove = this._slotsObject[slot.data.name]?.container; 717 | this._slotsObject[slot.data.name] = null; 718 | } 719 | 720 | if (containerToRemove) 721 | { 722 | this.removeChild(containerToRemove); 723 | 724 | containerToRemove.includeInBuild = true; 725 | } 726 | } 727 | 728 | /** 729 | * Returns a container attached to a slot, or undefined if no container is attached. 730 | * 731 | * @param slotRef - The slot id or slot to get the attachment from 732 | * @returns - The container attached to the slot 733 | */ 734 | public getSlotObject(slot: number | string | Slot) 735 | { 736 | slot = this.getSlotFromRef(slot); 737 | 738 | return this._slotsObject[slot.data.name].container; 739 | } 740 | 741 | private updateBounds() 742 | { 743 | this._boundsDirty = false; 744 | 745 | this.skeletonBounds ||= new SkeletonBounds(); 746 | 747 | const skeletonBounds = this.skeletonBounds; 748 | 749 | skeletonBounds.update(this.skeleton, true); 750 | 751 | if (skeletonBounds.minX === Infinity) 752 | { 753 | this._applyState(); 754 | 755 | const drawOrder = this.skeleton.drawOrder; 756 | const bounds = this._bounds; 757 | 758 | bounds.clear(); 759 | 760 | for (let i = 0; i < drawOrder.length; i++) 761 | { 762 | const slot = drawOrder[i]; 763 | 764 | const attachment = slot.getAttachment(); 765 | 766 | if (attachment && (attachment instanceof RegionAttachment || attachment instanceof MeshAttachment)) 767 | { 768 | const cacheData = this._getCachedData(slot, attachment); 769 | 770 | bounds.addVertexData(cacheData.vertices, 0, cacheData.vertices.length); 771 | } 772 | } 773 | } 774 | else 775 | { 776 | this._bounds.minX = skeletonBounds.minX; 777 | this._bounds.minY = skeletonBounds.minY; 778 | this._bounds.maxX = skeletonBounds.maxX; 779 | this._bounds.maxY = skeletonBounds.maxY; 780 | } 781 | } 782 | 783 | /** @internal */ 784 | addBounds(bounds: Bounds) 785 | { 786 | bounds.addBounds(this.bounds); 787 | } 788 | 789 | /** 790 | * Destroys this sprite renderable and optionally its texture. 791 | * @param options - Options parameter. A boolean will act as if all options 792 | * have been set to that value 793 | * @param {boolean} [options.texture=false] - Should it destroy the current texture of the renderable as well 794 | * @param {boolean} [options.textureSource=false] - Should it destroy the textureSource of the renderable as well 795 | */ 796 | public override destroy(options: DestroyOptions = false) 797 | { 798 | super.destroy(options); 799 | 800 | Ticker.shared.remove(this.internalUpdate, this); 801 | this.state.clearListeners(); 802 | this.debug = undefined; 803 | this.skeleton = null as any; 804 | this.state = null as any; 805 | (this._slotsObject as any) = null; 806 | this._lastAttachments = null; 807 | this.attachmentCacheData = null as any; 808 | } 809 | 810 | /** Converts a point from the skeleton coordinate system to the Pixi world coordinate system. */ 811 | public skeletonToPixiWorldCoordinates(point: { x: number; y: number }) 812 | { 813 | this.worldTransform.apply(point, point); 814 | } 815 | 816 | /** Converts a point from the Pixi world coordinate system to the skeleton coordinate system. */ 817 | public pixiWorldCoordinatesToSkeleton(point: { x: number; y: number }) 818 | { 819 | this.worldTransform.applyInverse(point, point); 820 | } 821 | 822 | /** Converts a point from the Pixi world coordinate system to the bone's local coordinate system. */ 823 | public pixiWorldCoordinatesToBone(point: { x: number; y: number }, bone: Bone) 824 | { 825 | this.pixiWorldCoordinatesToSkeleton(point); 826 | if (bone.parent) 827 | { 828 | bone.parent.worldToLocal(point as Vector2); 829 | } 830 | else 831 | { 832 | bone.worldToLocal(point as Vector2); 833 | } 834 | } 835 | 836 | static from({ skeleton, atlas, scale = 1 }: SpineFromOptions) 837 | { 838 | const cacheKey = `${skeleton}-${atlas}-${scale}`; 839 | 840 | if (Cache.has(cacheKey)) 841 | { 842 | return new Spine(Cache.get(cacheKey)); 843 | } 844 | 845 | const skeletonAsset = Assets.get(skeleton); 846 | 847 | const atlasAsset = Assets.get(atlas); 848 | const attachmentLoader = new AtlasAttachmentLoader(atlasAsset); 849 | // eslint-disable-next-line max-len 850 | const parser 851 | = skeletonAsset instanceof Uint8Array 852 | ? new SkeletonBinary(attachmentLoader) 853 | : new SkeletonJson(attachmentLoader); 854 | 855 | // TODO scale? 856 | parser.scale = scale; 857 | const skeletonData = parser.readSkeletonData(skeletonAsset); 858 | 859 | Cache.set(cacheKey, skeletonData); 860 | 861 | return new Spine({ 862 | skeletonData, 863 | }); 864 | } 865 | } 866 | -------------------------------------------------------------------------------- /src/SpineDebugRenderer.ts: -------------------------------------------------------------------------------- 1 | /** **************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated July 28, 2023. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2023, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software or 13 | * otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY 19 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY 22 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, 24 | * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND 25 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE 27 | * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | import { Container, Graphics, Text } from 'pixi.js'; 31 | import { Spine } from './Spine'; 32 | import { 33 | ClippingAttachment, 34 | MeshAttachment, 35 | PathAttachment, 36 | RegionAttachment, 37 | SkeletonBounds 38 | } from '@esotericsoftware/spine-core'; 39 | 40 | import type { AnimationStateListener } from '@esotericsoftware/spine-core'; 41 | 42 | /** 43 | * Make a class that extends from this interface to create your own debug renderer. 44 | * @public 45 | */ 46 | export interface ISpineDebugRenderer 47 | { 48 | /** 49 | * This will be called every frame, after the spine has been updated. 50 | */ 51 | renderDebug: (spine: Spine) => void; 52 | 53 | /** 54 | * This is called when the `spine.debug` object is set to null or when the spine is destroyed. 55 | */ 56 | unregisterSpine: (spine: Spine) => void; 57 | 58 | /** 59 | * This is called when the `spine.debug` object is set to a new instance of a debug renderer. 60 | */ 61 | registerSpine: (spine: Spine) => void; 62 | } 63 | 64 | type DebugDisplayObjects = { 65 | bones: Container; 66 | skeletonXY: Graphics; 67 | regionAttachmentsShape: Graphics; 68 | meshTrianglesLine: Graphics; 69 | meshHullLine: Graphics; 70 | clippingPolygon: Graphics; 71 | boundingBoxesRect: Graphics; 72 | boundingBoxesCircle: Graphics; 73 | boundingBoxesPolygon: Graphics; 74 | pathsCurve: Graphics; 75 | pathsLine: Graphics; 76 | parentDebugContainer: Container; 77 | eventText: Container; 78 | eventCallback: AnimationStateListener; 79 | }; 80 | 81 | /** 82 | * This is a debug renderer that uses PixiJS Graphics under the hood. 83 | * @public 84 | */ 85 | export class SpineDebugRenderer implements ISpineDebugRenderer 86 | { 87 | private readonly registeredSpines: Map = new Map(); 88 | 89 | public drawMeshHull = true; 90 | public drawMeshTriangles = true; 91 | public drawBones = true; 92 | public drawPaths = true; 93 | public drawBoundingBoxes = true; 94 | public drawClipping = true; 95 | public drawRegionAttachments = true; 96 | public drawEvents = true; 97 | 98 | public lineWidth = 1; 99 | public regionAttachmentsColor = 0x0078ff; 100 | public meshHullColor = 0x0078ff; 101 | public meshTrianglesColor = 0xffcc00; 102 | public clippingPolygonColor = 0xff00ff; 103 | public boundingBoxesRectColor = 0x00ff00; 104 | public boundingBoxesPolygonColor = 0x00ff00; 105 | public boundingBoxesCircleColor = 0x00ff00; 106 | public pathsCurveColor = 0xff0000; 107 | public pathsLineColor = 0xff00ff; 108 | public skeletonXYColor = 0xff0000; 109 | public bonesColor = 0x00eecc; 110 | public eventFontSize = 24; 111 | public eventFontColor = 0x0; 112 | 113 | /** 114 | * The debug is attached by force to each spine object. 115 | * So we need to create it inside the spine when we get the first update 116 | */ 117 | public registerSpine(spine: Spine): void 118 | { 119 | if (this.registeredSpines.has(spine)) 120 | { 121 | console.warn('SpineDebugRenderer.registerSpine() - this spine is already registered!', spine); 122 | 123 | return; 124 | } 125 | const debugDisplayObjects: DebugDisplayObjects = { 126 | parentDebugContainer: new Container(), 127 | bones: new Container(), 128 | skeletonXY: new Graphics(), 129 | regionAttachmentsShape: new Graphics(), 130 | meshTrianglesLine: new Graphics(), 131 | meshHullLine: new Graphics(), 132 | clippingPolygon: new Graphics(), 133 | boundingBoxesRect: new Graphics(), 134 | boundingBoxesCircle: new Graphics(), 135 | boundingBoxesPolygon: new Graphics(), 136 | pathsCurve: new Graphics(), 137 | pathsLine: new Graphics(), 138 | eventText: new Container(), 139 | eventCallback: { 140 | event: (_, event) => 141 | { 142 | if (this.drawEvents) 143 | { 144 | const scale = Math.abs(spine.scale.x || spine.scale.y || 1); 145 | const text = new Text({ 146 | text: event.data.name, 147 | style: { 148 | fontSize: this.eventFontSize / scale, 149 | fill: this.eventFontColor, 150 | fontFamily: 'monospace' 151 | } 152 | }); 153 | 154 | text.scale.x = Math.sign(spine.scale.x); 155 | text.anchor.set(0.5); 156 | debugDisplayObjects.eventText.addChild(text); 157 | setTimeout(() => 158 | { 159 | if (!text.destroyed) 160 | { 161 | text.destroy(); 162 | } 163 | }, 250); 164 | } 165 | }, 166 | }, 167 | }; 168 | 169 | debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.bones); 170 | debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.skeletonXY); 171 | debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.regionAttachmentsShape); 172 | debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.meshTrianglesLine); 173 | debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.meshHullLine); 174 | debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.clippingPolygon); 175 | debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.boundingBoxesRect); 176 | debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.boundingBoxesCircle); 177 | debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.boundingBoxesPolygon); 178 | debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.pathsCurve); 179 | debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.pathsLine); 180 | debugDisplayObjects.parentDebugContainer.addChild(debugDisplayObjects.eventText); 181 | 182 | (debugDisplayObjects.parentDebugContainer as any).zIndex = 9999999; 183 | 184 | // Disable screen reader and mouse input on debug objects. 185 | (debugDisplayObjects.parentDebugContainer as any).accessibleChildren = false; 186 | (debugDisplayObjects.parentDebugContainer as any).eventMode = 'none'; 187 | (debugDisplayObjects.parentDebugContainer as any).interactiveChildren = false; 188 | 189 | spine.addChild(debugDisplayObjects.parentDebugContainer); 190 | 191 | spine.state.addListener(debugDisplayObjects.eventCallback); 192 | 193 | this.registeredSpines.set(spine, debugDisplayObjects); 194 | } 195 | 196 | public renderDebug(spine: Spine): void 197 | { 198 | if (!this.registeredSpines.has(spine)) 199 | { 200 | // This should never happen. Spines are registered when you assign spine.debug 201 | this.registerSpine(spine); 202 | } 203 | 204 | const debugDisplayObjects = this.registeredSpines.get(spine); 205 | 206 | if (!debugDisplayObjects) 207 | { 208 | return; 209 | } 210 | spine.addChild(debugDisplayObjects.parentDebugContainer); 211 | 212 | debugDisplayObjects.skeletonXY.clear(); 213 | debugDisplayObjects.regionAttachmentsShape.clear(); 214 | debugDisplayObjects.meshTrianglesLine.clear(); 215 | debugDisplayObjects.meshHullLine.clear(); 216 | debugDisplayObjects.clippingPolygon.clear(); 217 | debugDisplayObjects.boundingBoxesRect.clear(); 218 | debugDisplayObjects.boundingBoxesCircle.clear(); 219 | debugDisplayObjects.boundingBoxesPolygon.clear(); 220 | debugDisplayObjects.pathsCurve.clear(); 221 | debugDisplayObjects.pathsLine.clear(); 222 | 223 | for (let len = debugDisplayObjects.bones.children.length; len > 0; len--) 224 | { 225 | debugDisplayObjects.bones.children[len - 1].destroy({ children: true, texture: true, textureSource: true }); 226 | } 227 | 228 | const scale = Math.abs(spine.scale.x || spine.scale.y || 1); 229 | const lineWidth = this.lineWidth / scale; 230 | 231 | if (this.drawBones) 232 | { 233 | this.drawBonesFunc(spine, debugDisplayObjects, lineWidth, scale); 234 | } 235 | 236 | if (this.drawPaths) 237 | { 238 | this.drawPathsFunc(spine, debugDisplayObjects, lineWidth); 239 | } 240 | 241 | if (this.drawBoundingBoxes) 242 | { 243 | this.drawBoundingBoxesFunc(spine, debugDisplayObjects, lineWidth); 244 | } 245 | 246 | if (this.drawClipping) 247 | { 248 | this.drawClippingFunc(spine, debugDisplayObjects, lineWidth); 249 | } 250 | 251 | if (this.drawMeshHull || this.drawMeshTriangles) 252 | { 253 | this.drawMeshHullAndMeshTriangles(spine, debugDisplayObjects, lineWidth); 254 | } 255 | 256 | if (this.drawRegionAttachments) 257 | { 258 | this.drawRegionAttachmentsFunc(spine, debugDisplayObjects, lineWidth); 259 | } 260 | 261 | if (this.drawEvents) 262 | { 263 | for (const child of debugDisplayObjects.eventText.children) 264 | { 265 | child.alpha -= 0.05; 266 | child.y -= 2; 267 | } 268 | } 269 | } 270 | 271 | private drawBonesFunc(spine: Spine, debugDisplayObjects: DebugDisplayObjects, lineWidth: number, scale: number): void 272 | { 273 | const skeleton = spine.skeleton; 274 | const skeletonX = skeleton.x; 275 | const skeletonY = skeleton.y; 276 | const bones = skeleton.bones; 277 | 278 | debugDisplayObjects.skeletonXY.strokeStyle = { width: lineWidth, color: this.skeletonXYColor }; 279 | 280 | for (let i = 0, len = bones.length; i < len; i++) 281 | { 282 | const bone = bones[i]; 283 | const boneLen = bone.data.length; 284 | const starX = skeletonX + bone.worldX; 285 | const starY = skeletonY + bone.worldY; 286 | const endX = skeletonX + (boneLen * bone.a) + bone.worldX; 287 | const endY = skeletonY + (boneLen * bone.b) + bone.worldY; 288 | 289 | if (bone.data.name === 'root' || bone.data.parent === null) 290 | { 291 | continue; 292 | } 293 | 294 | const w = Math.abs(starX - endX); 295 | const h = Math.abs(starY - endY); 296 | // a = w, // side length a 297 | const a2 = Math.pow(w, 2); // square root of side length a 298 | const b = h; // side length b 299 | const b2 = Math.pow(h, 2); // square root of side length b 300 | const c = Math.sqrt(a2 + b2); // side length c 301 | const c2 = Math.pow(c, 2); // square root of side length c 302 | const rad = Math.PI / 180; 303 | // A = Math.acos([a2 + c2 - b2] / [2 * a * c]) || 0, // Angle A 304 | // C = Math.acos([a2 + b2 - c2] / [2 * a * b]) || 0, // C angle 305 | const B = Math.acos((c2 + b2 - a2) / (2 * b * c)) || 0; // angle of corner B 306 | 307 | if (c === 0) 308 | { 309 | continue; 310 | } 311 | 312 | const gp = new Graphics(); 313 | 314 | debugDisplayObjects.bones.addChild(gp); 315 | 316 | // draw bone 317 | const refRation = c / 50 / scale; 318 | 319 | gp.context 320 | .poly([0, 0, 0 - refRation, c - (refRation * 3), 0, c - refRation, 0 + refRation, c - (refRation * 3)]) 321 | .fill(this.bonesColor); 322 | gp.x = starX; 323 | gp.y = starY; 324 | gp.pivot.y = c; 325 | 326 | // Calculate bone rotation angle 327 | let rotation = 0; 328 | 329 | if (starX < endX && starY < endY) 330 | { 331 | // bottom right 332 | rotation = -B + (180 * rad); 333 | } 334 | else if (starX > endX && starY < endY) 335 | { 336 | // bottom left 337 | rotation = 180 * (rad + B); 338 | } 339 | else if (starX > endX && starY > endY) 340 | { 341 | // top left 342 | rotation = -B; 343 | } 344 | else if (starX < endX && starY > endY) 345 | { 346 | // bottom left 347 | rotation = B; 348 | } 349 | else if (starY === endY && starX < endX) 350 | { 351 | // To the right 352 | rotation = 90 * rad; 353 | } 354 | else if (starY === endY && starX > endX) 355 | { 356 | // go left 357 | rotation = -90 * rad; 358 | } 359 | else if (starX === endX && starY < endY) 360 | { 361 | // down 362 | rotation = 180 * rad; 363 | } 364 | else if (starX === endX && starY > endY) 365 | { 366 | // up 367 | rotation = 0; 368 | } 369 | gp.rotation = rotation; 370 | 371 | // Draw the starting rotation point of the bone 372 | gp.circle(0, c, refRation * 1.2) 373 | .fill({ color: 0x000000, alpha: 0.6 }) 374 | .stroke({ width: lineWidth, color: this.skeletonXYColor }); 375 | } 376 | 377 | // Draw the skeleton starting point "X" form 378 | const startDotSize = lineWidth * 3; 379 | 380 | debugDisplayObjects.skeletonXY.context 381 | .moveTo(skeletonX - startDotSize, skeletonY - startDotSize) 382 | .lineTo(skeletonX + startDotSize, skeletonY + startDotSize) 383 | .moveTo(skeletonX + startDotSize, skeletonY - startDotSize) 384 | .lineTo(skeletonX - startDotSize, skeletonY + startDotSize) 385 | .stroke(); 386 | } 387 | 388 | private drawRegionAttachmentsFunc(spine: Spine, debugDisplayObjects: DebugDisplayObjects, lineWidth: number): void 389 | { 390 | const skeleton = spine.skeleton; 391 | const slots = skeleton.slots; 392 | 393 | for (let i = 0, len = slots.length; i < len; i++) 394 | { 395 | const slot = slots[i]; 396 | const attachment = slot.getAttachment(); 397 | 398 | if (attachment === null || !(attachment instanceof RegionAttachment)) 399 | { 400 | continue; 401 | } 402 | 403 | const regionAttachment = attachment; 404 | 405 | const vertices = new Float32Array(8); 406 | 407 | regionAttachment.computeWorldVertices(slot, vertices, 0, 2); 408 | 409 | debugDisplayObjects.regionAttachmentsShape.poly(Array.from(vertices.slice(0, 8))); 410 | } 411 | 412 | debugDisplayObjects.regionAttachmentsShape.stroke({ 413 | color: this.regionAttachmentsColor, 414 | width: lineWidth 415 | }); 416 | } 417 | 418 | private drawMeshHullAndMeshTriangles(spine: Spine, debugDisplayObjects: DebugDisplayObjects, lineWidth: number): void 419 | { 420 | const skeleton = spine.skeleton; 421 | const slots = skeleton.slots; 422 | 423 | for (let i = 0, len = slots.length; i < len; i++) 424 | { 425 | const slot = slots[i]; 426 | 427 | if (!slot.bone.active) 428 | { 429 | continue; 430 | } 431 | const attachment = slot.getAttachment(); 432 | 433 | if (attachment === null || !(attachment instanceof MeshAttachment)) 434 | { 435 | continue; 436 | } 437 | 438 | const meshAttachment = attachment; 439 | 440 | const vertices = new Float32Array(meshAttachment.worldVerticesLength); 441 | const triangles = meshAttachment.triangles; 442 | let hullLength = meshAttachment.hullLength; 443 | 444 | meshAttachment.computeWorldVertices(slot, 0, meshAttachment.worldVerticesLength, vertices, 0, 2); 445 | // draw the skinned mesh (triangle) 446 | if (this.drawMeshTriangles) 447 | { 448 | for (let i = 0, len = triangles.length; i < len; i += 3) 449 | { 450 | const v1 = triangles[i] * 2; 451 | const v2 = triangles[i + 1] * 2; 452 | const v3 = triangles[i + 2] * 2; 453 | 454 | debugDisplayObjects.meshTrianglesLine.context 455 | .moveTo(vertices[v1], vertices[v1 + 1]) 456 | .lineTo(vertices[v2], vertices[v2 + 1]) 457 | .lineTo(vertices[v3], vertices[v3 + 1]); 458 | } 459 | } 460 | 461 | // draw skin border 462 | if (this.drawMeshHull && hullLength > 0) 463 | { 464 | hullLength = (hullLength >> 1) * 2; 465 | let lastX = vertices[hullLength - 2]; 466 | let lastY = vertices[hullLength - 1]; 467 | 468 | for (let i = 0, len = hullLength; i < len; i += 2) 469 | { 470 | const x = vertices[i]; 471 | const y = vertices[i + 1]; 472 | 473 | debugDisplayObjects.meshHullLine.context 474 | .moveTo(x, y) 475 | .lineTo(lastX, lastY); 476 | lastX = x; 477 | lastY = y; 478 | } 479 | } 480 | } 481 | 482 | debugDisplayObjects.meshHullLine.stroke({ width: lineWidth, color: this.meshHullColor }); 483 | debugDisplayObjects.meshTrianglesLine.stroke({ width: lineWidth, color: this.meshTrianglesColor }); 484 | } 485 | 486 | drawClippingFunc(spine: Spine, debugDisplayObjects: DebugDisplayObjects, lineWidth: number): void 487 | { 488 | const skeleton = spine.skeleton; 489 | const slots = skeleton.slots; 490 | 491 | for (let i = 0, len = slots.length; i < len; i++) 492 | { 493 | const slot = slots[i]; 494 | 495 | if (!slot.bone.active) 496 | { 497 | continue; 498 | } 499 | const attachment = slot.getAttachment(); 500 | 501 | if (attachment === null || !(attachment instanceof ClippingAttachment)) 502 | { 503 | continue; 504 | } 505 | 506 | const clippingAttachment = attachment; 507 | 508 | const nn = clippingAttachment.worldVerticesLength; 509 | const world = new Float32Array(nn); 510 | 511 | clippingAttachment.computeWorldVertices(slot, 0, nn, world, 0, 2); 512 | debugDisplayObjects.clippingPolygon.poly(Array.from(world)); 513 | } 514 | 515 | debugDisplayObjects.clippingPolygon.stroke({ 516 | width: lineWidth, color: this.clippingPolygonColor, alpha: 1 517 | }); 518 | } 519 | 520 | drawBoundingBoxesFunc(spine: Spine, debugDisplayObjects: DebugDisplayObjects, lineWidth: number): void 521 | { 522 | // draw the total outline of the bounding box 523 | debugDisplayObjects.boundingBoxesRect.lineStyle(lineWidth, this.boundingBoxesRectColor, 5); 524 | 525 | const bounds = new SkeletonBounds(); 526 | 527 | bounds.update(spine.skeleton, true); 528 | 529 | debugDisplayObjects.boundingBoxesRect 530 | .rect(bounds.minX, bounds.minY, bounds.getWidth(), bounds.getHeight()) 531 | .stroke({ width: lineWidth, color: this.boundingBoxesRectColor }); 532 | 533 | const polygons = bounds.polygons; 534 | const drawPolygon = (polygonVertices: ArrayLike, _offset: unknown, count: number): void => 535 | { 536 | if (count < 3) 537 | { 538 | throw new Error('Polygon must contain at least 3 vertices'); 539 | } 540 | const paths:number[] = []; 541 | const dotSize = lineWidth * 2; 542 | 543 | for (let i = 0, len = polygonVertices.length; i < len; i += 2) 544 | { 545 | const x1 = polygonVertices[i]; 546 | const y1 = polygonVertices[i + 1]; 547 | 548 | // draw the bounding box node 549 | debugDisplayObjects.boundingBoxesCircle.beginFill(this.boundingBoxesCircleColor); 550 | debugDisplayObjects.boundingBoxesCircle.drawCircle(x1, y1, dotSize); 551 | debugDisplayObjects.boundingBoxesCircle.fill(0); 552 | 553 | paths.push(x1, y1); 554 | } 555 | 556 | // draw the bounding box area 557 | debugDisplayObjects.boundingBoxesPolygon 558 | .poly(paths) 559 | .fill({ 560 | color: this.boundingBoxesPolygonColor, 561 | alpha: 0.1 562 | }) 563 | .stroke({ 564 | width: lineWidth, 565 | color: this.boundingBoxesPolygonColor 566 | }); 567 | }; 568 | 569 | for (let i = 0, len = polygons.length; i < len; i++) 570 | { 571 | const polygon = polygons[i]; 572 | 573 | drawPolygon(polygon, 0, polygon.length); 574 | } 575 | } 576 | 577 | private drawPathsFunc(spine: Spine, debugDisplayObjects: DebugDisplayObjects, lineWidth: number): void 578 | { 579 | const skeleton = spine.skeleton; 580 | const slots = skeleton.slots; 581 | 582 | for (let i = 0, len = slots.length; i < len; i++) 583 | { 584 | const slot = slots[i]; 585 | 586 | if (!slot.bone.active) 587 | { 588 | continue; 589 | } 590 | const attachment = slot.getAttachment(); 591 | 592 | if (attachment === null || !(attachment instanceof PathAttachment)) 593 | { 594 | continue; 595 | } 596 | 597 | const pathAttachment = attachment; 598 | let nn = pathAttachment.worldVerticesLength; 599 | const world = new Float32Array(nn); 600 | 601 | pathAttachment.computeWorldVertices(slot, 0, nn, world, 0, 2); 602 | let x1 = world[2]; 603 | let y1 = world[3]; 604 | let x2 = 0; 605 | let y2 = 0; 606 | 607 | if (pathAttachment.closed) 608 | { 609 | const cx1 = world[0]; 610 | const cy1 = world[1]; 611 | const cx2 = world[nn - 2]; 612 | const cy2 = world[nn - 1]; 613 | 614 | x2 = world[nn - 4]; 615 | y2 = world[nn - 3]; 616 | 617 | // curve 618 | debugDisplayObjects.pathsCurve.moveTo(x1, y1); 619 | debugDisplayObjects.pathsCurve.bezierCurveTo(cx1, cy1, cx2, cy2, x2, y2); 620 | 621 | // handle 622 | debugDisplayObjects.pathsLine.moveTo(x1, y1); 623 | debugDisplayObjects.pathsLine.lineTo(cx1, cy1); 624 | debugDisplayObjects.pathsLine.moveTo(x2, y2); 625 | debugDisplayObjects.pathsLine.lineTo(cx2, cy2); 626 | } 627 | nn -= 4; 628 | for (let ii = 4; ii < nn; ii += 6) 629 | { 630 | const cx1 = world[ii]; 631 | const cy1 = world[ii + 1]; 632 | const cx2 = world[ii + 2]; 633 | const cy2 = world[ii + 3]; 634 | 635 | x2 = world[ii + 4]; 636 | y2 = world[ii + 5]; 637 | // curve 638 | debugDisplayObjects.pathsCurve.moveTo(x1, y1); 639 | debugDisplayObjects.pathsCurve.bezierCurveTo(cx1, cy1, cx2, cy2, x2, y2); 640 | 641 | // handle 642 | debugDisplayObjects.pathsLine.moveTo(x1, y1); 643 | debugDisplayObjects.pathsLine.lineTo(cx1, cy1); 644 | debugDisplayObjects.pathsLine.moveTo(x2, y2); 645 | debugDisplayObjects.pathsLine.lineTo(cx2, cy2); 646 | x1 = x2; 647 | y1 = y2; 648 | } 649 | } 650 | 651 | debugDisplayObjects.pathsCurve.stroke({ width: lineWidth, color: this.pathsCurveColor }); 652 | debugDisplayObjects.pathsLine.stroke({ width: lineWidth, color: this.pathsLineColor }); 653 | } 654 | 655 | public unregisterSpine(spine: Spine): void 656 | { 657 | if (!this.registeredSpines.has(spine)) 658 | { 659 | console.warn('SpineDebugRenderer.unregisterSpine() - spine is not registered, can\'t unregister!', spine); 660 | } 661 | const debugDisplayObjects = this.registeredSpines.get(spine); 662 | 663 | if (!debugDisplayObjects) 664 | { 665 | return; 666 | } 667 | 668 | spine.state.removeListener(debugDisplayObjects.eventCallback); 669 | 670 | debugDisplayObjects.parentDebugContainer.destroy({ textureSource: true, children: true, texture: true }); 671 | this.registeredSpines.delete(spine); 672 | } 673 | } 674 | -------------------------------------------------------------------------------- /src/SpinePipe.ts: -------------------------------------------------------------------------------- 1 | /** **************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated July 28, 2023. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2023, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software or 13 | * otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY 19 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY 22 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, 24 | * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND 25 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE 27 | * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | import { 31 | collectAllRenderables, 32 | extensions, ExtensionType, 33 | InstructionSet, 34 | type Renderer, 35 | type RenderPipe, 36 | } from 'pixi.js'; 37 | import { BatchableSpineSlot } from './BatchableSpineSlot'; 38 | import { Spine } from './Spine'; 39 | import { MeshAttachment, RegionAttachment, SkeletonClipping } from '@esotericsoftware/spine-core'; 40 | 41 | const clipper = new SkeletonClipping(); 42 | 43 | const spineBlendModeMap = { 44 | 0: 'normal', 45 | 1: 'add', 46 | 2: 'multiply', 47 | 3: 'screen' 48 | }; 49 | 50 | // eslint-disable-next-line max-len 51 | export class SpinePipe implements RenderPipe 52 | { 53 | /** @ignore */ 54 | static extension = { 55 | type: [ 56 | ExtensionType.WebGLPipes, 57 | ExtensionType.WebGPUPipes, 58 | ExtensionType.CanvasPipes, 59 | ], 60 | name: 'spine', 61 | } as const; 62 | 63 | renderer: Renderer; 64 | 65 | private gpuSpineData:Record = {}; 66 | 67 | constructor(renderer: Renderer) 68 | { 69 | this.renderer = renderer; 70 | } 71 | 72 | validateRenderable(spine: Spine): boolean 73 | { 74 | spine._applyState(); 75 | 76 | // if pine attachments have changed, we need to rebuild the batch! 77 | if (spine.spineAttachmentsDirty) 78 | { 79 | return true; 80 | } 81 | // if the textures have changed, we need to rebuild the batch, but only if the texture is not already in the batch 82 | else if (spine.spineTexturesDirty) 83 | { 84 | // loop through and see if the textures have changed.. 85 | const drawOrder = spine.skeleton.drawOrder; 86 | const gpuSpine = this.gpuSpineData[spine.uid]; 87 | 88 | for (let i = 0, n = drawOrder.length; i < n; i++) 89 | { 90 | const slot = drawOrder[i]; 91 | const attachment = slot.getAttachment(); 92 | 93 | if (attachment instanceof RegionAttachment || attachment instanceof MeshAttachment) 94 | { 95 | const cacheData = spine._getCachedData(slot, attachment); 96 | const batchableSpineSlot = gpuSpine.slotBatches[cacheData.id]; 97 | 98 | const texture = cacheData.texture; 99 | 100 | if (texture !== batchableSpineSlot.texture) 101 | { 102 | if (!batchableSpineSlot._batcher.checkAndUpdateTexture(batchableSpineSlot, texture)) 103 | { 104 | return true; 105 | } 106 | } 107 | } 108 | } 109 | } 110 | 111 | return false; 112 | } 113 | 114 | addRenderable(spine: Spine, instructionSet:InstructionSet) 115 | { 116 | const gpuSpine = this.gpuSpineData[spine.uid] ||= { slotBatches: {} }; 117 | 118 | const batcher = this.renderer.renderPipes.batch; 119 | 120 | const drawOrder = spine.skeleton.drawOrder; 121 | 122 | const roundPixels = (this.renderer._roundPixels | spine._roundPixels) as 0 | 1; 123 | 124 | spine._applyState(); 125 | 126 | for (let i = 0, n = drawOrder.length; i < n; i++) 127 | { 128 | const slot = drawOrder[i]; 129 | const attachment = slot.getAttachment(); 130 | const blendMode = spineBlendModeMap[slot.data.blendMode]; 131 | 132 | if (attachment instanceof RegionAttachment || attachment instanceof MeshAttachment) 133 | { 134 | const cacheData = spine._getCachedData(slot, attachment); 135 | const batchableSpineSlot = gpuSpine.slotBatches[cacheData.id] ||= new BatchableSpineSlot(); 136 | 137 | batchableSpineSlot.setData( 138 | spine, 139 | cacheData, 140 | blendMode, 141 | roundPixels 142 | ); 143 | 144 | if (!cacheData.skipRender) 145 | { 146 | batcher.addToBatch(batchableSpineSlot, instructionSet); 147 | } 148 | } 149 | 150 | const containerAttachment = spine._slotsObject[slot.data.name]; 151 | 152 | if (containerAttachment) 153 | { 154 | const container = containerAttachment.container; 155 | 156 | container.includeInBuild = true; 157 | collectAllRenderables(container, instructionSet, this.renderer); 158 | container.includeInBuild = false; 159 | } 160 | } 161 | 162 | clipper.clipEnd(); 163 | } 164 | 165 | updateRenderable(spine: Spine) 166 | { 167 | // we assume that spine will always change its verts size.. 168 | const gpuSpine = this.gpuSpineData[spine.uid]; 169 | 170 | spine._applyState(); 171 | 172 | const drawOrder = spine.skeleton.drawOrder; 173 | 174 | for (let i = 0, n = drawOrder.length; i < n; i++) 175 | { 176 | const slot = drawOrder[i]; 177 | const attachment = slot.getAttachment(); 178 | 179 | if (attachment instanceof RegionAttachment || attachment instanceof MeshAttachment) 180 | { 181 | const cacheData = spine._getCachedData(slot, attachment); 182 | 183 | if (!cacheData.skipRender) 184 | { 185 | const batchableSpineSlot = gpuSpine.slotBatches[spine._getCachedData(slot, attachment).id]; 186 | 187 | batchableSpineSlot._batcher?.updateElement(batchableSpineSlot); 188 | } 189 | } 190 | } 191 | } 192 | 193 | destroyRenderable(spine: Spine) 194 | { 195 | // TODO remove the renderable from the batcher 196 | this.gpuSpineData[spine.uid] = null as any; 197 | } 198 | 199 | destroy() 200 | { 201 | this.gpuSpineData = null as any; 202 | this.renderer = null as any; 203 | } 204 | } 205 | 206 | extensions.add(SpinePipe); 207 | -------------------------------------------------------------------------------- /src/SpineTexture.ts: -------------------------------------------------------------------------------- 1 | /** **************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated July 28, 2023. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2023, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software or 13 | * otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY 19 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY 22 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, 24 | * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND 25 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE 27 | * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | import { Texture as PixiTexture } from 'pixi.js'; 31 | import { BlendMode, Texture, TextureFilter, TextureWrap } from '@esotericsoftware/spine-core'; 32 | 33 | import type { BLEND_MODES, SCALE_MODE, TextureSource, WRAP_MODE } from 'pixi.js'; 34 | 35 | export class SpineTexture extends Texture 36 | { 37 | private static readonly textureMap: Map = new Map(); 38 | 39 | public static from(texture: TextureSource): SpineTexture 40 | { 41 | if (SpineTexture.textureMap.has(texture)) 42 | { 43 | return SpineTexture.textureMap.get(texture) as SpineTexture; 44 | } 45 | 46 | return new SpineTexture(texture); 47 | } 48 | 49 | public readonly texture: PixiTexture; 50 | 51 | private constructor(image: TextureSource) 52 | { 53 | // Todo: maybe add error handling if you feed a video texture to spine? 54 | super(image.resource); 55 | this.texture = PixiTexture.from(image); 56 | } 57 | 58 | public setFilters(minFilter: TextureFilter, magFilter: TextureFilter): void 59 | { 60 | const style = this.texture.source.style; 61 | 62 | style.minFilter = SpineTexture.toPixiTextureFilter(minFilter); 63 | style.magFilter = SpineTexture.toPixiTextureFilter(magFilter); 64 | this.texture.source.autoGenerateMipmaps = SpineTexture.toPixiMipMap(minFilter); 65 | this.texture.source.updateMipmaps(); 66 | } 67 | 68 | public setWraps(uWrap: TextureWrap, vWrap: TextureWrap): void 69 | { 70 | const style = this.texture.source.style; 71 | 72 | style.addressModeU = SpineTexture.toPixiTextureWrap(uWrap); 73 | style.addressModeV = SpineTexture.toPixiTextureWrap(vWrap); 74 | } 75 | 76 | public dispose(): void 77 | { 78 | // I am not entirely sure about this... 79 | this.texture.destroy(); 80 | } 81 | 82 | private static toPixiMipMap(filter: TextureFilter): boolean 83 | { 84 | switch (filter) 85 | { 86 | case TextureFilter.Nearest: 87 | case TextureFilter.Linear: 88 | return false; 89 | 90 | case TextureFilter.MipMapNearestLinear: 91 | case TextureFilter.MipMapNearestNearest: 92 | case TextureFilter.MipMapLinearLinear: // TextureFilter.MipMapLinearLinear == TextureFilter.MipMap 93 | case TextureFilter.MipMapLinearNearest: 94 | return true; 95 | 96 | default: 97 | throw new Error(`Unknown texture filter: ${String(filter)}`); 98 | } 99 | } 100 | 101 | private static toPixiTextureFilter(filter: TextureFilter): SCALE_MODE 102 | { 103 | switch (filter) 104 | { 105 | case TextureFilter.Nearest: 106 | case TextureFilter.MipMapNearestLinear: 107 | case TextureFilter.MipMapNearestNearest: 108 | return 'nearest'; 109 | 110 | case TextureFilter.Linear: 111 | case TextureFilter.MipMapLinearLinear: // TextureFilter.MipMapLinearLinear == TextureFilter.MipMap 112 | case TextureFilter.MipMapLinearNearest: 113 | return 'linear'; 114 | 115 | default: 116 | throw new Error(`Unknown texture filter: ${String(filter)}`); 117 | } 118 | } 119 | 120 | private static toPixiTextureWrap(wrap: TextureWrap): WRAP_MODE 121 | { 122 | switch (wrap) 123 | { 124 | case TextureWrap.ClampToEdge: 125 | return 'clamp-to-edge'; 126 | 127 | case TextureWrap.MirroredRepeat: 128 | return 'mirror-repeat'; 129 | 130 | case TextureWrap.Repeat: 131 | return 'repeat'; 132 | 133 | default: 134 | throw new Error(`Unknown texture wrap: ${String(wrap)}`); 135 | } 136 | } 137 | 138 | public static toPixiBlending(blend: BlendMode): BLEND_MODES 139 | { 140 | switch (blend) 141 | { 142 | case BlendMode.Normal: 143 | return 'normal'; 144 | 145 | case BlendMode.Additive: 146 | return 'add'; 147 | 148 | case BlendMode.Multiply: 149 | return 'multiply'; 150 | 151 | case BlendMode.Screen: 152 | return 'screen'; 153 | 154 | default: 155 | throw new Error(`Unknown blendMode: ${String(blend)}`); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/assets/atlasLoader.ts: -------------------------------------------------------------------------------- 1 | /** **************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated July 28, 2023. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2023, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software or 13 | * otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY 19 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY 22 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, 24 | * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND 25 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE 27 | * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | import { 31 | checkExtension, 32 | DOMAdapter, 33 | extensions, 34 | ExtensionType, 35 | LoaderParserPriority, 36 | path, 37 | TextureSource 38 | } from 'pixi.js'; 39 | import { SpineTexture } from '../SpineTexture'; 40 | import { TextureAtlas } from '@esotericsoftware/spine-core'; 41 | 42 | import type { AssetExtension, Loader, ResolvedAsset, Texture } from 'pixi.js'; 43 | 44 | type RawAtlas = string; 45 | 46 | const spineTextureAtlasLoader: AssetExtension = { 47 | extension: ExtensionType.Asset, 48 | 49 | loader: { 50 | extension: { 51 | type: ExtensionType.LoadParser, 52 | priority: LoaderParserPriority.Normal, 53 | name: 'spineTextureAtlasLoader', 54 | }, 55 | 56 | test(url: string): boolean 57 | { 58 | return checkExtension(url, '.atlas'); 59 | }, 60 | 61 | async load(url: string): Promise 62 | { 63 | const response = await DOMAdapter.get().fetch(url); 64 | 65 | const txt = await response.text(); 66 | 67 | return txt; 68 | }, 69 | 70 | testParse(asset: unknown, options: ResolvedAsset): Promise 71 | { 72 | const isExtensionRight = checkExtension(options.src as string, '.atlas'); 73 | const isString = typeof asset === 'string'; 74 | 75 | return Promise.resolve(isExtensionRight && isString); 76 | }, 77 | 78 | unload(atlas: TextureAtlas) 79 | { 80 | atlas.dispose(); 81 | }, 82 | 83 | async parse(asset: RawAtlas, options: ResolvedAsset, loader: Loader): Promise 84 | { 85 | const metadata: ISpineAtlasMetadata = options.data || {}; 86 | let basePath = path.dirname(options.src as string); 87 | 88 | if (basePath && basePath.lastIndexOf('/') !== basePath.length - 1) 89 | { 90 | basePath += '/'; 91 | } 92 | 93 | // Retval is going to be a texture atlas. However we need to wait for it's callback to resolve this promise. 94 | const retval = new TextureAtlas(asset); 95 | 96 | // If the user gave me only one texture, that one is assumed to be the "first" texture in the atlas 97 | if (metadata.images instanceof TextureSource || typeof metadata.images === 'string') 98 | { 99 | const pixiTexture = metadata.images; 100 | 101 | metadata.images = {} as Record; 102 | metadata.images[retval.pages[0].name] = pixiTexture; 103 | } 104 | 105 | // we will wait for all promises for the textures at the same time at the end. 106 | const textureLoadingPromises:Promise[] = []; 107 | 108 | // fill the pages 109 | for (const page of retval.pages) 110 | { 111 | const pageName = page.name; 112 | const providedPage = metadata?.images ? metadata.images[pageName] : undefined; 113 | 114 | if (providedPage instanceof TextureSource) 115 | { 116 | page.setTexture(SpineTexture.from(providedPage)); 117 | } 118 | else 119 | { 120 | // eslint-disable-next-line max-len 121 | const url: string = providedPage ?? path.normalize([...basePath.split(path.sep), pageName].join(path.sep)); 122 | 123 | const assetsToLoadIn = { 124 | src: url, 125 | data: { 126 | ...metadata.imageMetadata, 127 | alphaMode: page.pma ? 'premultiplied-alpha' : 'premultiply-alpha-on-upload' 128 | } 129 | }; 130 | 131 | const pixiPromise = loader.load(assetsToLoadIn).then((texture) => 132 | { 133 | page.setTexture(SpineTexture.from(texture.source)); 134 | }); 135 | 136 | textureLoadingPromises.push(pixiPromise); 137 | } 138 | } 139 | 140 | await Promise.all(textureLoadingPromises); 141 | 142 | return retval; 143 | }, 144 | }, 145 | } as AssetExtension; 146 | 147 | extensions.add(spineTextureAtlasLoader); 148 | 149 | export interface ISpineAtlasMetadata 150 | { 151 | // If you are downloading an .atlas file, this metadata will go to the Texture loader 152 | imageMetadata?: any; 153 | // If you already have atlas pages loaded as pixi textures 154 | // and want to use that to create the atlas, you can pass them here 155 | images?: TextureSource | string | Record; 156 | } 157 | -------------------------------------------------------------------------------- /src/assets/skeletonLoader.ts: -------------------------------------------------------------------------------- 1 | /** **************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated July 28, 2023. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2023, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software or 13 | * otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY 19 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY 22 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, 24 | * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND 25 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE 27 | * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | import { 31 | type AssetExtension, 32 | checkExtension, 33 | DOMAdapter, 34 | extensions, 35 | ExtensionType, 36 | LoaderParserPriority, 37 | ResolvedAsset 38 | } from 'pixi.js'; 39 | 40 | type SkeletonJsonAsset = any; 41 | type SkeletonBinaryAsset = Uint8Array; 42 | 43 | function isJson(resource: any): resource is SkeletonJsonAsset 44 | { 45 | return Object.prototype.hasOwnProperty.call(resource, 'bones'); 46 | } 47 | 48 | function isBuffer(resource: any): resource is SkeletonBinaryAsset 49 | { 50 | return resource instanceof Uint8Array; 51 | } 52 | 53 | const spineLoaderExtension: AssetExtension = { 54 | extension: ExtensionType.Asset, 55 | 56 | loader: { 57 | extension: { 58 | type: ExtensionType.LoadParser, 59 | priority: LoaderParserPriority.Normal, 60 | name: 'spineSkeletonLoader', 61 | }, 62 | 63 | test(url) 64 | { 65 | return checkExtension(url, '.skel'); 66 | }, 67 | 68 | async load(url: string): Promise 69 | { 70 | const response = await DOMAdapter.get().fetch(url); 71 | 72 | const buffer = new Uint8Array(await response.arrayBuffer()); 73 | 74 | return buffer; 75 | }, 76 | testParse(asset: unknown, options: ResolvedAsset): Promise 77 | { 78 | const isJsonSpineModel = checkExtension(options.src, '.json') && isJson(asset); 79 | const isBinarySpineModel = checkExtension(options.src, '.skel') && isBuffer(asset); 80 | 81 | return Promise.resolve(isJsonSpineModel || isBinarySpineModel); 82 | }, 83 | }, 84 | } as AssetExtension; 85 | 86 | extensions.add(spineLoaderExtension); 87 | -------------------------------------------------------------------------------- /src/darktint/DarkTintBatchGeometry.ts: -------------------------------------------------------------------------------- 1 | import { Buffer, BufferUsage, Geometry } from 'pixi.js'; 2 | 3 | const placeHolderBufferData = new Float32Array(1); 4 | const placeHolderIndexData = new Uint32Array(1); 5 | 6 | export class DarkTintBatchGeometry extends Geometry 7 | { 8 | constructor() 9 | { 10 | const vertexSize = 7; 11 | 12 | const attributeBuffer = new Buffer({ 13 | data: placeHolderBufferData, 14 | label: 'attribute-batch-buffer', 15 | usage: BufferUsage.VERTEX | BufferUsage.COPY_DST, 16 | shrinkToFit: false, 17 | }); 18 | 19 | const indexBuffer = new Buffer({ 20 | data: placeHolderIndexData, 21 | label: 'index-batch-buffer', 22 | usage: BufferUsage.INDEX | BufferUsage.COPY_DST, // | BufferUsage.STATIC, 23 | shrinkToFit: false, 24 | }); 25 | 26 | const stride = vertexSize * 4; 27 | 28 | super({ 29 | attributes: { 30 | aPosition: { 31 | buffer: attributeBuffer, 32 | format: 'float32x2', 33 | stride, 34 | offset: 0, 35 | }, 36 | aUV: { 37 | buffer: attributeBuffer, 38 | format: 'float32x2', 39 | stride, 40 | offset: 2 * 4, 41 | }, 42 | aColor: { 43 | buffer: attributeBuffer, 44 | format: 'unorm8x4', 45 | stride, 46 | offset: 4 * 4, 47 | }, 48 | aDarkColor: { 49 | buffer: attributeBuffer, 50 | format: 'unorm8x4', 51 | stride, 52 | offset: 5 * 4, 53 | }, 54 | aTextureIdAndRound: { 55 | buffer: attributeBuffer, 56 | format: 'uint16x2', 57 | stride, 58 | offset: 6 * 4, 59 | }, 60 | }, 61 | indexBuffer 62 | }); 63 | } 64 | } 65 | 66 | -------------------------------------------------------------------------------- /src/darktint/DarkTintBatcher.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Batcher, 3 | Color, 4 | DefaultBatchableMeshElement, 5 | DefaultBatchableQuadElement, 6 | extensions, 7 | ExtensionType, 8 | Shader 9 | } from 'pixi.js'; 10 | import { DarkTintBatchGeometry } from './DarkTintBatchGeometry'; 11 | import { DarkTintShader } from './DarkTintShader'; 12 | 13 | let defaultShader: Shader = null; 14 | 15 | /** The default batcher is used to batch quads and meshes. */ 16 | export class DarkTintBatcher extends Batcher 17 | { 18 | /** @ignore */ 19 | public static extension = { 20 | type: [ 21 | ExtensionType.Batcher, 22 | ], 23 | name: 'darkTint', 24 | } as const; 25 | 26 | public geometry = new DarkTintBatchGeometry(); 27 | public shader = defaultShader || (defaultShader = new DarkTintShader(this.maxTextures)); 28 | public name = DarkTintBatcher.extension.name; 29 | 30 | /** The size of one attribute. 1 = 32 bit. x, y, u, v, color, darkColor, textureIdAndRound -> total = 7 */ 31 | public vertexSize = 7; 32 | 33 | public packAttributes( 34 | element: DefaultBatchableMeshElement & { darkColor: number }, 35 | float32View: Float32Array, 36 | uint32View: Uint32Array, 37 | index: number, 38 | textureId: number 39 | ) 40 | { 41 | const textureIdAndRound = (textureId << 16) | (element.roundPixels & 0xFFFF); 42 | 43 | const wt = element.transform; 44 | 45 | const a = wt.a; 46 | const b = wt.b; 47 | const c = wt.c; 48 | const d = wt.d; 49 | const tx = wt.tx; 50 | const ty = wt.ty; 51 | 52 | const { positions, uvs } = element; 53 | 54 | const argb = element.color; 55 | const worldAlpha = ((argb >> 24) & 0xFF) / 255; 56 | const darkColor = Color.shared.setValue(element.darkColor).premultiply(worldAlpha, true).toPremultiplied(1, false); 57 | 58 | const offset = element.attributeOffset; 59 | const end = offset + element.attributeSize; 60 | 61 | for (let i = offset; i < end; i++) 62 | { 63 | const i2 = i * 2; 64 | 65 | const x = positions[i2]; 66 | const y = positions[(i2) + 1]; 67 | 68 | float32View[index++] = (a * x) + (c * y) + tx; 69 | float32View[index++] = (d * y) + (b * x) + ty; 70 | 71 | float32View[index++] = uvs[i2]; 72 | float32View[index++] = uvs[(i2) + 1]; 73 | 74 | uint32View[index++] = argb; 75 | uint32View[index++] = darkColor; 76 | 77 | uint32View[index++] = textureIdAndRound; 78 | } 79 | } 80 | 81 | public packQuadAttributes( 82 | element: DefaultBatchableQuadElement & { darkColor: number }, 83 | float32View: Float32Array, 84 | uint32View: Uint32Array, 85 | index: number, 86 | textureId: number 87 | ) 88 | { 89 | const texture = element.texture; 90 | 91 | const wt = element.transform; 92 | 93 | const a = wt.a; 94 | const b = wt.b; 95 | const c = wt.c; 96 | const d = wt.d; 97 | const tx = wt.tx; 98 | const ty = wt.ty; 99 | 100 | const bounds = element.bounds; 101 | 102 | const w0 = bounds.maxX; 103 | const w1 = bounds.minX; 104 | const h0 = bounds.maxY; 105 | const h1 = bounds.minY; 106 | 107 | const uvs = texture.uvs; 108 | 109 | // _ _ _ _ 110 | // a b g r 111 | const argb = element.color; 112 | const darkColor = element.darkColor; 113 | 114 | const textureIdAndRound = (textureId << 16) | (element.roundPixels & 0xFFFF); 115 | 116 | float32View[index + 0] = (a * w1) + (c * h1) + tx; 117 | float32View[index + 1] = (d * h1) + (b * w1) + ty; 118 | 119 | float32View[index + 2] = uvs.x0; 120 | float32View[index + 3] = uvs.y0; 121 | 122 | uint32View[index + 4] = argb; 123 | uint32View[index + 5] = darkColor; 124 | uint32View[index + 6] = textureIdAndRound; 125 | 126 | // xy 127 | float32View[index + 7] = (a * w0) + (c * h1) + tx; 128 | float32View[index + 8] = (d * h1) + (b * w0) + ty; 129 | 130 | float32View[index + 9] = uvs.x1; 131 | float32View[index + 10] = uvs.y1; 132 | 133 | uint32View[index + 11] = argb; 134 | uint32View[index + 12] = darkColor; 135 | uint32View[index + 13] = textureIdAndRound; 136 | 137 | // xy 138 | float32View[index + 14] = (a * w0) + (c * h0) + tx; 139 | float32View[index + 15] = (d * h0) + (b * w0) + ty; 140 | 141 | float32View[index + 16] = uvs.x2; 142 | float32View[index + 17] = uvs.y2; 143 | 144 | uint32View[index + 18] = argb; 145 | uint32View[index + 19] = darkColor; 146 | uint32View[index + 20] = textureIdAndRound; 147 | 148 | // xy 149 | float32View[index + 21] = (a * w1) + (c * h0) + tx; 150 | float32View[index + 22] = (d * h0) + (b * w1) + ty; 151 | 152 | float32View[index + 23] = uvs.x3; 153 | float32View[index + 24] = uvs.y3; 154 | 155 | uint32View[index + 25] = argb; 156 | uint32View[index + 26] = darkColor; 157 | uint32View[index + 27] = textureIdAndRound; 158 | } 159 | } 160 | 161 | extensions.add(DarkTintBatcher); 162 | -------------------------------------------------------------------------------- /src/darktint/DarkTintShader.ts: -------------------------------------------------------------------------------- 1 | import { 2 | colorBit, 3 | colorBitGl, 4 | compileHighShaderGlProgram, 5 | compileHighShaderGpuProgram, 6 | generateTextureBatchBit, 7 | generateTextureBatchBitGl, 8 | getBatchSamplersUniformGroup, 9 | roundPixelsBit, 10 | roundPixelsBitGl, 11 | Shader 12 | } from 'pixi.js'; 13 | import { darkTintBit, darkTintBitGl } from './darkTintBit'; 14 | 15 | export class DarkTintShader extends Shader 16 | { 17 | constructor(maxTextures: number) 18 | { 19 | const glProgram = compileHighShaderGlProgram({ 20 | name: 'dark-tint-batch', 21 | bits: [ 22 | colorBitGl, 23 | darkTintBitGl, 24 | generateTextureBatchBitGl(maxTextures), 25 | roundPixelsBitGl, 26 | ] 27 | }); 28 | 29 | const gpuProgram = compileHighShaderGpuProgram({ 30 | name: 'dark-tint-batch', 31 | bits: [ 32 | colorBit, 33 | darkTintBit, 34 | generateTextureBatchBit(maxTextures), 35 | roundPixelsBit, 36 | ] 37 | }); 38 | 39 | super({ 40 | glProgram, 41 | gpuProgram, 42 | resources: { 43 | batchSamplers: getBatchSamplersUniformGroup(maxTextures), 44 | } 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/darktint/darkTintBit.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | export const darkTintBit = { 3 | name: 'color-bit', 4 | vertex: { 5 | header: /* wgsl */` 6 | @in aDarkColor: vec4; 7 | @out vDarkColor: vec4; 8 | `, 9 | main: /* wgsl */` 10 | vDarkColor = aDarkColor; 11 | ` 12 | }, 13 | fragment: { 14 | header: /* wgsl */` 15 | @in vDarkColor: vec4; 16 | `, 17 | end: /* wgsl */` 18 | 19 | let alpha = outColor.a * vColor.a; 20 | let rgb = ((outColor.a - 1.0) * vDarkColor.a + 1.0 - outColor.rgb) * vDarkColor.rgb + outColor.rgb * vColor.rgb; 21 | 22 | finalColor = vec4(rgb, alpha); 23 | 24 | ` 25 | } 26 | }; 27 | 28 | export const darkTintBitGl = { 29 | name: 'color-bit', 30 | vertex: { 31 | header: /* glsl */` 32 | in vec4 aDarkColor; 33 | out vec4 vDarkColor; 34 | `, 35 | main: /* glsl */` 36 | vDarkColor = aDarkColor; 37 | ` 38 | }, 39 | fragment: { 40 | header: /* glsl */` 41 | in vec4 vDarkColor; 42 | `, 43 | end: /* glsl */` 44 | 45 | finalColor.a = outColor.a * vColor.a; 46 | finalColor.rgb = ((outColor.a - 1.0) * vDarkColor.a + 1.0 - outColor.rgb) * vDarkColor.rgb + outColor.rgb * vColor.rgb; 47 | ` 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** **************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated September 24, 2021. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2021, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software 13 | * or otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY 19 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY 22 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, 24 | * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND 25 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 | * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | import './require-shim.js'; // Side effects add require pixi.js to global scope 31 | import './assets/atlasLoader.js'; // Side effects install the loaders into pixi 32 | import './assets/skeletonLoader.js'; // Side effects install the loaders into pixi 33 | import './darktint/DarkTintBatcher.js'; // Side effects install the batcher into pixi 34 | import './SpinePipe.js'; 35 | 36 | export * from './assets/atlasLoader.js'; 37 | export * from './assets/skeletonLoader.js'; 38 | export * from './require-shim.js'; 39 | export * from './Spine.js'; 40 | export * from './SpineDebugRenderer.js'; 41 | export * from './SpinePipe.js'; 42 | export * from './SpineTexture.js'; 43 | export * from '@esotericsoftware/spine-core'; 44 | -------------------------------------------------------------------------------- /src/require-shim.ts: -------------------------------------------------------------------------------- 1 | /** **************************************************************************** 2 | * Spine Runtimes License Agreement 3 | * Last updated July 28, 2023. Replaces all prior versions. 4 | * 5 | * Copyright (c) 2013-2023, Esoteric Software LLC 6 | * 7 | * Integration of the Spine Runtimes into software or otherwise creating 8 | * derivative works of the Spine Runtimes is permitted under the terms and 9 | * conditions of Section 2 of the Spine Editor License Agreement: 10 | * http://esotericsoftware.com/spine-editor-license 11 | * 12 | * Otherwise, it is permitted to integrate the Spine Runtimes into software or 13 | * otherwise create derivative works of the Spine Runtimes (collectively, 14 | * "Products"), provided that each user of the Products must obtain their own 15 | * Spine Editor license and redistribution of the Products in any form must 16 | * include this license and copyright notice. 17 | * 18 | * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY 19 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY 22 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, 24 | * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND 25 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE 27 | * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | *****************************************************************************/ 29 | 30 | declare global 31 | { 32 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 33 | // @ts-ignore 34 | // eslint-disable-next-line no-var 35 | var require: any; 36 | // eslint-disable-next-line no-var 37 | var PIXI: any; 38 | } 39 | 40 | if (typeof window !== 'undefined' && window.PIXI) 41 | { 42 | const prevRequire = window.require; 43 | 44 | // eslint-disable-next-line consistent-return 45 | (window as any).require = (x: string) => 46 | { 47 | if (prevRequire) return prevRequire(x); 48 | else if (x.startsWith('@pixi/') || x.startsWith('pixi.js')) return window.PIXI; 49 | }; 50 | } 51 | 52 | export { }; 53 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["./src"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "lib": ["ESNext", "DOM", "ScriptHost"], 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "allowJs": true, 9 | "moduleResolution": "node", 10 | "sourceMap": false, 11 | "declaration": true, 12 | "declarationMap": false, 13 | "declarationDir": "./lib", 14 | "outDir": "./lib", 15 | "noImplicitAny": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "noImplicitReturns": true, 19 | "strictNullChecks": false, 20 | "resolveJsonModule": true, 21 | "skipLibCheck": true, 22 | "noImplicitOverride": true 23 | }, 24 | "include": ["./src", "./typings", "./stories", "./test"], 25 | "ts-node": { 26 | "files": true, 27 | "compilerOptions": { 28 | "esModuleInterop": true, 29 | "module": "commonjs" 30 | } 31 | } 32 | } 33 | --------------------------------------------------------------------------------