diff --git a/.github/workflows/spine-godot-v4-all.yml b/.github/workflows/spine-godot-v4-all.yml index 7920d48b2..bb6a41236 100644 --- a/.github/workflows/spine-godot-v4-all.yml +++ b/.github/workflows/spine-godot-v4-all.yml @@ -15,8 +15,8 @@ jobs: version: [ {"tag": "4.0.4-stable", "version": "4.0.4.stable", "mono": false}, - {"tag": "4.1.1-stable", "version": "4.1.1.stable", "mono": false}, - {"tag": "4.1.1-stable", "version": "4.1.1.stable", "mono": true}, + {"tag": "4.1.2-stable", "version": "4.1.2.stable", "mono": false}, + {"tag": "4.1.2-stable", "version": "4.1.2.stable", "mono": true}, ] uses: ./.github/workflows/spine-godot-v4.yml with: diff --git a/.github/workflows/spine-godot.yml b/.github/workflows/spine-godot.yml index fa4cab519..cd3d9af80 100644 --- a/.github/workflows/spine-godot.yml +++ b/.github/workflows/spine-godot.yml @@ -12,8 +12,8 @@ env: AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_EC2_METADATA_DISABLED: true EM_VERSION: 3.1.14 - GODOT_TAG: 3.5.2-stable - GODOT_VERSION: 3.5.2.stable + GODOT_TAG: 3.5.3-stable + GODOT_VERSION: 3.5.3.stable jobs: godot-editor-windows: diff --git a/CHANGELOG.md b/CHANGELOG.md index a316f9333..8212b464d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -113,6 +113,7 @@ If you are using `SkeletonRenderSeparator` and need to enable and disable the `SkeletonRenderSeparator` component at runtime, you can increase the `RenderCombinedMesh` `Reference Renderers` array by one and assign the `SkeletonRenderer` itself at the last entry after the parts renderers. Disabled `MeshRenderer` components will be skipped when combining the final mesh, so the combined mesh is automatically filled from the desired active renderers. * Timeline extension package: Added static `EditorEvent` callback to allow editor scripts to react to animation events outside of play-mode. Register to the events via `Spine.Unity.Playables.SpineAnimationStateMixerBehaviour.EditorEvent += YourCallback;`. * URP Shaders: Added `Depth Write` property to shaders `Universal Render Pipeline/Spine/Skeleton` and `Universal Render Pipeline/Spine/Skeleton Lit`. Defaults to false to maintain existing behaviour. + * Added `Animation Update` mode (called `UpdateTiming` in code) `In Late Update` for `SkeletonAnimation`, `SkeletonMecanim` and `SkeletonGraphic`. This allows you to update the `SkeletonMecanim` skeleton in the same frame that the Mecanim Animator updated its state, which happens between `Update` and `LateUpdate`. * **Breaking changes** * Made `SkeletonGraphic.unscaledTime` parameter protected, use the new property `UnscaledTime` instead. diff --git a/spine-c/spine-c/src/spine/MeshAttachment.c b/spine-c/spine-c/src/spine/MeshAttachment.c index 3657900ad..e62c074c2 100644 --- a/spine-c/spine-c/src/spine/MeshAttachment.c +++ b/spine-c/spine-c/src/spine/MeshAttachment.c @@ -43,7 +43,6 @@ void _spMeshAttachment_dispose(spAttachment *attachment) { FREE(self->edges); } else _spAttachment_deinit(attachment); - if (self->sequence) FREE(self->sequence); FREE(self); } diff --git a/spine-flutter/CHANGELOG.md b/spine-flutter/CHANGELOG.md index 3ead16391..60d9ac18b 100644 --- a/spine-flutter/CHANGELOG.md +++ b/spine-flutter/CHANGELOG.md @@ -1,3 +1,9 @@ +# 4.1.7 +* Fix allocation patter for temporary structs on Windows, which resulted in a hard crash without a stack trace on the native side. + +# 4.1.6 +* Fixed bug in path handling on Windows. + # 4.1.5 * Updated http dependency to 1.1.0 diff --git a/spine-flutter/example/assets/dragon-ess.json b/spine-flutter/example/assets/dragon-ess.json new file mode 100644 index 000000000..274ef6306 --- /dev/null +++ b/spine-flutter/example/assets/dragon-ess.json @@ -0,0 +1,1079 @@ +{ +"skeleton": { + "hash": "aPoA1GjXkVI", + "spine": "4.1.17", + "x": -366.31, + "y": -327.81, + "width": 660.39, + "height": 643, + "images": "./images/", + "audio": "././" +}, +"bones": [ + { "name": "root", "y": -176.12 }, + { "name": "center", "parent": "root", "y": 176.12, "color": "ffe300ff" }, + { + "name": "back", + "parent": "center", + "length": 115.38, + "rotation": 151.83, + "x": 16.04, + "y": 27.94, + "color": "ffe400ff" + }, + { + "name": "chest", + "parent": "center", + "length": 31.24, + "rotation": 161.7, + "x": 52.53, + "y": 15.35, + "color": "ffe400ff" + }, + { + "name": "neck", + "parent": "center", + "length": 41.37, + "rotation": 39.06, + "x": 64.76, + "y": 11.98, + "color": "ffe400ff" + }, + { + "name": "chin", + "parent": "neck", + "length": 153.16, + "rotation": -69.07, + "x": 64.63, + "y": -6.99, + "color": "ffe400ff" + }, + { + "name": "head", + "parent": "neck", + "length": 188.84, + "rotation": 8.07, + "x": 69.96, + "y": 2.5, + "color": "ffe400ff" + }, + { + "name": "left-front-thigh", + "parent": "chest", + "length": 67.42, + "rotation": 138.94, + "x": -45.59, + "y": 7.93, + "color": "ff0000ff" + }, + { + "name": "left-front-leg", + "parent": "left-front-thigh", + "length": 51.58, + "rotation": 43.36, + "x": 67.42, + "y": 0.03, + "color": "ff0000ff" + }, + { + "name": "left-front-toe1", + "parent": "left-front-leg", + "length": 51.45, + "rotation": -98.01, + "x": 45.54, + "y": 2.43, + "color": "ff0000ff" + }, + { + "name": "left-front-toe2", + "parent": "left-front-leg", + "length": 61.98, + "rotation": -55.26, + "x": 51.58, + "y": -0.13, + "color": "ff0000ff" + }, + { + "name": "left-front-toe3", + "parent": "left-front-leg", + "length": 45.65, + "rotation": -11.14, + "x": 54.19, + "y": 0.6, + "scaleX": 1.135, + "color": "ff0000ff" + }, + { + "name": "left-front-toe4", + "parent": "left-front-leg", + "length": 53.47, + "rotation": 19.43, + "x": 50.61, + "y": 7.09, + "scaleX": 1.135, + "color": "ff0000ff" + }, + { + "name": "right-rear-thigh", + "parent": "back", + "length": 123.47, + "rotation": 104.88, + "x": 65.31, + "y": 59.89, + "color": "29ff00ff" + }, + { + "name": "left-rear-thigh", + "parent": "right-rear-thigh", + "length": 88.06, + "rotation": 28.35, + "x": -8.59, + "y": 30.19, + "color": "ff0000ff" + }, + { + "name": "left-rear-leg", + "parent": "left-rear-thigh", + "length": 103.74, + "rotation": -122.41, + "x": 96.04, + "y": -0.97, + "color": "ff0000ff" + }, + { + "name": "left-wing", + "parent": "chest", + "length": 301.12, + "rotation": -75.51, + "x": -7.25, + "y": -24.66, + "color": "ff0000ff" + }, + { + "name": "right-front-thigh", + "parent": "chest", + "length": 81.64, + "rotation": 67.97, + "x": -10.89, + "y": 28.25, + "color": "29ff00ff" + }, + { + "name": "right-front-leg", + "parent": "right-front-thigh", + "length": 66.53, + "rotation": 92.7, + "x": 83.05, + "y": -0.31, + "color": "29ff00ff" + }, + { + "name": "right-front-toe1", + "parent": "right-front-leg", + "length": 46.66, + "rotation": 8.59, + "x": 70.03, + "y": 5.31, + "color": "29ff00ff" + }, + { + "name": "right-front-toe2", + "parent": "right-front-leg", + "length": 53.67, + "rotation": -35.02, + "x": 66.53, + "y": 0.34, + "color": "29ff00ff" + }, + { + "name": "right-front-toe3", + "parent": "right-front-leg", + "length": 58.39, + "rotation": -74.67, + "x": 62.1, + "y": -0.79, + "color": "29ff00ff" + }, + { + "name": "right-rear-leg", + "parent": "right-rear-thigh", + "length": 91.06, + "rotation": -129.04, + "x": 123.47, + "y": -0.27, + "color": "29ff00ff" + }, + { + "name": "right-rear-toe1", + "parent": "right-rear-leg", + "length": 95, + "rotation": 141.98, + "x": 90.07, + "y": 2.12, + "color": "29ff00ff" + }, + { + "name": "right-rear-toe2", + "parent": "right-rear-leg", + "length": 99.29, + "rotation": 125.32, + "x": 89.6, + "y": 1.52, + "color": "29ff00ff" + }, + { + "name": "right-rear-toe3", + "parent": "right-rear-leg", + "length": 103.46, + "rotation": 112.27, + "x": 91.06, + "y": -0.35, + "color": "29ff00ff" + }, + { + "name": "right-wing", + "parent": "head", + "length": 359.5, + "rotation": 83.21, + "x": -74.68, + "y": 20.91, + "color": "29ff00ff" + }, + { + "name": "tail1", + "parent": "back", + "length": 65.65, + "rotation": 44.32, + "x": 115.38, + "y": -0.2, + "color": "ffe400ff" + }, + { + "name": "tail2", + "parent": "tail1", + "length": 54.5, + "rotation": 12, + "x": 65.65, + "y": 0.23, + "color": "ffe400ff" + }, + { + "name": "tail3", + "parent": "tail2", + "length": 41.78, + "rotation": 1.8, + "x": 54.5, + "y": 0.37, + "color": "ffe400ff" + }, + { + "name": "tail4", + "parent": "tail3", + "length": 34.19, + "rotation": -1.8, + "x": 41.78, + "y": 0.16, + "color": "ffe400ff" + }, + { + "name": "tail5", + "parent": "tail4", + "length": 32.33, + "rotation": -3.15, + "x": 34.19, + "y": -0.19, + "color": "ffe400ff" + }, + { + "name": "tail6", + "parent": "tail5", + "length": 80.08, + "rotation": -29.55, + "x": 32.33, + "y": -0.23, + "color": "ffe400ff" + } +], +"slots": [ + { "name": "left-rear-leg", "bone": "left-rear-leg", "attachment": "left-rear-leg" }, + { "name": "left-rear-thigh", "bone": "left-rear-thigh", "attachment": "left-rear-thigh" }, + { "name": "left-wing", "bone": "left-wing", "attachment": "left-wing" }, + { "name": "tail6", "bone": "tail6", "attachment": "tail06" }, + { "name": "tail5", "bone": "tail5", "attachment": "tail05" }, + { "name": "tail4", "bone": "tail4", "attachment": "tail04" }, + { "name": "tail3", "bone": "tail3", "attachment": "tail03" }, + { "name": "tail2", "bone": "tail2", "attachment": "tail02" }, + { "name": "tail1", "bone": "tail1", "attachment": "tail01" }, + { "name": "back", "bone": "back", "attachment": "back" }, + { "name": "left-front-thigh", "bone": "left-front-thigh", "attachment": "left-front-thigh" }, + { "name": "left-front-leg", "bone": "left-front-leg", "attachment": "left-front-leg" }, + { "name": "left-front-toe1", "bone": "left-front-toe1", "attachment": "front-toe-a" }, + { "name": "left-front-toe4", "bone": "left-front-toe4", "attachment": "front-toe-b" }, + { "name": "left-front-toe3", "bone": "left-front-toe3", "attachment": "front-toe-b" }, + { "name": "left-front-toe2", "bone": "left-front-toe2", "attachment": "front-toe-b" }, + { "name": "chest", "bone": "chest", "attachment": "chest" }, + { "name": "right-rear-toe1", "bone": "right-rear-toe1", "attachment": "right-rear-toe" }, + { "name": "right-rear-toe2", "bone": "right-rear-toe2", "attachment": "right-rear-toe" }, + { "name": "right-rear-toe3", "bone": "right-rear-toe3", "attachment": "right-rear-toe" }, + { "name": "right-rear-leg", "bone": "right-rear-leg", "attachment": "right-rear-leg" }, + { "name": "right-rear-thigh", "bone": "right-rear-thigh", "attachment": "right-rear-thigh" }, + { "name": "right-front-toe1", "bone": "right-front-toe1", "attachment": "front-toe-b" }, + { "name": "right-front-thigh", "bone": "right-front-thigh", "attachment": "right-front-thigh" }, + { "name": "right-front-leg", "bone": "right-front-leg", "attachment": "right-front-leg" }, + { "name": "right-front-toe2", "bone": "right-front-toe2", "attachment": "front-toe-b" }, + { "name": "right-front-toe3", "bone": "right-front-toe3", "attachment": "front-toe-b" }, + { "name": "chin", "bone": "chin", "attachment": "chin" }, + { "name": "right-wing", "bone": "right-wing", "attachment": "right-wing" }, + { "name": "head", "bone": "head", "attachment": "head" }, + { "name": "thiagobrayner", "bone": "root", "attachment": "thiagobrayner" } +], +"skins": [ + { + "name": "default", + "attachments": { + "back": { + "back": { "x": 35.85, "y": 19.99, "rotation": -151.83, "width": 190, "height": 185 } + }, + "chest": { + "chest": { "x": -14.6, "y": 24.79, "rotation": -161.7, "width": 136, "height": 122 } + }, + "chin": { + "chin": { "x": 66.55, "y": 7.32, "rotation": 30.01, "width": 214, "height": 146 } + }, + "head": { + "head": { "x": 76.69, "y": 32.21, "rotation": -47.13, "width": 296, "height": 260 } + }, + "left-front-leg": { + "left-front-leg": { "x": 14.69, "y": 0.49, "rotation": 16, "width": 84, "height": 57 } + }, + "left-front-thigh": { + "left-front-thigh": { "x": 27.66, "y": -11.59, "rotation": 58.66, "width": 84, "height": 72 } + }, + "left-front-toe1": { + "front-toe-a": { "x": 31.93, "y": 0.61, "rotation": 109.56, "width": 29, "height": 50 } + }, + "left-front-toe2": { + "front-toe-b": { "x": 26.84, "y": -4.95, "rotation": 109.51, "width": 56, "height": 57 } + }, + "left-front-toe3": { + "front-toe-b": { + "x": 18.22, + "y": -7.22, + "scaleX": 0.8811, + "scaleY": 0.9409, + "rotation": 99.71, + "width": 56, + "height": 57 + } + }, + "left-front-toe4": { + "front-toe-b": { "x": 23.21, "y": -11.69, "scaleX": 0.8811, "rotation": 79.89, "width": 56, "height": 57 } + }, + "left-rear-leg": { + "left-rear-leg": { "x": 67.29, "y": 12.63, "rotation": -162.65, "width": 206, "height": 177 } + }, + "left-rear-thigh": { + "left-rear-thigh": { "x": 56.03, "y": 27.39, "rotation": 74.94, "width": 91, "height": 149 } + }, + "left-wing": { + "left-wing": { + "x": -36.32, + "y": -44.53, + "rotation": -83.7, + "width": 264, + "height": 589, + "sequence": { "count": 9, "digits": 2 } + } + }, + "right-front-leg": { + "right-front-leg": { "x": 17.8, "y": 4.23, "rotation": 37.63, "width": 101, "height": 89 } + }, + "right-front-thigh": { + "right-front-thigh": { "x": 35.29, "y": 2.11, "rotation": 130.33, "width": 108, "height": 108 } + }, + "right-front-toe1": { + "front-toe-b": { "x": 24.5, "y": -2.61, "rotation": 104.18, "width": 56, "height": 57 } + }, + "right-front-toe2": { + "front-toe-b": { "x": 26.39, "y": 1.17, "rotation": 104.58, "width": 56, "height": 57 } + }, + "right-front-toe3": { + "front-toe-b": { "x": 30.67, "y": -0.07, "rotation": 112.3, "width": 56, "height": 57 } + }, + "right-rear-leg": { + "right-rear-leg": { "x": 60.88, "y": -5.73, "rotation": -127.67, "width": 116, "height": 100 } + }, + "right-rear-thigh": { + "right-rear-thigh": { "x": 53.25, "y": 12.58, "rotation": 103.29, "width": 91, "height": 149 } + }, + "right-rear-toe1": { + "right-rear-toe": { "x": 54.76, "y": -5.72, "rotation": 134.79, "width": 109, "height": 77 } + }, + "right-rear-toe2": { + "right-rear-toe": { "x": 57.03, "y": -7.23, "rotation": 134.43, "width": 109, "height": 77 } + }, + "right-rear-toe3": { + "right-rear-toe": { "x": 47.46, "y": -7.64, "rotation": 134.34, "width": 109, "height": 77 } + }, + "right-wing": { + "right-wing": { + "x": 35.09, + "y": 78.11, + "rotation": -130.34, + "width": 365, + "height": 643, + "sequence": { "count": 9, "digits": 2 } + } + }, + "tail1": { + "tail01": { "x": 22.6, "y": -4.5, "rotation": 163.85, "width": 120, "height": 153 } + }, + "tail2": { + "tail02": { "x": 18.12, "y": -1.75, "rotation": 151.85, "width": 95, "height": 120 } + }, + "tail3": { + "tail03": { "x": 16.94, "y": -2.01, "rotation": 150.04, "width": 73, "height": 92 } + }, + "tail4": { + "tail04": { "x": 15.35, "y": -2.18, "rotation": 151.85, "width": 56, "height": 71 } + }, + "tail5": { + "tail05": { "x": 15.06, "y": -3.57, "rotation": 155, "width": 52, "height": 59 } + }, + "tail6": { + "tail06": { "x": 28.02, "y": -16.83, "rotation": -175.45, "width": 95, "height": 68 } + }, + "thiagobrayner": { + "thiagobrayner": { "y": -95, "width": 350, "height": 31 } + } + } + } +], +"animations": { + "flying": { + "bones": { + "back": { + "rotate": [ + { + "curve": [ 0.042, 2.86, 0.143, 10.41 ] + }, + { + "time": 0.1667, + "value": 11.59, + "curve": [ 0.245, 15.58, 0.383, 22.41 ] + }, + { + "time": 0.5, + "value": 22.4, + "curve": [ 0.593, 22.39, 0.75, -3.18 ] + }, + { + "time": 0.8333, + "value": -3.18, + "curve": [ 0.875, -3.18, 0.942, -2.19 ] + }, + { "time": 1 } + ] + }, + "neck": { + "rotate": [ + { + "curve": [ 0.092, -0.1, 0.304, -21.22 ] + }, + { + "time": 0.3333, + "value": -23.16, + "curve": [ 0.386, -26.7, 0.442, -31.89 ] + }, + { + "time": 0.5333, + "value": -31.93, + "curve": [ 0.644, -31.97, 0.888, -0.1 ] + }, + { "time": 1 } + ] + }, + "right-rear-leg": { + "rotate": [ + { + "curve": [ 0.054, -7.16, 0.227, -10.75 ] + }, + { + "time": 0.3333, + "value": -10.75, + "curve": [ 0.417, -10.75, 0.583, 23.32 ] + }, + { + "time": 0.6667, + "value": 23.32, + "curve": [ 0.792, 23.32, 0.881, 13.94 ] + }, + { "time": 1 } + ] + }, + "right-rear-toe3": { + "rotate": [ + { + "value": -7.31, + "curve": [ 0.051, -3.08, 0.097, 0 ] + }, + { + "time": 0.1333, + "curve": [ 0.258, 0, 0.508, -36.06 ] + }, + { + "time": 0.6333, + "value": -36.06, + "curve": [ 0.723, -36.06, 0.875, -17.83 ] + }, + { "time": 1, "value": -7.31 } + ] + }, + "right-rear-toe2": { + "rotate": [ + { + "value": -1.41, + "curve": [ 0.025, -0.6, 0.047, 0 ] + }, + { + "time": 0.0667, + "curve": [ 0.192, 0, 0.442, -20.32 ] + }, + { + "time": 0.5667, + "value": -20.32, + "curve": [ 0.673, -20.32, 0.868, -5.88 ] + }, + { "time": 1, "value": -1.41 } + ] + }, + "right-rear-toe1": { + "rotate": [ + { + "curve": [ 0.125, 0, 0.375, -18.71 ] + }, + { + "time": 0.5, + "value": -18.71, + "curve": [ 0.625, -18.71, 0.875, 0 ] + }, + { "time": 1 } + ] + }, + "head": { + "rotate": [ + { + "curve": [ 0.125, 0, 0.375, 1.04 ] + }, + { + "time": 0.5, + "value": 1.04, + "curve": [ 0.625, 1.04, 0.875, 0 ] + }, + { "time": 1 } + ] + }, + "chin": { + "rotate": [ + { + "curve": [ 0.136, -10.78, 0.217, -12.01 ] + }, + { + "time": 0.3333, + "value": -11.71, + "curve": [ 0.443, -11.42, 0.508, 21.91 ] + }, + { + "time": 0.6667, + "value": 22.01, + "curve": [ 0.847, 22.13, 0.95, 5.91 ] + }, + { "time": 1 } + ] + }, + "left-front-thigh": { + "rotate": [ + { + "value": -0.02, + "curve": [ 0.068, -0.02, 0.348, -32.09 ] + }, + { + "time": 0.5, + "value": -32.02, + "curve": [ 0.653, -31.95, 0.906, 0 ] + }, + { "time": 1 } + ] + }, + "right-front-thigh": { + "rotate": [ + { + "curve": [ 0.053, -5.52, 0.125, -12.96 ] + }, + { + "time": 0.1667, + "value": -12.96, + "curve": [ 0.25, -12.96, 0.417, 16.2 ] + }, + { + "time": 0.5, + "value": 16.2, + "curve": [ 0.625, 16.2, 0.866, 9.48 ] + }, + { "time": 1 } + ] + }, + "left-front-leg": { + "rotate": [ + { + "curve": [ 0.09, -12.24, 0.131, -21.37 ] + }, + { + "time": 0.2, + "value": -21.49, + "curve": [ 0.319, -21.94, 0.5, 77.25 ] + }, + { + "time": 0.6, + "value": 77.25, + "curve": [ 0.7, 77.25, 0.908, 12.37 ] + }, + { "time": 1 } + ] + }, + "left-front-toe1": { + "rotate": [ + { + "curve": [ 0.071, -4.48, 0.2, -12.68 ] + }, + { + "time": 0.2667, + "value": -12.68, + "curve": [ 0.375, -12.68, 0.592, 6.99 ] + }, + { + "time": 0.7, + "value": 6.99, + "curve": [ 0.775, 6.99, 0.904, 3.55 ] + }, + { "time": 1 } + ] + }, + "left-front-toe2": { + "scale": [ + { + "curve": [ 0.125, 1, 0.375, 1.331, 0.125, 1, 0.375, 1.029 ] + }, + { + "time": 0.5, + "x": 1.331, + "y": 1.029, + "curve": [ 0.625, 1.331, 0.875, 1, 0.625, 1.029, 0.875, 1 ] + }, + { "time": 1 } + ] + }, + "left-front-toe4": { + "rotate": [ + { + "curve": [ 0.125, 0, 0.375, 26.52 ] + }, + { + "time": 0.5, + "value": 26.52, + "curve": [ 0.625, 26.52, 0.875, 0 ] + }, + { "time": 1 } + ], + "scale": [ + { + "curve": [ 0.125, 1, 0.375, 1.211, 0.125, 1, 0.375, 0.993 ] + }, + { + "time": 0.5, + "x": 1.211, + "y": 0.993, + "curve": [ 0.625, 1.211, 0.875, 1, 0.625, 0.993, 0.875, 1 ] + }, + { "time": 1 } + ] + }, + "left-front-toe3": { + "rotate": [ + { + "curve": [ 0.125, 0, 0.375, 16.99 ] + }, + { + "time": 0.5, + "value": 16.99, + "curve": [ 0.625, 16.99, 0.875, 0 ] + }, + { "time": 1 } + ], + "scale": [ + { + "curve": [ 0.125, 1, 0.375, 1.355, 0.125, 1, 0.375, 1.008 ] + }, + { + "time": 0.5, + "x": 1.355, + "y": 1.008, + "curve": [ 0.625, 1.355, 0.875, 1, 0.625, 1.008, 0.875, 1 ] + }, + { "time": 1 } + ] + }, + "right-front-leg": { + "rotate": [ + { + "curve": [ 0.11, -8.79, 0.179, -18.3 ] + }, + { + "time": 0.2667, + "value": -18.31, + "curve": [ 0.352, -18.33, 0.55, 15.48 ] + }, + { + "time": 0.6333, + "value": 15.48, + "curve": [ 0.758, 15.48, 0.875, 7.9 ] + }, + { "time": 1 } + ] + }, + "right-front-toe1": { + "rotate": [ + { + "curve": [ 0.042, 0, 0.147, 8.28 ] + }, + { + "time": 0.1667, + "value": 10.52, + "curve": [ 0.238, 18.41, 0.406, 35.06 ] + }, + { + "time": 0.5, + "value": 34.84, + "curve": [ 0.577, 34.84, 0.669, 29.65 ] + }, + { + "time": 0.7667, + "value": 20.24, + "curve": [ 0.852, 11.97, 0.941, 0 ] + }, + { "time": 1 } + ], + "scale": [ + { + "curve": [ 0.125, 1, 0.375, 1.412, 0.125, 1, 0.375, 1 ] + }, + { + "time": 0.5, + "x": 1.412, + "curve": [ 0.625, 1.412, 0.875, 1, 0.625, 1, 0.875, 1 ] + }, + { "time": 1 } + ] + }, + "right-front-toe2": { + "rotate": [ + { + "curve": [ 0.042, 0, 0.125, 24.7 ] + }, + { + "time": 0.1667, + "value": 24.7, + "curve": [ 0.25, 24.7, 0.417, 7.35 ] + }, + { + "time": 0.5, + "value": 7.35, + "curve": [ 0.544, 7.35, 0.671, 33.55 ] + }, + { + "time": 0.7333, + "value": 32.36, + "curve": [ 0.853, 30.19, 0.919, 0 ] + }, + { "time": 1 } + ], + "scale": [ + { + "curve": [ 0.125, 1, 0.375, 1.407, 0.125, 1, 0.375, 1.058 ] + }, + { + "time": 0.5, + "x": 1.407, + "y": 1.058, + "curve": [ 0.625, 1.407, 0.875, 1, 0.625, 1.058, 0.875, 1 ] + }, + { "time": 1 } + ] + }, + "right-front-toe3": { + "rotate": [ + { + "curve": [ 0.033, 0, 0.1, 11.01 ] + }, + { + "time": 0.1333, + "value": 11.01, + "curve": [ 0.208, 11.01, 0.358, -5.41 ] + }, + { + "time": 0.4333, + "value": -5.41, + "curve": [ 0.508, -5.41, 0.658, 20.3 ] + }, + { + "time": 0.7333, + "value": 20.3, + "curve": [ 0.8, 20.3, 0.933, 0 ] + }, + { "time": 1 } + ], + "scale": [ + { + "curve": [ 0.125, 1, 0.375, 1.33, 0.125, 1, 0.375, 1.182 ] + }, + { + "time": 0.5, + "x": 1.33, + "y": 1.182, + "curve": [ 0.625, 1.33, 0.875, 1, 0.625, 1.182, 0.875, 1 ] + }, + { "time": 1 } + ] + }, + "right-wing": { + "rotate": [ + { + "value": -9.59, + "curve": [ 0.131, -9.59, 0.191, 22.41 ] + }, + { + "time": 0.3333, + "value": 22.81, + "curve": [ 0.417, 23.05, 0.533, 7.5 ] + }, + { + "time": 0.5667, + "value": 2.07, + "curve": [ 0.588, -1.48, 0.639, -8.35 ] + }, + { + "time": 0.7, + "value": -8.29, + "curve": [ 0.743, -8.24, 0.792, 5.28 ] + }, + { + "time": 0.8333, + "value": 5.02, + "curve": [ 0.904, 4.59, 0.957, -9.72 ] + }, + { "time": 1, "value": -9.59 } + ] + }, + "left-wing": { + "rotate": [ + { + "curve": [ 0.039, 0, 0.084, -21.1 ] + }, + { + "time": 0.1333, + "value": -21.1, + "curve": [ 0.292, -21.1, 0.505, 4.34 ] + }, + { + "time": 0.6333, + "value": 4.48, + "curve": [ 0.679, 4.48, 0.699, -6.03 ] + }, + { + "time": 0.7667, + "value": -5.95, + "curve": [ 0.857, -5.84, 0.936, 0 ] + }, + { "time": 1 } + ] + }, + "left-rear-leg": { + "rotate": [ + { + "curve": [ 0.08, 5.97, 0.25, 15.82 ] + }, + { + "time": 0.3333, + "value": 15.82, + "curve": [ 0.425, 15.82, 0.608, -17.33 ] + }, + { + "time": 0.7, + "value": -17.33, + "curve": [ 0.775, -17.33, 0.871, -8.98 ] + }, + { "time": 1 } + ] + }, + "center": { + "rotate": [ + { + "curve": [ 0.072, 6.07, 0.213, 17.69 ] + }, + { + "time": 0.3333, + "value": 17.69, + "curve": [ 0.454, 17.68, 0.583, -15 ] + }, + { + "time": 0.6667, + "value": -15, + "curve": [ 0.804, -15, 0.966, -3.62 ] + }, + { "time": 1 } + ], + "translate": [ + { + "curve": [ 0.125, 0, 0.375, -0.01, 0.162, 0, 0.288, 144.81 ] + }, + { + "time": 0.5, + "x": -0.01, + "y": 144.61, + "curve": [ 0.625, -0.01, 0.875, 0, 0.718, 145.5, 0.832, 0 ] + }, + { "time": 1 } + ] + }, + "tail1": { + "rotate": [ + { + "value": -1.88, + "curve": [ 0.125, -1.88, 0.375, -67.4 ] + }, + { + "time": 0.5, + "value": -67.4, + "curve": [ 0.625, -67.4, 0.875, -1.88 ] + }, + { "time": 1, "value": -1.88 } + ] + }, + "tail2": { + "rotate": [ + { + "value": -27.45, + "curve": [ 0.125, -27.45, 0.375, 2.68 ] + }, + { + "time": 0.5, + "value": 2.68, + "curve": [ 0.625, 2.68, 0.875, -27.45 ] + }, + { "time": 1, "value": -27.45 } + ] + }, + "tail3": { + "rotate": [ + { + "value": -7.63, + "curve": [ 0.125, -7.63, 0.375, 24.54 ] + }, + { + "time": 0.5, + "value": 24.54, + "curve": [ 0.625, 24.54, 0.875, -7.63 ] + }, + { "time": 1, "value": -7.63 } + ] + }, + "tail4": { + "rotate": [ + { + "value": -10.04, + "curve": [ 0.125, -10.04, 0.375, 1.03 ] + }, + { + "time": 0.5, + "value": 1.03, + "curve": [ 0.625, 1.03, 0.875, -10.04 ] + }, + { "time": 1, "value": -10.04 } + ] + }, + "tail5": { + "rotate": [ + { + "value": -11.26, + "curve": [ 0.152, -11.21, 0.334, 9.91 ] + }, + { + "time": 0.5, + "value": 9.93, + "curve": [ 0.662, 9.95, 0.844, -11.17 ] + }, + { "time": 1, "value": -11.26 } + ] + }, + "tail6": { + "rotate": [ + { + "value": 26.68, + "curve": [ 0.194, 26.72, 0.328, 35.84 ] + }, + { + "time": 0.5, + "value": 35.84, + "curve": [ 0.678, 35.84, 0.818, 26.62 ] + }, + { "time": 1, "value": 26.68 } + ] + }, + "right-rear-thigh": { + "rotate": [ + { + "curve": [ 0.055, -6.4, 0.166, -22.66 ] + }, + { + "time": 0.2667, + "value": -22.66, + "curve": [ 0.384, -22.66, 0.517, 19.08 ] + }, + { + "time": 0.6333, + "value": 19.32, + "curve": [ 0.746, 19.56, 0.91, 9.17 ] + }, + { "time": 1 } + ] + } + }, + "attachments": { + "default": { + "left-wing": { + "left-wing": { + "sequence": [ + { "mode": "loop", "delay": 0.0667 }, + { "time": 0.6 }, + { "time": 0.7333, "mode": "loop", "index": 1 }, + { "time": 0.8, "mode": "loop", "index": 2, "delay": 0.0333 }, + { "time": 0.9667, "index": 7 } + ] + } + }, + "right-wing": { + "right-wing": { + "sequence": [ + { "mode": "loop", "delay": 0.0667 }, + { "time": 0.6 }, + { "time": 0.7333, "mode": "loop", "index": 1 }, + { "time": 0.8, "mode": "loop", "index": 2, "delay": 0.0333 }, + { "time": 0.9667, "index": 7 } + ] + } + } + } + } + } +} +} \ No newline at end of file diff --git a/spine-flutter/example/ios/Runner.xcodeproj/project.pbxproj b/spine-flutter/example/ios/Runner.xcodeproj/project.pbxproj index e5637925c..664dc88cf 100644 --- a/spine-flutter/example/ios/Runner.xcodeproj/project.pbxproj +++ b/spine-flutter/example/ios/Runner.xcodeproj/project.pbxproj @@ -156,7 +156,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { diff --git a/spine-flutter/example/lib/flame_example.dart b/spine-flutter/example/lib/flame_example.dart index fef9edc0d..53e5df667 100644 --- a/spine-flutter/example/lib/flame_example.dart +++ b/spine-flutter/example/lib/flame_example.dart @@ -114,7 +114,7 @@ class SimpleFlameExample extends FlameGame { // Load the Spineboy atlas and skeleton data from asset files // and create a SpineComponent from them, scaled down and // centered on the screen - spineboy = await SpineComponent.fromAssets("assets/spineboy.atlas", "assets/spineboy-pro.skel", + spineboy = await SpineComponent.fromAssets("assets/spineboy.atlas", "assets/spineboy-pro.json", scale: Vector2(0.4, 0.4), anchor: Anchor.center, position: Vector2(size.x / 2, size.y / 2)); // Set the "walk" animation on track 0 in looping mode @@ -129,6 +129,36 @@ class SimpleFlameExample extends FlameGame { } } +class DragonExample extends FlameGame { + late final Atlas cachedAtlas; + late final SkeletonData cachedSkeletonData; + late final SpineComponent dragon; + + @override + Future onLoad() async { + cachedAtlas = await Atlas.fromAsset("assets/dragon.atlas"); + cachedSkeletonData = await SkeletonData.fromAsset(cachedAtlas, "assets/dragon-ess.json"); + final drawable = SkeletonDrawable(cachedAtlas, cachedSkeletonData, false); + dragon = SpineComponent( + drawable, + scale: Vector2(0.4, 0.4), + anchor: Anchor.center, + position: Vector2(size.x / 2, size.y / 2 - 150), + ); + // Set the "walk" animation on track 0 in looping mode + dragon.animationState.setAnimationByName(0, "flying", true); + await add(dragon); + } + + @override + void onDetach() { + // Dispose the native resources that have been loaded for spineboy. + dragon.dispose(); + cachedSkeletonData.dispose(); + cachedAtlas.dispose(); + } +} + class PreloadAndShareSpineDataExample extends FlameGame { late final SkeletonData cachedSkeletonData; late final Atlas cachedAtlas; diff --git a/spine-flutter/example/lib/main.dart b/spine-flutter/example/lib/main.dart index e0ec04ee0..0c5b2bc2c 100644 --- a/spine-flutter/example/lib/main.dart +++ b/spine-flutter/example/lib/main.dart @@ -143,7 +143,19 @@ class ExampleSelector extends StatelessWidget { ); }, ), - spacer + spacer, + ElevatedButton( + child: const Text('Flame: Dragon Example'), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SpineFlameGameWidget(DragonExample()), + ), + ); + }, + ), + spacer, ]))); } } diff --git a/spine-flutter/example/lib/pause_play_animation.dart b/spine-flutter/example/lib/pause_play_animation.dart index 96b55375f..912394def 100644 --- a/spine-flutter/example/lib/pause_play_animation.dart +++ b/spine-flutter/example/lib/pause_play_animation.dart @@ -65,7 +65,7 @@ class PlayPauseAnimationState extends State { appBar: AppBar(title: const Text('Play/Pause')), body: SpineWidget.fromAsset( "assets/dragon.atlas", - "assets/dragon-ess.skel", + "assets/dragon-ess.json", controller, boundsProvider: SkinAndAnimationBounds(animation: "flying"), ), diff --git a/spine-flutter/example/macos/Runner.xcodeproj/project.pbxproj b/spine-flutter/example/macos/Runner.xcodeproj/project.pbxproj index 06f931d0a..d04bbf466 100644 --- a/spine-flutter/example/macos/Runner.xcodeproj/project.pbxproj +++ b/spine-flutter/example/macos/Runner.xcodeproj/project.pbxproj @@ -203,7 +203,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { diff --git a/spine-flutter/example/pubspec.lock b/spine-flutter/example/pubspec.lock index 458916f82..d1522f504 100644 --- a/spine-flutter/example/pubspec.lock +++ b/spine-flutter/example/pubspec.lock @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" crypto: dependency: transitive description: @@ -114,10 +114,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -202,6 +202,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" web_ffi_fork: dependency: transitive description: @@ -211,5 +219,5 @@ packages: source: hosted version: "0.7.4" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.10.5" diff --git a/spine-flutter/lib/assets/libspine_flutter.wasm b/spine-flutter/lib/assets/libspine_flutter.wasm index ccef18809..3a5a2fc77 100755 Binary files a/spine-flutter/lib/assets/libspine_flutter.wasm and b/spine-flutter/lib/assets/libspine_flutter.wasm differ diff --git a/spine-flutter/lib/spine_flutter.dart b/spine-flutter/lib/spine_flutter.dart index 0da5432d2..8fdc66c3f 100644 --- a/spine-flutter/lib/spine_flutter.dart +++ b/spine-flutter/lib/spine_flutter.dart @@ -126,7 +126,7 @@ class Atlas { final numImagePaths = _bindings.spine_atlas_get_num_image_paths(atlas); for (int i = 0; i < numImagePaths; i++) { final Pointer atlasPageFile = _bindings.spine_atlas_get_image_path(atlas, i).cast(); - final imagePath = path.join(atlasDir, atlasPageFile.toDartString()); + final imagePath = atlasDir + "/" + atlasPageFile.toDartString(); var imageData = await loadFile(imagePath); final Codec codec = await instantiateImageCodec(imageData); final FrameInfo frameInfo = await codec.getNextFrame(); @@ -807,7 +807,6 @@ class Bone { Vec2 worldToLocal(double worldX, double worldY) { final local = _bindings.spine_bone_world_to_local(_bone, worldX, worldY); final result = Vec2(_bindings.spine_vector_get_x(local), _bindings.spine_vector_get_y(local)); - _allocator.free(local); return result; } @@ -815,7 +814,6 @@ class Bone { Vec2 localToWorld(double localX, double localY) { final world = _bindings.spine_bone_local_to_world(_bone, localX, localY); final result = Vec2(_bindings.spine_vector_get_x(world), _bindings.spine_vector_get_y(world)); - _allocator.free(world); return result; } @@ -1871,7 +1869,6 @@ class PointAttachment extends Attachment { Vec2 computeWorldPosition(Bone bone) { final position = _bindings.spine_point_attachment_compute_world_position(_attachment, bone._bone); final result = Vec2(_bindings.spine_vector_get_x(position), _bindings.spine_vector_get_y(position)); - _allocator.free(position); return result; } @@ -2888,7 +2885,6 @@ class Skeleton { final nativeBounds = _bindings.spine_skeleton_get_bounds(_skeleton); final bounds = Bounds(_bindings.spine_bounds_get_x(nativeBounds), _bindings.spine_bounds_get_y(nativeBounds), _bindings.spine_bounds_get_width(nativeBounds), _bindings.spine_bounds_get_height(nativeBounds)); - _allocator.free(nativeBounds); return bounds; } diff --git a/spine-flutter/pubspec.yaml b/spine-flutter/pubspec.yaml index c83a75d50..6b5d7b659 100644 --- a/spine-flutter/pubspec.yaml +++ b/spine-flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: spine_flutter description: The official Spine Flutter Runtime to load, display and interact with Spine animations. -version: 4.1.5 +version: 4.1.7 homepage: https://esotericsoftware.com repository: https://github.com/esotericsoftware/spine-runtimes issue_tracker: https://github.com/esotericsoftware/spine-runtimes/issues diff --git a/spine-flutter/src/spine_flutter.cpp b/spine-flutter/src/spine_flutter.cpp index 5265f503f..2a3909440 100644 --- a/spine-flutter/src/spine_flutter.cpp +++ b/spine-flutter/src/spine_flutter.cpp @@ -1473,8 +1473,9 @@ spine_path_constraint spine_skeleton_find_path_constraint(spine_skeleton skeleto return (spine_path_constraint) _skeleton->findPathConstraint(constraintName); } +_spine_bounds tmp_bounds; spine_bounds spine_skeleton_get_bounds(spine_skeleton skeleton) { - _spine_bounds *bounds = (_spine_bounds *) malloc(sizeof(_spine_bounds)); + _spine_bounds *bounds = &tmp_bounds; if (skeleton == nullptr) return (spine_bounds) bounds; Skeleton *_skeleton = (Skeleton *) skeleton; Vector vertices; @@ -2128,8 +2129,9 @@ void spine_bone_set_to_setup_pose(spine_bone bone) { _bone->setToSetupPose(); } +_spine_vector tmp_vector; spine_vector spine_bone_world_to_local(spine_bone bone, float worldX, float worldY) { - _spine_vector *coords = SpineExtension::calloc<_spine_vector>(1, __FILE__, __LINE__); + _spine_vector *coords = &tmp_vector; if (bone == nullptr) return (spine_vector) coords; Bone *_bone = (Bone *) bone; _bone->worldToLocal(worldX, worldY, coords->x, coords->y); @@ -2137,7 +2139,7 @@ spine_vector spine_bone_world_to_local(spine_bone bone, float worldX, float worl } spine_vector spine_bone_local_to_world(spine_bone bone, float localX, float localY) { - _spine_vector *coords = SpineExtension::calloc<_spine_vector>(1, __FILE__, __LINE__); + _spine_vector *coords = &tmp_vector; if (bone == nullptr) return (spine_vector) coords; Bone *_bone = (Bone *) bone; _bone->localToWorld(localX, localY, coords->x, coords->y); @@ -2521,7 +2523,7 @@ void spine_attachment_dispose(spine_attachment attachment) { // PointAttachment spine_vector spine_point_attachment_compute_world_position(spine_point_attachment attachment, spine_bone bone) { - _spine_vector *result = SpineExtension::calloc<_spine_vector>(1, __FILE__, __LINE__); + _spine_vector *result = &tmp_vector; if (attachment == nullptr) return (spine_vector) result; PointAttachment *_attachment = (PointAttachment *) attachment; _attachment->computeWorldPosition(*(Bone *) bone, result->x, result->y); diff --git a/spine-godot/example-v4-csharp/examples/02-animation-state-listeners/AnimationStateListener.cs b/spine-godot/example-v4-csharp/examples/02-animation-state-listeners/AnimationStateListener.cs index 6a01413e8..33c68f184 100644 --- a/spine-godot/example-v4-csharp/examples/02-animation-state-listeners/AnimationStateListener.cs +++ b/spine-godot/example-v4-csharp/examples/02-animation-state-listeners/AnimationStateListener.cs @@ -10,28 +10,28 @@ public partial class AnimationStateListener : Node2D spineboy.AnimationStarted += (sprite, animationState, trackEntry) => { var spineTrackEntry = trackEntry as SpineTrackEntry; - Console.WriteLine("Animation started: " + spineTrackEntry.GetAnimation().GetName()); + GD.Print("Animation started: " + spineTrackEntry.GetAnimation().GetName()); }; spineboy.AnimationInterrupted += (sprite, animationState, trackEntry) => { var spineTrackEntry = trackEntry as SpineTrackEntry; - Console.WriteLine("Animation interrupted: " + spineTrackEntry.GetAnimation().GetName()); + GD.Print("Animation interrupted: " + spineTrackEntry.GetAnimation().GetName()); }; spineboy.AnimationCompleted += (sprite, animationState, trackEntry) => { var spineTrackEntry = trackEntry as SpineTrackEntry; - Console.WriteLine("Animation completed: " + spineTrackEntry.GetAnimation().GetName()); + GD.Print("Animation completed: " + spineTrackEntry.GetAnimation().GetName()); }; spineboy.AnimationDisposed += (sprite, animationState, trackEntry) => { var spineTrackEntry = trackEntry as SpineTrackEntry; - Console.WriteLine("Animation disposed: " + spineTrackEntry.GetAnimation().GetName()); + GD.Print("Animation disposed: " + spineTrackEntry.GetAnimation().GetName()); }; spineboy.AnimationEvent += (sprite, animationState, trackEntry, eventObject) => { var spineTrackEntry = trackEntry as SpineTrackEntry; var spineEvent = eventObject as SpineEvent; - Console.WriteLine("Animation event: " + spineTrackEntry.GetAnimation().GetName() + ", " + spineEvent.GetData().GetEventName()); + GD.Print("Animation event: " + spineTrackEntry.GetAnimation().GetName() + ", " + spineEvent.GetData().GetEventName()); if (spineEvent.GetData().GetEventName() == "footstep") footStepAudio.Play(); }; diff --git a/spine-godot/example-v4-csharp/examples/03-mix-and-match/MixAndMatch.cs b/spine-godot/example-v4-csharp/examples/03-mix-and-match/MixAndMatch.cs index 6420080f4..aa17392b0 100644 --- a/spine-godot/example-v4-csharp/examples/03-mix-and-match/MixAndMatch.cs +++ b/spine-godot/example-v4-csharp/examples/03-mix-and-match/MixAndMatch.cs @@ -21,7 +21,7 @@ public partial class MixAndMatch : SpineSprite foreach (SpineSkinEntry entry in custom_skin.GetAttachments()) { - Console.WriteLine(entry.GetSlotIndex() + " " + entry.GetName()); + GD.Print(entry.GetSlotIndex() + " " + entry.GetName()); } GetAnimationState().SetAnimation("dance", true, 0); diff --git a/spine-godot/spine_godot/SpineAtlasResource.cpp b/spine-godot/spine_godot/SpineAtlasResource.cpp index c56ee34e1..dbe3b747a 100644 --- a/spine-godot/spine_godot/SpineAtlasResource.cpp +++ b/spine-godot/spine_godot/SpineAtlasResource.cpp @@ -122,6 +122,8 @@ void SpineAtlasResource::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "source_path"), "", "get_source_path"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "textures"), "", "get_textures"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "normal_maps"), "", "get_normal_maps"); + + ADD_SIGNAL(MethodInfo("skeleton_atlas_changed")); } SpineAtlasResource::SpineAtlasResource() : atlas(nullptr), texture_loader(nullptr), normal_map_prefix("n") { @@ -230,6 +232,28 @@ Error SpineAtlasResource::save_to_file(const String &path) { return OK; } +#if VERSION_MAJOR > 3 +Error SpineAtlasResource::copy_from(const Ref &p_resource) { + auto error = Resource::copy_from(p_resource); + if (error != OK) return error; + + const Ref &spineAtlas = static_cast &>(p_resource); + this->clear(); + this->atlas = spineAtlas->atlas; + this->texture_loader = spineAtlas->texture_loader; + spineAtlas->clear_native_data(); + + this->source_path = spineAtlas->source_path; + this->atlas_data = spineAtlas->atlas_data; + this->normal_map_prefix = spineAtlas->normal_map_prefix; + this->textures = spineAtlas->textures; + this->normal_maps = spineAtlas->normal_maps; + emit_signal(SNAME("skeleton_file_changed")); + + return OK; +} +#endif + #if VERSION_MAJOR > 3 RES SpineAtlasResourceFormatLoader::load(const String &path, const String &original_path, Error *error, bool use_sub_threads, float *progress, CacheMode cache_mode) { #else diff --git a/spine-godot/spine_godot/SpineAtlasResource.h b/spine-godot/spine_godot/SpineAtlasResource.h index 6ae492990..55807a58e 100644 --- a/spine-godot/spine_godot/SpineAtlasResource.h +++ b/spine-godot/spine_godot/SpineAtlasResource.h @@ -45,8 +45,8 @@ class SpineAtlasResource : public Resource { protected: static void _bind_methods(); - spine::Atlas *atlas; - GodotSpineTextureLoader *texture_loader; + mutable spine::Atlas *atlas; + mutable GodotSpineTextureLoader *texture_loader; String source_path; String atlas_data; @@ -69,11 +69,20 @@ public: Error save_to_file(const String &path);// .spatlas +#if VERSION_MAJOR > 3 + virtual Error copy_from(const Ref &p_resource); +#endif + String get_source_path(); Array get_textures(); Array get_normal_maps(); + + void clear_native_data() const { + this->atlas = nullptr; + this->texture_loader = nullptr; + } }; class SpineAtlasResourceFormatLoader : public ResourceFormatLoader { diff --git a/spine-godot/spine_godot/SpineSkeletonDataResource.cpp b/spine-godot/spine_godot/SpineSkeletonDataResource.cpp index 36a0e636e..b8bfdb21e 100644 --- a/spine-godot/spine_godot/SpineSkeletonDataResource.cpp +++ b/spine-godot/spine_godot/SpineSkeletonDataResource.cpp @@ -115,6 +115,7 @@ void SpineSkeletonDataResource::_bind_methods() { ClassDB::bind_method(D_METHOD("get_images_path"), &SpineSkeletonDataResource::get_images_path); ClassDB::bind_method(D_METHOD("get_audio_path"), &SpineSkeletonDataResource::get_audio_path); ClassDB::bind_method(D_METHOD("get_fps"), &SpineSkeletonDataResource::get_fps); + ClassDB::bind_method(D_METHOD("update_skeleton_data"), &SpineSkeletonDataResource::update_skeleton_data); ADD_SIGNAL(MethodInfo("skeleton_data_changed")); ADD_SIGNAL(MethodInfo("_internal_spine_objects_invalidated")); @@ -190,6 +191,15 @@ bool SpineSkeletonDataResource::is_skeleton_data_loaded() const { void SpineSkeletonDataResource::set_atlas_res(const Ref &atlas) { atlas_res = atlas; + if (atlas_res.is_valid()) { +#if VERSION_MAJOR > 3 + if (!atlas_res->is_connected(SNAME("skeleton_atlas_changed"), callable_mp(this, &SpineSkeletonDataResource::update_skeleton_data))) + atlas_res->connect(SNAME("skeleton_atlas_changed"), callable_mp(this, &SpineSkeletonDataResource::update_skeleton_data)); +#else + if (!atlas_res->is_connected(SNAME("skeleton_atlas_changed"), this, SNAME("update_skeleton_data"))) + atlas_res->connect(SNAME("skeleton_atlas_changed"), this, SNAME("update_skeleton_data")); +#endif + } update_skeleton_data(); } @@ -199,6 +209,15 @@ Ref SpineSkeletonDataResource::get_atlas_res() { void SpineSkeletonDataResource::set_skeleton_file_res(const Ref &skeleton_file) { skeleton_file_res = skeleton_file; + if (skeleton_file_res.is_valid()) { +#if VERSION_MAJOR > 3 + if (!skeleton_file_res->is_connected(SNAME("skeleton_file_changed"), callable_mp(this, &SpineSkeletonDataResource::update_skeleton_data))) + skeleton_file_res->connect(SNAME("skeleton_file_changed"), callable_mp(this, &SpineSkeletonDataResource::update_skeleton_data)); +#else + if (!skeleton_file_res->is_connected(SNAME("skeleton_file_changed"), this, SNAME("update_skeleton_data"))) + skeleton_file_res->connect(SNAME("skeleton_file_changed"), this, SNAME("update_skeleton_data")); +#endif + } update_skeleton_data(); } diff --git a/spine-godot/spine_godot/SpineSkeletonFileResource.cpp b/spine-godot/spine_godot/SpineSkeletonFileResource.cpp index 53462c29e..7880d2a25 100644 --- a/spine-godot/spine_godot/SpineSkeletonFileResource.cpp +++ b/spine-godot/spine_godot/SpineSkeletonFileResource.cpp @@ -29,8 +29,12 @@ #include "SpineSkeletonFileResource.h" #if VERSION_MAJOR > 3 +#include "core/error/error_list.h" +#include "core/error/error_macros.h" #include "core/io/file_access.h" #else +#include "core/error_list.h" +#include "core/error_macros.h" #include "core/os/file_access.h" #endif #include @@ -85,6 +89,7 @@ static char *readString(BinaryInput *input) { } void SpineSkeletonFileResource::_bind_methods() { + ADD_SIGNAL(MethodInfo("skeleton_file_changed")); } static bool checkVersion(const char *version) { @@ -157,6 +162,18 @@ Error SpineSkeletonFileResource::save_to_file(const String &path) { return OK; } +#if VERSION_MAJOR > 3 +Error SpineSkeletonFileResource::copy_from(const Ref &p_resource) { + auto error = Resource::copy_from(p_resource); + if (error != OK) return error; + const Ref &spineFile = static_cast &>(p_resource); + this->json = spineFile->json; + this->binary = spineFile->binary; + emit_signal(SNAME("skeleton_file_changed")); + return OK; +} +#endif + #if VERSION_MAJOR > 3 RES SpineSkeletonFileResourceFormatLoader::load(const String &path, const String &original_path, Error *error, bool use_sub_threads, float *progress, CacheMode cache_mode) { #else diff --git a/spine-godot/spine_godot/SpineSkeletonFileResource.h b/spine-godot/spine_godot/SpineSkeletonFileResource.h index 4fedd592e..94924d3f5 100644 --- a/spine-godot/spine_godot/SpineSkeletonFileResource.h +++ b/spine-godot/spine_godot/SpineSkeletonFileResource.h @@ -52,6 +52,10 @@ public: Error load_from_file(const String &path); Error save_to_file(const String &path); + +#if VERSION_MAJOR > 3 + virtual Error copy_from(const Ref &p_resource); +#endif }; class SpineSkeletonFileResourceFormatLoader : public ResourceFormatLoader { diff --git a/spine-ts/spine-webgl/src/SpineCanvas.ts b/spine-ts/spine-webgl/src/SpineCanvas.ts index ec8dc6cfa..b200ef893 100644 --- a/spine-ts/spine-webgl/src/SpineCanvas.ts +++ b/spine-ts/spine-webgl/src/SpineCanvas.ts @@ -87,7 +87,7 @@ export class SpineCanvas { update: () => { }, render: () => { }, error: () => { }, - dispose: () => { }, + dispose: () => { }, } if (config.webglConfig) config.webglConfig = { alpha: true }; @@ -131,7 +131,7 @@ export class SpineCanvas { } /** Disposes the app, so the update() and render() functions are no longer called. Calls the dispose() callback.*/ - dispose() { + dispose () { if (this.config.app.dispose) this.config.app.dispose(this); this.disposed = true; } diff --git a/spine-ue4/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineAtlasImportFactory.cpp b/spine-ue4/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineAtlasImportFactory.cpp index df4c681bc..b9391e101 100644 --- a/spine-ue4/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineAtlasImportFactory.cpp +++ b/spine-ue4/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineAtlasImportFactory.cpp @@ -28,11 +28,9 @@ *****************************************************************************/ #include "SpineAtlasImportFactory.h" -#include "AssetRegistryModule.h" #include "AssetToolsModule.h" -#include "Developer/AssetTools/Public/IAssetTools.h" -#include "PackageTools.h" #include "SpineAtlasAsset.h" +#include "Editor.h" #define LOCTEXT_NAMESPACE "Spine" @@ -56,6 +54,9 @@ bool USpineAtlasAssetFactory::FactoryCanImport(const FString &Filename) { } UObject *USpineAtlasAssetFactory::FactoryCreateFile(UClass *InClass, UObject *InParent, FName InName, EObjectFlags Flags, const FString &Filename, const TCHAR *Parms, FFeedbackContext *Warn, bool &bOutOperationCanceled) { + FString FileExtension = FPaths::GetExtension(Filename); + GEditor->GetEditorSubsystem()->BroadcastAssetPreImport(this, InClass, InParent, InName, *FileExtension); + FString rawString; if (!FFileHelper::LoadFileToString(rawString, *Filename)) { return nullptr; @@ -64,13 +65,12 @@ UObject *USpineAtlasAssetFactory::FactoryCreateFile(UClass *InClass, UObject *In FString currentSourcePath, filenameNoExtension, unusedExtension; const FString longPackagePath = FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetPathName()); FPaths::Split(UFactory::GetCurrentFilename(), currentSourcePath, filenameNoExtension, unusedExtension); - FString name(InName.ToString()); - name.Append("-atlas"); - USpineAtlasAsset *asset = NewObject(InParent, InClass, FName(*name), Flags); + USpineAtlasAsset *asset = NewObject(InParent, InClass, InName, Flags); asset->SetRawData(rawString); asset->SetAtlasFileName(FName(*Filename)); LoadAtlas(asset, currentSourcePath, longPackagePath); + GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, asset); return asset; } @@ -109,6 +109,7 @@ EReimportResult::Type USpineAtlasAssetFactory::Reimport(UObject *Obj) { else Obj->MarkPackageDirty(); + GEditor->GetEditorSubsystem()->BroadcastAssetReimport(asset); return EReimportResult::Succeeded; } diff --git a/spine-ue4/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineEditorPlugin.cpp b/spine-ue4/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineEditorPlugin.cpp index e556a854d..3945076c6 100644 --- a/spine-ue4/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineEditorPlugin.cpp +++ b/spine-ue4/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineEditorPlugin.cpp @@ -28,16 +28,44 @@ *****************************************************************************/ #include "SpineEditorPlugin.h" +#include "AssetTypeActions_Base.h" +#include "SpineAtlasAsset.h" +#include "SpineSkeletonDataAsset.h" + +class FSpineAtlasAssetTypeActions : public FAssetTypeActions_Base { +public: + UClass *GetSupportedClass() const override { return USpineAtlasAsset::StaticClass(); }; + FText GetName() const override { return INVTEXT("Spine atlas asset"); }; + FColor GetTypeColor() const override { return FColor::Red; }; + uint32 GetCategories() override { return EAssetTypeCategories::Misc; }; +}; + +class FSpineSkeletonDataAssetTypeActions : public FAssetTypeActions_Base { +public: + UClass *GetSupportedClass() const override { return USpineSkeletonDataAsset::StaticClass(); }; + FText GetName() const override { return INVTEXT("Spine data asset"); }; + FColor GetTypeColor() const override { return FColor::Red; }; + uint32 GetCategories() override { return EAssetTypeCategories::Misc; }; +}; class FSpineEditorPlugin : public ISpineEditorPlugin { virtual void StartupModule() override; virtual void ShutdownModule() override; + TSharedPtr SpineAtlasAssetTypeActions; + TSharedPtr SpineSkeletonDataAssetTypeActions; }; IMPLEMENT_MODULE(FSpineEditorPlugin, SpineEditorPlugin) void FSpineEditorPlugin::StartupModule() { + SpineAtlasAssetTypeActions = MakeShared(); + FAssetToolsModule::GetModule().Get().RegisterAssetTypeActions(SpineAtlasAssetTypeActions.ToSharedRef()); + SpineSkeletonDataAssetTypeActions = MakeShared(); + FAssetToolsModule::GetModule().Get().RegisterAssetTypeActions(SpineSkeletonDataAssetTypeActions.ToSharedRef()); } void FSpineEditorPlugin::ShutdownModule() { + if (!FModuleManager::Get().IsModuleLoaded("AssetTools")) return; + FAssetToolsModule::GetModule().Get().UnregisterAssetTypeActions(SpineAtlasAssetTypeActions.ToSharedRef()); + FAssetToolsModule::GetModule().Get().UnregisterAssetTypeActions(SpineSkeletonDataAssetTypeActions.ToSharedRef()); } diff --git a/spine-ue4/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineSkeletonImportFactory.cpp b/spine-ue4/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineSkeletonImportFactory.cpp index 3603abfc1..f92d03f95 100644 --- a/spine-ue4/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineSkeletonImportFactory.cpp +++ b/spine-ue4/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineSkeletonImportFactory.cpp @@ -76,10 +76,7 @@ void LoadAtlas(const FString &Filename, const FString &TargetPath) { } UObject *USpineSkeletonAssetFactory::FactoryCreateFile(UClass *InClass, UObject *InParent, FName InName, EObjectFlags Flags, const FString &Filename, const TCHAR *Parms, FFeedbackContext *Warn, bool &bOutOperationCanceled) { - FString name(InName.ToString()); - name.Append("-data"); - - USpineSkeletonDataAsset *asset = NewObject(InParent, InClass, FName(*name), Flags); + USpineSkeletonDataAsset *asset = NewObject(InParent, InClass, InName, Flags); TArray rawData; if (!FFileHelper::LoadFileToArray(rawData, *Filename, 0)) { return nullptr; diff --git a/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineAtlasAsset.cpp b/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineAtlasAsset.cpp index 9be02dfcc..bd7f63306 100644 --- a/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineAtlasAsset.cpp +++ b/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineAtlasAsset.cpp @@ -53,10 +53,6 @@ void USpineAtlasAsset::PostInitProperties() { } void USpineAtlasAsset::GetAssetRegistryTags(TArray &OutTags) const { - if (importData) { - OutTags.Add(FAssetRegistryTag(SourceFileTagName(), importData->GetSourceData().ToJson(), FAssetRegistryTag::TT_Hidden)); - } - Super::GetAssetRegistryTags(OutTags); } diff --git a/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonDataAsset.h b/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonDataAsset.h index 9abc7c5df..dd23d8c9e 100644 --- a/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonDataAsset.h +++ b/spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonDataAsset.h @@ -111,7 +111,7 @@ public: protected: UPROPERTY(VisibleAnywhere, Instanced, Category = ImportSettings) - class UAssetImportData *importData; + class UAssetImportData *importData = nullptr; virtual void PostInitProperties() override; virtual void GetAssetRegistryTags(TArray &OutTags) const override; diff --git a/spine-ue4/README.md b/spine-ue4/README.md index 387442d7d..e4f765428 100644 --- a/spine-ue4/README.md +++ b/spine-ue4/README.md @@ -1,5 +1,5 @@ # spine-ue4 -The spine-ue4 runtime provides functionality to load, manipulate and render [Spine](http://esotericsoftware.com) skeletal animation data using [Unreal Engine 4.21+](https://www.unrealengine.com/). spine-ue4 is based on [spine-cpp](../spine-cpp). +The spine-ue4 runtime provides functionality to load, manipulate and render [Spine](http://esotericsoftware.com) skeletal animation data using [Unreal Engine 4.27-5.2](https://www.unrealengine.com/). spine-ue4 is based on [spine-cpp](../spine-cpp). ## Licensing @@ -34,7 +34,7 @@ See the [Spine Runtimes documentation](http://esotericsoftware.com/spine-documen ## Example ### [Please see the spine-ue4 guide for full documentation](http://esotericsoftware.com/spine-ue4) -The Spine UE4 example works on all platforms supported by Unreal Engine. The samples require Unreal Engine 4.25+. +The Spine UE4 example works on all platforms supported by Unreal Engine. The samples require Unreal Engine 4.27-5.2. 1. Copy the `spine-cpp` folder from this repositories root directory to your `Plugins/SpinePlugin/Sources/SpinePlugin/Public/` directory. You can run the `setup.bat` or `setup.sh` scripts to accomplish this. 2. Open the SpineUE4.uproject file with Unreal Editor diff --git a/spine-ue4/SpineUE4.uproject b/spine-ue4/SpineUE4.uproject index 40c4435b1..1a871bfec 100644 --- a/spine-ue4/SpineUE4.uproject +++ b/spine-ue4/SpineUE4.uproject @@ -1,6 +1,6 @@ { "FileVersion": 3, - "EngineAssociation": "5.2", + "EngineAssociation": "5.3", "Category": "", "Description": "", "Modules": [ diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/AtlasAssetBase.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/AtlasAssetBase.cs index 59b5bf5b6..4190ef2b1 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/AtlasAssetBase.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/AtlasAssetBase.cs @@ -71,6 +71,11 @@ namespace Spine.Unity { onDemandTextureLoader.RequestLoadMaterialTextures(material, ref overrideMaterial); } + public virtual void RequireTextureLoaded (Texture placeholderTexture, ref Texture replacementTexture, System.Action onTextureLoaded) { + if (onDemandTextureLoader) + onDemandTextureLoader.RequestLoadTexture(placeholderTexture, ref replacementTexture, onTextureLoaded); + } + [SerializeField] protected LoadingMode textureLoadingMode = LoadingMode.Normal; [SerializeField] protected OnDemandTextureLoader onDemandTextureLoader = null; #endif diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/OnDemandTextureLoader.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/OnDemandTextureLoader.cs index 16de1b725..a7b5c4613 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/OnDemandTextureLoader.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/OnDemandTextureLoader.cs @@ -51,6 +51,27 @@ namespace Spine.Unity { /// A newly created list of materials which has a placeholder texture assigned. /// True, if any placeholder texture is assigned at a Material of the associated AtlasAssetBase. public abstract bool HasPlaceholderTexturesAssigned (out List placeholderMaterials); + + /// + /// Returns whether any main texture is null at a Material of the associated AtlasAssetBase. + /// + /// A newly created list of materials which has a null main texture assigned. + /// True, if any null main texture is assigned at a Material of the associated AtlasAssetBase. + public virtual bool HasNullMainTexturesAssigned (out List nullTextureMaterials) { + nullTextureMaterials = null; + if (!atlasAsset) return false; + + bool anyNullTexture = false; + foreach (Material material in atlasAsset.Materials) { + if (material.mainTexture == null) { + anyNullTexture = true; + if (nullTextureMaterials == null) nullTextureMaterials = new List(); + nullTextureMaterials.Add(material); + } + } + return anyNullTexture; + } + /// /// Assigns previously setup target textures at each Material where placeholder textures are setup. /// True on success, false if the target texture could not be assigned at any of the @@ -60,13 +81,20 @@ namespace Spine.Unity { public abstract void EndCustomTextureLoading (); public abstract bool HasPlaceholderAssigned (Material material); public abstract void RequestLoadMaterialTextures (Material material, ref Material overrideMaterial); + public abstract void RequestLoadTexture (Texture placeholderTexture, ref Texture replacementTexture, + System.Action onTextureLoaded = null); public abstract void Clear (bool clearAtlasAsset = false); #region Event delegates public delegate void TextureLoadDelegate (OnDemandTextureLoader loader, Material material, int textureIndex); + protected event TextureLoadDelegate onTextureRequested; protected event TextureLoadDelegate onTextureLoaded; protected event TextureLoadDelegate onTextureUnloaded; + public event TextureLoadDelegate TextureRequested { + add { onTextureRequested += value; } + remove { onTextureRequested -= value; } + } public event TextureLoadDelegate TextureLoaded { add { onTextureLoaded += value; } remove { onTextureLoaded -= value; } @@ -76,6 +104,10 @@ namespace Spine.Unity { remove { onTextureUnloaded -= value; } } + protected void OnTextureRequested (Material material, int textureIndex) { + if (onTextureRequested != null) + onTextureRequested(this, material, textureIndex); + } protected void OnTextureLoaded (Material material, int textureIndex) { if (onTextureLoaded != null) onTextureLoaded(this, material, textureIndex); diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonAnimation.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonAnimation.cs index 29c280661..087dabd76 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonAnimation.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonAnimation.cs @@ -273,8 +273,12 @@ namespace Spine.Unity { } public override void LateUpdate () { + if (updateTiming == UpdateTiming.InLateUpdate && valid) + Update(unscaledTime ? Time.unscaledDeltaTime : Time.deltaTime); + // instantiation can happen from Update() after this component, leading to a missing Update() call. if (!wasUpdatedAfterInit) Update(0); + base.LateUpdate(); } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs index d8e0cf2dc..fac6c783a 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs @@ -35,6 +35,8 @@ #define HAS_CULL_TRANSPARENT_MESH #endif +#define SPINE_OPTIONAL_ON_DEMAND_LOADING + using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; @@ -415,6 +417,9 @@ namespace Spine.Unity { if (freeze) return; if (updateMode != UpdateMode.FullUpdate) return; + if (updateTiming == UpdateTiming.InLateUpdate) + Update(unscaledTime ? Time.unscaledDeltaTime : Time.deltaTime); + PrepareInstructionsAndRenderers(); SetVerticesDirty(); // triggers Rebuild and avoids potential double-update in a single frame @@ -818,14 +823,22 @@ namespace Spine.Unity { else canvasRenderer.SetMesh(null); + bool assignTexture = false; if (currentInstructions.submeshInstructions.Count > 0) { Material material = currentInstructions.submeshInstructions.Items[0].material; if (material != null && baseTexture != material.mainTexture) { baseTexture = material.mainTexture; if (overrideTexture == null && assignAtCanvasRenderer) - canvasRenderer.SetTexture(this.mainTexture); + assignTexture = true; } } + +#if SPINE_OPTIONAL_ON_DEMAND_LOADING + if (Application.isPlaying) + HandleOnDemandLoading(); +#endif + if (assignTexture) + canvasRenderer.SetTexture(this.mainTexture); } protected void UpdateMaterialsMultipleCanvasRenderers (SkeletonRendererInstruction currentInstructions) { @@ -896,7 +909,6 @@ namespace Spine.Unity { bool pmaVertexColors = meshGenerator.settings.pmaVertexColors; Material[] usedMaterialItems = usedMaterials.Items; Texture[] usedTextureItems = usedTextures.Items; - bool assignAtCanvasRenderer = (assignMeshOverrideSingle == null || !disableMeshAssignmentOnOverride); for (int i = 0; i < submeshCount; i++) { SubmeshInstruction submeshInstructionItem = currentInstructions.submeshInstructions.Items[i]; meshGenerator.Begin(); @@ -929,13 +941,50 @@ namespace Spine.Unity { #endif } canvasRenderer.materialCount = 1; - if (assignAtCanvasRenderer) - canvasRenderer.SetMaterial(usedMaterialItems[i], usedTextureItems[i]); } + +#if SPINE_OPTIONAL_ON_DEMAND_LOADING + if (Application.isPlaying) + HandleOnDemandLoading(); +#endif + bool assignAtCanvasRenderer = (assignMeshOverrideSingle == null || !disableMeshAssignmentOnOverride); + if (assignAtCanvasRenderer) { + for (int i = 0; i < submeshCount; i++) { + CanvasRenderer canvasRenderer = canvasRenderers[i]; + canvasRenderer.SetMaterial(usedMaterialItems[i], usedTextureItems[i]); + } + } + if (assignMeshOverrideMultiple != null) assignMeshOverrideMultiple(submeshCount, meshesItems, usedMaterialItems, usedTextureItems); } +#if SPINE_OPTIONAL_ON_DEMAND_LOADING + void HandleOnDemandLoading () { + foreach (AtlasAssetBase atlasAsset in skeletonDataAsset.atlasAssets) { + if (atlasAsset.TextureLoadingMode != AtlasAssetBase.LoadingMode.Normal) { + atlasAsset.BeginCustomTextureLoading(); + + if (!this.allowMultipleCanvasRenderers) { + Texture loadedTexture = null; + atlasAsset.RequireTextureLoaded(this.mainTexture, ref loadedTexture, null); + if (loadedTexture) + this.baseTexture = loadedTexture; + } else { + Texture[] textureItems = usedTextures.Items; + for (int i = 0, count = usedTextures.Count; i < count; ++i) { + Texture loadedTexture = null; + atlasAsset.RequireTextureLoaded(textureItems[i], ref loadedTexture, null); + if (loadedTexture) + usedTextures.Items[i] = loadedTexture; + } + } + atlasAsset.EndCustomTextureLoading(); + } + } + } +#endif + protected void EnsureCanvasRendererCount (int targetCount) { #if UNITY_EDITOR RemoveNullCanvasRenderers(); diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs index 8e694feec..fa17e9d98 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs @@ -109,6 +109,13 @@ namespace Spine.Unity { UpdateAnimation(); } + /// Manual animation update. Required when updateTiming is set to ManualUpdate. + /// Ignored parameter. + public virtual void Update (float deltaTime) { + if (!valid) return; + UpdateAnimation(); + } + protected void UpdateAnimation () { wasUpdatedAfterInit = true; @@ -160,6 +167,8 @@ namespace Spine.Unity { } public override void LateUpdate () { + if (updateTiming == UpdateTiming.InLateUpdate && valid && translator != null && translator.Animator != null) + UpdateAnimation(); // instantiation can happen from Update() after this component, leading to a missing Update() call. if (!wasUpdatedAfterInit) Update(); base.LateUpdate(); diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/ISkeletonAnimation.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/ISkeletonAnimation.cs index 67ecf8393..06dea7f70 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/ISkeletonAnimation.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/ISkeletonAnimation.cs @@ -40,7 +40,8 @@ namespace Spine.Unity { public enum UpdateTiming { ManualUpdate = 0, InUpdate, - InFixedUpdate + InFixedUpdate, + InLateUpdate } public delegate void ISkeletonAnimationDelegate (ISkeletonAnimation animated); diff --git a/spine-unity/Assets/Spine/package.json b/spine-unity/Assets/Spine/package.json index ac358f72b..a21deed83 100644 --- a/spine-unity/Assets/Spine/package.json +++ b/spine-unity/Assets/Spine/package.json @@ -2,7 +2,7 @@ "name": "com.esotericsoftware.spine.spine-unity", "displayName": "spine-unity Runtime", "description": "This plugin provides the spine-unity runtime core.", - "version": "4.1.25", + "version": "4.1.29", "unity": "2018.3", "author": { "name": "Esoteric Software", diff --git a/spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor/AddressablesTextureLoaderInspector.cs b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor/AddressablesTextureLoaderInspector.cs index dcb3bf2a4..f0af3df67 100644 --- a/spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor/AddressablesTextureLoaderInspector.cs +++ b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor/AddressablesTextureLoaderInspector.cs @@ -47,9 +47,10 @@ namespace Spine.Unity.Editor { [CustomEditor(typeof(AddressablesTextureLoader)), CanEditMultipleObjects] public class AddressablesTextureLoaderInspector : GenericTextureLoaderInspector { - public string LoaderSuffix { get { return "_Addressable"; } } public class AddressablesMethodImplementations : StaticMethodImplementations { + public override string LoaderSuffix { get { return "_Addressable"; } } + public override GenericTextureLoader GetOrCreateLoader (string loaderPath) { AddressablesTextureLoader loader = AssetDatabase.LoadAssetAtPath(loaderPath); if (loader == null) { diff --git a/spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime/AddressablesTextureLoader.cs b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime/AddressablesTextureLoader.cs index be137bb4b..3411087f2 100644 --- a/spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime/AddressablesTextureLoader.cs +++ b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime/AddressablesTextureLoader.cs @@ -76,13 +76,17 @@ namespace Spine.Unity { [System.Serializable] public class AddressablesTextureLoader : GenericOnDemandTextureLoader { public override void CreateTextureRequest (AddressableTextureReference targetReference, - MaterialOnDemandData materialData, int textureIndex, Material materialToUpdate) { + MaterialOnDemandData materialData, int textureIndex, Material materialToUpdate, + System.Action onTextureLoaded) { + OnTextureRequested(materialToUpdate, textureIndex); materialData.textureRequests[textureIndex].handle = targetReference.assetReference.LoadAssetAsync(); materialData.textureRequests[textureIndex].handle.Completed += (obj) => { if (obj.Status == AsyncOperationStatus.Succeeded) { - materialToUpdate.mainTexture = (Texture)targetReference.assetReference.Asset; + Texture loadedTexture = (Texture)targetReference.assetReference.Asset; + materialToUpdate.mainTexture = loadedTexture; OnTextureLoaded(materialToUpdate, textureIndex); + if (onTextureLoaded != null) onTextureLoaded(loadedTexture); } }; } diff --git a/spine-unity/Modules/com.esotericsoftware.spine.addressables/package.json b/spine-unity/Modules/com.esotericsoftware.spine.addressables/package.json index 56662e150..f2ae6d3e0 100644 --- a/spine-unity/Modules/com.esotericsoftware.spine.addressables/package.json +++ b/spine-unity/Modules/com.esotericsoftware.spine.addressables/package.json @@ -2,7 +2,7 @@ "name": "com.esotericsoftware.spine.addressables", "displayName": "Spine Addressables Extensions [Experimental]", "description": "This experimental plugin provides integration of Addressables on-demand texture loading for the spine-unity runtime.\nPlease be sure to test this package first and create backups of your project before using.\n\nUsage: First declare your target Material textures as addressable. Then select the SpineAtlasAsset, right-click the SpineAtlasAsset Inspector heading and select 'Add Addressables Loader'. This generates an 'AddressableTextureLoader' asset providing configuration parameters and sets up low-resolution placeholder textures which are automatically assigned in a pre-build step when building your game executable.\n\nPrerequisites:\nIt requires a working installation of the spine-unity runtime (via the spine-unity unitypackage), version 4.1.\n(See http://esotericsoftware.com/git/spine-runtimes/spine-unity)", - "version": "4.1.0-preview.1", + "version": "4.1.0-preview.2", "unity": "2018.3", "author": { "name": "Esoteric Software", diff --git a/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor/GenericOnDemandTextureLoaderInspector.cs b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor/GenericOnDemandTextureLoaderInspector.cs index 9744617fc..786e1b8b7 100644 --- a/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor/GenericOnDemandTextureLoaderInspector.cs +++ b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor/GenericOnDemandTextureLoaderInspector.cs @@ -37,6 +37,7 @@ using System; using System.Collections.Generic; +using System.Linq; using UnityEditor; using UnityEngine; @@ -92,7 +93,7 @@ namespace Spine.Unity.Editor { /// When set to e.g. "_Addressable", the loader asset created for /// the "Skeleton_Atlas" asset is named "Skeleton_Addressable". /// - public string LoaderSuffix { get; } + public virtual string LoaderSuffix { get { return "_Loader"; } } public abstract bool SetupOnDemandLoadingReference ( ref TargetReference targetTextureReference, Texture targetTexture); @@ -237,7 +238,7 @@ namespace Spine.Unity.Editor { #if NEWPLAYMODECALLBACKS static void OnPlaymodeChanged (PlayModeStateChange mode) { - bool assignTargetTextures = mode == PlayModeStateChange.ExitingPlayMode; + bool assignTargetTextures = mode == PlayModeStateChange.EnteredEditMode; #else static void OnPlaymodeChanged () { bool assignTargetTextures = !Application.isPlaying; @@ -259,14 +260,23 @@ namespace Spine.Unity.Editor { public static void AssignTargetTexturesAtLoader (OnDemandTextureLoader loader) { List placeholderMaterials; + List nullTextureMaterials; bool anyPlaceholdersAssigned = loader.HasPlaceholderTexturesAssigned(out placeholderMaterials); - if (anyPlaceholdersAssigned) { - Debug.Log("OnDemandTextureLoader detected placeholders assigned at one or more materials. Resetting to target textures.", loader); + bool anyMaterialNull = loader.HasNullMainTexturesAssigned(out nullTextureMaterials); + if (anyPlaceholdersAssigned || anyMaterialNull) { + Debug.Log("OnDemandTextureLoader detected placeholders assigned or null main textures at one or more materials. Resetting to target textures.", loader); AssetDatabase.StartAssetEditing(); IEnumerable modifiedMaterials; loader.AssignTargetTextures(out modifiedMaterials); - foreach (Material placeholderMaterial in placeholderMaterials) { - EditorUtility.SetDirty(placeholderMaterial); + if (placeholderMaterials != null) { + foreach (Material placeholderMaterial in placeholderMaterials) { + EditorUtility.SetDirty(placeholderMaterial); + } + } + if (nullTextureMaterials != null) { + foreach (Material nullTextureMaterial in nullTextureMaterials) { + EditorUtility.SetDirty(nullTextureMaterial); + } } AssetDatabase.StopAssetEditing(); AssetDatabase.SaveAssets(); diff --git a/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime/GenericOnDemandTextureLoader.cs b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime/GenericOnDemandTextureLoader.cs index 47a02f031..95e33ce19 100644 --- a/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime/GenericOnDemandTextureLoader.cs +++ b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime/GenericOnDemandTextureLoader.cs @@ -164,6 +164,7 @@ namespace Spine.Unity { if (placeholderMaterials == null) placeholderMaterials = new List(); placeholderMaterials.Add(material); } + materialIndex++; } return anyPlaceholderAssigned; } @@ -180,8 +181,7 @@ namespace Spine.Unity { atlasAsset, i + 1, targetMaterial), this); return false; } - Material ignoredArgument = null; - RequestLoadMaterialTextures(targetMaterial, ref ignoredArgument); + AssignTargetTextures(targetMaterial, i); ++i; } modifiedMaterials = atlasAsset.Materials; @@ -223,7 +223,7 @@ namespace Spine.Unity { int foundMaterialIndex = Array.FindIndex(placeholderMap, entry => entry.textures[textureIndex].placeholderTexture == currentTexture); if (foundMaterialIndex >= 0) - RequestLoadTexture(material, foundMaterialIndex, textureIndex); + RequestLoadTexture(material, foundMaterialIndex, textureIndex, null); int loadedMaterialIndex = Array.FindIndex(loadedDataAtMaterial, entry => entry.textureRequests[textureIndex].WasRequested && @@ -232,33 +232,69 @@ namespace Spine.Unity { loadedDataAtMaterial[loadedMaterialIndex].lastFrameRequested = Time.frameCount; } - protected virtual void RequestLoadTexture (Material material, int materialIndex, int textureIndex) { + public override void RequestLoadTexture (Texture placeholderTexture, ref Texture replacementTexture, + System.Action onTextureLoaded = null) { + + if (placeholderTexture == null) return; + + Texture currentTexture = placeholderTexture; + int textureIndex = 0; // Todo: currently only main texture is supported. + + int foundMaterialIndex = Array.FindIndex(placeholderMap, entry => entry.textures[textureIndex].placeholderTexture == currentTexture); + if (foundMaterialIndex >= 0) { + Material material = atlasAsset.Materials.ElementAt(foundMaterialIndex); + Texture loadedTexture = RequestLoadTexture(material, foundMaterialIndex, textureIndex, onTextureLoaded); + if (loadedTexture != null) + replacementTexture = loadedTexture; + } + + int loadedMaterialIndex = Array.FindIndex(loadedDataAtMaterial, entry => + entry.textureRequests[textureIndex].WasRequested && + entry.textureRequests[textureIndex].IsTarget(placeholderTexture)); + if (loadedMaterialIndex >= 0) + loadedDataAtMaterial[loadedMaterialIndex].lastFrameRequested = Time.frameCount; + } + + protected void AssignTargetTextures (Material material, int materialIndex) { + int textureIndex = 0; // Todo: currently only main texture is supported. + RequestLoadTexture(material, materialIndex, textureIndex, null); + } + + protected virtual Texture RequestLoadTexture (Material material, int materialIndex, int textureIndex, + System.Action onTextureLoaded) { + PlaceholderTextureMapping[] placeholderTextures = placeholderMap[materialIndex].textures; TargetReference targetReference = placeholderTextures[textureIndex].targetTextureReference; loadedDataAtMaterial[materialIndex].lastFrameRequested = Time.frameCount; #if UNITY_EDITOR if (!Application.isPlaying) { - if (targetReference.EditorTexture != null) + if (targetReference.EditorTexture != null) { material.mainTexture = targetReference.EditorTexture; - return; + if (onTextureLoaded != null) onTextureLoaded(targetReference.EditorTexture); + } + return targetReference.EditorTexture; } #endif MaterialOnDemandData materialData = loadedDataAtMaterial[materialIndex]; if (materialData.textureRequests[textureIndex].WasRequested) { Texture loadedTexture = GetAlreadyLoadedTexture(materialIndex, textureIndex); - if (loadedTexture != null) + if (loadedTexture != null) { material.mainTexture = loadedTexture; - return; + if (onTextureLoaded != null) onTextureLoaded(loadedTexture); + } + return loadedTexture; } - CreateTextureRequest(targetReference, materialData, textureIndex, material); + CreateTextureRequest(targetReference, materialData, textureIndex, material, onTextureLoaded); + return null; } public abstract Texture GetAlreadyLoadedTexture (int materialIndex, int textureIndex); public abstract void CreateTextureRequest (TargetReference targetReference, - MaterialOnDemandData materialData, int textureIndex, Material materialToUpdate); + MaterialOnDemandData materialData, int textureIndex, Material materialToUpdate, + System.Action onTextureLoaded); public virtual void UnloadUnusedTextures () { int currentFrameCount = Time.frameCount; diff --git a/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/package.json b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/package.json index 5682f3443..88ff77e98 100644 --- a/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/package.json +++ b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/package.json @@ -2,7 +2,7 @@ "name": "com.esotericsoftware.spine.on-demand-loading", "displayName": "Spine On-Demand Loading Extensions [Experimental]", "description": "This experimental plugin provides a generic basic implementation of on-demand texture loading for the spine-unity runtime. You might want to use the available com.esotericsoftware.spine.addressables package which depends on this package.\nPlease be sure to test this package first and create backups of your project before using.\n\nPrerequisites:\nIt requires a working installation of the spine-unity runtime (via the spine-unity unitypackage), version 4.1.\n(See http://esotericsoftware.com/git/spine-runtimes/spine-unity)", - "version": "4.1.0", + "version": "4.1.0-preview.2", "unity": "2018.3", "author": { "name": "Esoteric Software",