Prechádzať zdrojové kódy

Merge branch '4.1' of https://github.com/esotericsoftware/spine-runtimes into 4.1

badlogic 1 rok pred
rodič
commit
1c9b263955
44 zmenil súbory, kde vykonal 1458 pridanie a 79 odobranie
  1. 2 2
      .github/workflows/spine-godot-v4-all.yml
  2. 2 2
      .github/workflows/spine-godot.yml
  3. 1 0
      CHANGELOG.md
  4. 0 1
      spine-c/spine-c/src/spine/MeshAttachment.c
  5. 6 0
      spine-flutter/CHANGELOG.md
  6. 1079 0
      spine-flutter/example/assets/dragon-ess.json
  7. 1 1
      spine-flutter/example/ios/Runner.xcodeproj/project.pbxproj
  8. 31 1
      spine-flutter/example/lib/flame_example.dart
  9. 13 1
      spine-flutter/example/lib/main.dart
  10. 1 1
      spine-flutter/example/lib/pause_play_animation.dart
  11. 1 1
      spine-flutter/example/macos/Runner.xcodeproj/project.pbxproj
  12. 13 5
      spine-flutter/example/pubspec.lock
  13. BIN
      spine-flutter/lib/assets/libspine_flutter.wasm
  14. 1 5
      spine-flutter/lib/spine_flutter.dart
  15. 1 1
      spine-flutter/pubspec.yaml
  16. 6 4
      spine-flutter/src/spine_flutter.cpp
  17. 5 5
      spine-godot/example-v4-csharp/examples/02-animation-state-listeners/AnimationStateListener.cs
  18. 1 1
      spine-godot/example-v4-csharp/examples/03-mix-and-match/MixAndMatch.cs
  19. 24 0
      spine-godot/spine_godot/SpineAtlasResource.cpp
  20. 11 2
      spine-godot/spine_godot/SpineAtlasResource.h
  21. 19 0
      spine-godot/spine_godot/SpineSkeletonDataResource.cpp
  22. 17 0
      spine-godot/spine_godot/SpineSkeletonFileResource.cpp
  23. 4 0
      spine-godot/spine_godot/SpineSkeletonFileResource.h
  24. 2 2
      spine-ts/spine-webgl/src/SpineCanvas.ts
  25. 7 6
      spine-ue4/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineAtlasImportFactory.cpp
  26. 28 0
      spine-ue4/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineEditorPlugin.cpp
  27. 1 4
      spine-ue4/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineSkeletonImportFactory.cpp
  28. 0 4
      spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineAtlasAsset.cpp
  29. 1 1
      spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonDataAsset.h
  30. 2 2
      spine-ue4/README.md
  31. 1 1
      spine-ue4/SpineUE4.uproject
  32. 5 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/AtlasAssetBase.cs
  33. 32 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/OnDemandTextureLoader.cs
  34. 4 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonAnimation.cs
  35. 52 3
      spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs
  36. 9 0
      spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs
  37. 2 1
      spine-unity/Assets/Spine/Runtime/spine-unity/ISkeletonAnimation.cs
  38. 1 1
      spine-unity/Assets/Spine/package.json
  39. 2 1
      spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor/AddressablesTextureLoaderInspector.cs
  40. 6 2
      spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime/AddressablesTextureLoader.cs
  41. 1 1
      spine-unity/Modules/com.esotericsoftware.spine.addressables/package.json
  42. 16 6
      spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor/GenericOnDemandTextureLoaderInspector.cs
  43. 46 10
      spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime/GenericOnDemandTextureLoader.cs
  44. 1 1
      spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/package.json

+ 2 - 2
.github/workflows/spine-godot-v4-all.yml

@@ -15,8 +15,8 @@ jobs:
         version:
         version:
           [
           [
             {"tag": "4.0.4-stable", "version": "4.0.4.stable", "mono": false},
             {"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
     uses: ./.github/workflows/spine-godot-v4.yml
     with:
     with:

+ 2 - 2
.github/workflows/spine-godot.yml

@@ -12,8 +12,8 @@ env:
   AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
   AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
   AWS_EC2_METADATA_DISABLED: true
   AWS_EC2_METADATA_DISABLED: true
   EM_VERSION: 3.1.14
   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:
 jobs:
   godot-editor-windows:
   godot-editor-windows:

+ 1 - 0
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.
     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;`.
   * 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.
   * 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**
 * **Breaking changes**
   * Made `SkeletonGraphic.unscaledTime` parameter protected, use the new property `UnscaledTime` instead.
   * Made `SkeletonGraphic.unscaledTime` parameter protected, use the new property `UnscaledTime` instead.

+ 0 - 1
spine-c/spine-c/src/spine/MeshAttachment.c

@@ -43,7 +43,6 @@ void _spMeshAttachment_dispose(spAttachment *attachment) {
 		FREE(self->edges);
 		FREE(self->edges);
 	} else
 	} else
 		_spAttachment_deinit(attachment);
 		_spAttachment_deinit(attachment);
-	if (self->sequence) FREE(self->sequence);
 	FREE(self);
 	FREE(self);
 }
 }
 
 

+ 6 - 0
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
 # 4.1.5
 * Updated http dependency to 1.1.0
 * Updated http dependency to 1.1.0
 
 

+ 1079 - 0
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 }
+						]
+					}
+				}
+			}
+		}
+	}
+}
+}

+ 1 - 1
spine-flutter/example/ios/Runner.xcodeproj/project.pbxproj

@@ -156,7 +156,7 @@
 		97C146E61CF9000F007C117D /* Project object */ = {
 		97C146E61CF9000F007C117D /* Project object */ = {
 			isa = PBXProject;
 			isa = PBXProject;
 			attributes = {
 			attributes = {
-				LastUpgradeCheck = 1300;
+				LastUpgradeCheck = 1430;
 				ORGANIZATIONNAME = "";
 				ORGANIZATIONNAME = "";
 				TargetAttributes = {
 				TargetAttributes = {
 					97C146ED1CF9000F007C117D = {
 					97C146ED1CF9000F007C117D = {

+ 31 - 1
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
     // Load the Spineboy atlas and skeleton data from asset files
     // and create a SpineComponent from them, scaled down and
     // and create a SpineComponent from them, scaled down and
     // centered on the screen
     // 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));
         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
     // 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<void> 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 {
 class PreloadAndShareSpineDataExample extends FlameGame {
   late final SkeletonData cachedSkeletonData;
   late final SkeletonData cachedSkeletonData;
   late final Atlas cachedAtlas;
   late final Atlas cachedAtlas;

+ 13 - 1
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<void>(
+                  builder: (context) => SpineFlameGameWidget(DragonExample()),
+                ),
+              );
+            },
+          ),
+          spacer,
         ])));
         ])));
   }
   }
 }
 }

+ 1 - 1
spine-flutter/example/lib/pause_play_animation.dart

@@ -65,7 +65,7 @@ class PlayPauseAnimationState extends State<PlayPauseAnimation> {
       appBar: AppBar(title: const Text('Play/Pause')),
       appBar: AppBar(title: const Text('Play/Pause')),
       body: SpineWidget.fromAsset(
       body: SpineWidget.fromAsset(
         "assets/dragon.atlas",
         "assets/dragon.atlas",
-        "assets/dragon-ess.skel",
+        "assets/dragon-ess.json",
         controller,
         controller,
         boundsProvider: SkinAndAnimationBounds(animation: "flying"),
         boundsProvider: SkinAndAnimationBounds(animation: "flying"),
       ),
       ),

+ 1 - 1
spine-flutter/example/macos/Runner.xcodeproj/project.pbxproj

@@ -203,7 +203,7 @@
 			isa = PBXProject;
 			isa = PBXProject;
 			attributes = {
 			attributes = {
 				LastSwiftUpdateCheck = 0920;
 				LastSwiftUpdateCheck = 0920;
-				LastUpgradeCheck = 1300;
+				LastUpgradeCheck = 1430;
 				ORGANIZATIONNAME = "";
 				ORGANIZATIONNAME = "";
 				TargetAttributes = {
 				TargetAttributes = {
 					33CC10EC2044A3C60003C045 = {
 					33CC10EC2044A3C60003C045 = {

+ 13 - 5
spine-flutter/example/pubspec.lock

@@ -21,10 +21,10 @@ packages:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: collection
       name: collection
-      sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
+      sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
       url: "https://pub.dev"
       url: "https://pub.dev"
     source: hosted
     source: hosted
-    version: "1.17.1"
+    version: "1.17.2"
   crypto:
   crypto:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -114,10 +114,10 @@ packages:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: material_color_utilities
       name: material_color_utilities
-      sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
+      sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
       url: "https://pub.dev"
       url: "https://pub.dev"
     source: hosted
     source: hosted
-    version: "0.2.0"
+    version: "0.5.0"
   meta:
   meta:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -202,6 +202,14 @@ packages:
       url: "https://pub.dev"
       url: "https://pub.dev"
     source: hosted
     source: hosted
     version: "2.1.4"
     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:
   web_ffi_fork:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -211,5 +219,5 @@ packages:
     source: hosted
     source: hosted
     version: "0.7.4"
     version: "0.7.4"
 sdks:
 sdks:
-  dart: ">=3.0.0 <4.0.0"
+  dart: ">=3.1.0-185.0.dev <4.0.0"
   flutter: ">=3.10.5"
   flutter: ">=3.10.5"

BIN
spine-flutter/lib/assets/libspine_flutter.wasm


+ 1 - 5
spine-flutter/lib/spine_flutter.dart

@@ -126,7 +126,7 @@ class Atlas {
     final numImagePaths = _bindings.spine_atlas_get_num_image_paths(atlas);
     final numImagePaths = _bindings.spine_atlas_get_num_image_paths(atlas);
     for (int i = 0; i < numImagePaths; i++) {
     for (int i = 0; i < numImagePaths; i++) {
       final Pointer<Utf8> atlasPageFile = _bindings.spine_atlas_get_image_path(atlas, i).cast();
       final Pointer<Utf8> 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);
       var imageData = await loadFile(imagePath);
       final Codec codec = await instantiateImageCodec(imageData);
       final Codec codec = await instantiateImageCodec(imageData);
       final FrameInfo frameInfo = await codec.getNextFrame();
       final FrameInfo frameInfo = await codec.getNextFrame();
@@ -807,7 +807,6 @@ class Bone {
   Vec2 worldToLocal(double worldX, double worldY) {
   Vec2 worldToLocal(double worldX, double worldY) {
     final local = _bindings.spine_bone_world_to_local(_bone, worldX, 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));
     final result = Vec2(_bindings.spine_vector_get_x(local), _bindings.spine_vector_get_y(local));
-    _allocator.free(local);
     return result;
     return result;
   }
   }
 
 
@@ -815,7 +814,6 @@ class Bone {
   Vec2 localToWorld(double localX, double localY) {
   Vec2 localToWorld(double localX, double localY) {
     final world = _bindings.spine_bone_local_to_world(_bone, localX, 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));
     final result = Vec2(_bindings.spine_vector_get_x(world), _bindings.spine_vector_get_y(world));
-    _allocator.free(world);
     return result;
     return result;
   }
   }
 
 
@@ -1871,7 +1869,6 @@ class PointAttachment extends Attachment<spine_point_attachment> {
   Vec2 computeWorldPosition(Bone bone) {
   Vec2 computeWorldPosition(Bone bone) {
     final position = _bindings.spine_point_attachment_compute_world_position(_attachment, 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));
     final result = Vec2(_bindings.spine_vector_get_x(position), _bindings.spine_vector_get_y(position));
-    _allocator.free(position);
     return result;
     return result;
   }
   }
 
 
@@ -2888,7 +2885,6 @@ class Skeleton {
     final nativeBounds = _bindings.spine_skeleton_get_bounds(_skeleton);
     final nativeBounds = _bindings.spine_skeleton_get_bounds(_skeleton);
     final bounds = Bounds(_bindings.spine_bounds_get_x(nativeBounds), _bindings.spine_bounds_get_y(nativeBounds),
     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));
         _bindings.spine_bounds_get_width(nativeBounds), _bindings.spine_bounds_get_height(nativeBounds));
-    _allocator.free(nativeBounds);
     return bounds;
     return bounds;
   }
   }
 
 

+ 1 - 1
spine-flutter/pubspec.yaml

@@ -1,6 +1,6 @@
 name: spine_flutter
 name: spine_flutter
 description: The official Spine Flutter Runtime to load, display and interact with Spine animations.
 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
 homepage: https://esotericsoftware.com
 repository: https://github.com/esotericsoftware/spine-runtimes
 repository: https://github.com/esotericsoftware/spine-runtimes
 issue_tracker: https://github.com/esotericsoftware/spine-runtimes/issues
 issue_tracker: https://github.com/esotericsoftware/spine-runtimes/issues

+ 6 - 4
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);
 	return (spine_path_constraint) _skeleton->findPathConstraint(constraintName);
 }
 }
 
 
+_spine_bounds tmp_bounds;
 spine_bounds spine_skeleton_get_bounds(spine_skeleton skeleton) {
 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;
 	if (skeleton == nullptr) return (spine_bounds) bounds;
 	Skeleton *_skeleton = (Skeleton *) skeleton;
 	Skeleton *_skeleton = (Skeleton *) skeleton;
 	Vector<float> vertices;
 	Vector<float> vertices;
@@ -2128,8 +2129,9 @@ void spine_bone_set_to_setup_pose(spine_bone bone) {
 	_bone->setToSetupPose();
 	_bone->setToSetupPose();
 }
 }
 
 
+_spine_vector tmp_vector;
 spine_vector spine_bone_world_to_local(spine_bone bone, float worldX, float worldY) {
 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;
 	if (bone == nullptr) return (spine_vector) coords;
 	Bone *_bone = (Bone *) bone;
 	Bone *_bone = (Bone *) bone;
 	_bone->worldToLocal(worldX, worldY, coords->x, coords->y);
 	_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 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;
 	if (bone == nullptr) return (spine_vector) coords;
 	Bone *_bone = (Bone *) bone;
 	Bone *_bone = (Bone *) bone;
 	_bone->localToWorld(localX, localY, coords->x, coords->y);
 	_bone->localToWorld(localX, localY, coords->x, coords->y);
@@ -2521,7 +2523,7 @@ void spine_attachment_dispose(spine_attachment attachment) {
 
 
 // PointAttachment
 // PointAttachment
 spine_vector spine_point_attachment_compute_world_position(spine_point_attachment attachment, spine_bone bone) {
 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;
 	if (attachment == nullptr) return (spine_vector) result;
 	PointAttachment *_attachment = (PointAttachment *) attachment;
 	PointAttachment *_attachment = (PointAttachment *) attachment;
 	_attachment->computeWorldPosition(*(Bone *) bone, result->x, result->y);
 	_attachment->computeWorldPosition(*(Bone *) bone, result->x, result->y);

+ 5 - 5
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) =>
 		spineboy.AnimationStarted += (sprite, animationState, trackEntry) =>
 		{
 		{
 			var spineTrackEntry = trackEntry as SpineTrackEntry;
 			var spineTrackEntry = trackEntry as SpineTrackEntry;
-			Console.WriteLine("Animation started: " + spineTrackEntry.GetAnimation().GetName());
+			GD.Print("Animation started: " + spineTrackEntry.GetAnimation().GetName());
 		};
 		};
 		spineboy.AnimationInterrupted += (sprite, animationState, trackEntry) =>
 		spineboy.AnimationInterrupted += (sprite, animationState, trackEntry) =>
 		{
 		{
 			var spineTrackEntry = trackEntry as SpineTrackEntry;
 			var spineTrackEntry = trackEntry as SpineTrackEntry;
-			Console.WriteLine("Animation interrupted: " + spineTrackEntry.GetAnimation().GetName());
+			GD.Print("Animation interrupted: " + spineTrackEntry.GetAnimation().GetName());
 		};
 		};
 		spineboy.AnimationCompleted += (sprite, animationState, trackEntry) =>
 		spineboy.AnimationCompleted += (sprite, animationState, trackEntry) =>
 		{
 		{
 			var spineTrackEntry = trackEntry as SpineTrackEntry;
 			var spineTrackEntry = trackEntry as SpineTrackEntry;
-			Console.WriteLine("Animation completed: " + spineTrackEntry.GetAnimation().GetName());
+			GD.Print("Animation completed: " + spineTrackEntry.GetAnimation().GetName());
 		};
 		};
 		spineboy.AnimationDisposed += (sprite, animationState, trackEntry) =>
 		spineboy.AnimationDisposed += (sprite, animationState, trackEntry) =>
 		{
 		{
 			var spineTrackEntry = trackEntry as SpineTrackEntry;
 			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) =>
 		spineboy.AnimationEvent += (sprite, animationState, trackEntry, eventObject) =>
 		{
 		{
 			var spineTrackEntry = trackEntry as SpineTrackEntry;
 			var spineTrackEntry = trackEntry as SpineTrackEntry;
 			var spineEvent = eventObject as SpineEvent;
 			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")
 			if (spineEvent.GetData().GetEventName() == "footstep")
 				footStepAudio.Play();
 				footStepAudio.Play();
 		};
 		};

+ 1 - 1
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())
 		foreach (SpineSkinEntry entry in custom_skin.GetAttachments())
 		{
 		{
-			Console.WriteLine(entry.GetSlotIndex() + " " + entry.GetName());
+			GD.Print(entry.GetSlotIndex() + " " + entry.GetName());
 		}
 		}
 
 
 		GetAnimationState().SetAnimation("dance", true, 0);
 		GetAnimationState().SetAnimation("dance", true, 0);

+ 24 - 0
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::STRING, "source_path"), "", "get_source_path");
 	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "textures"), "", "get_textures");
 	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "textures"), "", "get_textures");
 	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "normal_maps"), "", "get_normal_maps");
 	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") {
 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;
 	return OK;
 }
 }
 
 
+#if VERSION_MAJOR > 3
+Error SpineAtlasResource::copy_from(const Ref<Resource> &p_resource) {
+	auto error = Resource::copy_from(p_resource);
+	if (error != OK) return error;
+
+	const Ref<SpineAtlasResource> &spineAtlas = static_cast<const Ref<SpineAtlasResource> &>(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
 #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) {
 RES SpineAtlasResourceFormatLoader::load(const String &path, const String &original_path, Error *error, bool use_sub_threads, float *progress, CacheMode cache_mode) {
 #else
 #else

+ 11 - 2
spine-godot/spine_godot/SpineAtlasResource.h

@@ -45,8 +45,8 @@ class SpineAtlasResource : public Resource {
 protected:
 protected:
 	static void _bind_methods();
 	static void _bind_methods();
 
 
-	spine::Atlas *atlas;
-	GodotSpineTextureLoader *texture_loader;
+	mutable spine::Atlas *atlas;
+	mutable GodotSpineTextureLoader *texture_loader;
 
 
 	String source_path;
 	String source_path;
 	String atlas_data;
 	String atlas_data;
@@ -69,11 +69,20 @@ public:
 
 
 	Error save_to_file(const String &path);// .spatlas
 	Error save_to_file(const String &path);// .spatlas
 
 
+#if VERSION_MAJOR > 3
+	virtual Error copy_from(const Ref<Resource> &p_resource);
+#endif
+
 	String get_source_path();
 	String get_source_path();
 
 
 	Array get_textures();
 	Array get_textures();
 
 
 	Array get_normal_maps();
 	Array get_normal_maps();
+
+	void clear_native_data() const {
+		this->atlas = nullptr;
+		this->texture_loader = nullptr;
+	}
 };
 };
 
 
 class SpineAtlasResourceFormatLoader : public ResourceFormatLoader {
 class SpineAtlasResourceFormatLoader : public ResourceFormatLoader {

+ 19 - 0
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_images_path"), &SpineSkeletonDataResource::get_images_path);
 	ClassDB::bind_method(D_METHOD("get_audio_path"), &SpineSkeletonDataResource::get_audio_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("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("skeleton_data_changed"));
 	ADD_SIGNAL(MethodInfo("_internal_spine_objects_invalidated"));
 	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<SpineAtlasResource> &atlas) {
 void SpineSkeletonDataResource::set_atlas_res(const Ref<SpineAtlasResource> &atlas) {
 	atlas_res = 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();
 	update_skeleton_data();
 }
 }
 
 
@@ -199,6 +209,15 @@ Ref<SpineAtlasResource> SpineSkeletonDataResource::get_atlas_res() {
 
 
 void SpineSkeletonDataResource::set_skeleton_file_res(const Ref<SpineSkeletonFileResource> &skeleton_file) {
 void SpineSkeletonDataResource::set_skeleton_file_res(const Ref<SpineSkeletonFileResource> &skeleton_file) {
 	skeleton_file_res = 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();
 	update_skeleton_data();
 }
 }
 
 

+ 17 - 0
spine-godot/spine_godot/SpineSkeletonFileResource.cpp

@@ -29,8 +29,12 @@
 
 
 #include "SpineSkeletonFileResource.h"
 #include "SpineSkeletonFileResource.h"
 #if VERSION_MAJOR > 3
 #if VERSION_MAJOR > 3
+#include "core/error/error_list.h"
+#include "core/error/error_macros.h"
 #include "core/io/file_access.h"
 #include "core/io/file_access.h"
 #else
 #else
+#include "core/error_list.h"
+#include "core/error_macros.h"
 #include "core/os/file_access.h"
 #include "core/os/file_access.h"
 #endif
 #endif
 #include <spine/Json.h>
 #include <spine/Json.h>
@@ -85,6 +89,7 @@ static char *readString(BinaryInput *input) {
 }
 }
 
 
 void SpineSkeletonFileResource::_bind_methods() {
 void SpineSkeletonFileResource::_bind_methods() {
+	ADD_SIGNAL(MethodInfo("skeleton_file_changed"));
 }
 }
 
 
 static bool checkVersion(const char *version) {
 static bool checkVersion(const char *version) {
@@ -157,6 +162,18 @@ Error SpineSkeletonFileResource::save_to_file(const String &path) {
 	return OK;
 	return OK;
 }
 }
 
 
+#if VERSION_MAJOR > 3
+Error SpineSkeletonFileResource::copy_from(const Ref<Resource> &p_resource) {
+	auto error = Resource::copy_from(p_resource);
+	if (error != OK) return error;
+	const Ref<SpineSkeletonFileResource> &spineFile = static_cast<const Ref<SpineSkeletonFileResource> &>(p_resource);
+	this->json = spineFile->json;
+	this->binary = spineFile->binary;
+	emit_signal(SNAME("skeleton_file_changed"));
+	return OK;
+}
+#endif
+
 #if VERSION_MAJOR > 3
 #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) {
 RES SpineSkeletonFileResourceFormatLoader::load(const String &path, const String &original_path, Error *error, bool use_sub_threads, float *progress, CacheMode cache_mode) {
 #else
 #else

+ 4 - 0
spine-godot/spine_godot/SpineSkeletonFileResource.h

@@ -52,6 +52,10 @@ public:
 	Error load_from_file(const String &path);
 	Error load_from_file(const String &path);
 
 
 	Error save_to_file(const String &path);
 	Error save_to_file(const String &path);
+
+#if VERSION_MAJOR > 3
+	virtual Error copy_from(const Ref<Resource> &p_resource);
+#endif
 };
 };
 
 
 class SpineSkeletonFileResourceFormatLoader : public ResourceFormatLoader {
 class SpineSkeletonFileResourceFormatLoader : public ResourceFormatLoader {

+ 2 - 2
spine-ts/spine-webgl/src/SpineCanvas.ts

@@ -87,7 +87,7 @@ export class SpineCanvas {
 			update: () => { },
 			update: () => { },
 			render: () => { },
 			render: () => { },
 			error: () => { },
 			error: () => { },
-			dispose: () => { },
+			dispose: () => { },
 		}
 		}
 		if (config.webglConfig) config.webglConfig = { alpha: true };
 		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.*/
 	/** 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);
 		if (this.config.app.dispose) this.config.app.dispose(this);
 		this.disposed = true;
 		this.disposed = true;
 	}
 	}

+ 7 - 6
spine-ue4/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineAtlasImportFactory.cpp

@@ -28,11 +28,9 @@
  *****************************************************************************/
  *****************************************************************************/
 
 
 #include "SpineAtlasImportFactory.h"
 #include "SpineAtlasImportFactory.h"
-#include "AssetRegistryModule.h"
 #include "AssetToolsModule.h"
 #include "AssetToolsModule.h"
-#include "Developer/AssetTools/Public/IAssetTools.h"
-#include "PackageTools.h"
 #include "SpineAtlasAsset.h"
 #include "SpineAtlasAsset.h"
+#include "Editor.h"
 
 
 #define LOCTEXT_NAMESPACE "Spine"
 #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) {
 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<UImportSubsystem>()->BroadcastAssetPreImport(this, InClass, InParent, InName, *FileExtension);
+
 	FString rawString;
 	FString rawString;
 	if (!FFileHelper::LoadFileToString(rawString, *Filename)) {
 	if (!FFileHelper::LoadFileToString(rawString, *Filename)) {
 		return nullptr;
 		return nullptr;
@@ -64,13 +65,12 @@ UObject *USpineAtlasAssetFactory::FactoryCreateFile(UClass *InClass, UObject *In
 	FString currentSourcePath, filenameNoExtension, unusedExtension;
 	FString currentSourcePath, filenameNoExtension, unusedExtension;
 	const FString longPackagePath = FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetPathName());
 	const FString longPackagePath = FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetPathName());
 	FPaths::Split(UFactory::GetCurrentFilename(), currentSourcePath, filenameNoExtension, unusedExtension);
 	FPaths::Split(UFactory::GetCurrentFilename(), currentSourcePath, filenameNoExtension, unusedExtension);
-	FString name(InName.ToString());
-	name.Append("-atlas");
 
 
-	USpineAtlasAsset *asset = NewObject<USpineAtlasAsset>(InParent, InClass, FName(*name), Flags);
+	USpineAtlasAsset *asset = NewObject<USpineAtlasAsset>(InParent, InClass, InName, Flags);
 	asset->SetRawData(rawString);
 	asset->SetRawData(rawString);
 	asset->SetAtlasFileName(FName(*Filename));
 	asset->SetAtlasFileName(FName(*Filename));
 	LoadAtlas(asset, currentSourcePath, longPackagePath);
 	LoadAtlas(asset, currentSourcePath, longPackagePath);
+	GEditor->GetEditorSubsystem<UImportSubsystem>()->BroadcastAssetPostImport(this, asset);
 	return asset;
 	return asset;
 }
 }
 
 
@@ -109,6 +109,7 @@ EReimportResult::Type USpineAtlasAssetFactory::Reimport(UObject *Obj) {
 	else
 	else
 		Obj->MarkPackageDirty();
 		Obj->MarkPackageDirty();
 
 
+	GEditor->GetEditorSubsystem<UImportSubsystem>()->BroadcastAssetReimport(asset);
 	return EReimportResult::Succeeded;
 	return EReimportResult::Succeeded;
 }
 }
 
 

+ 28 - 0
spine-ue4/Plugins/SpinePlugin/Source/SpineEditorPlugin/Private/SpineEditorPlugin.cpp

@@ -28,16 +28,44 @@
  *****************************************************************************/
  *****************************************************************************/
 
 
 #include "SpineEditorPlugin.h"
 #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 {
 class FSpineEditorPlugin : public ISpineEditorPlugin {
 	virtual void StartupModule() override;
 	virtual void StartupModule() override;
 	virtual void ShutdownModule() override;
 	virtual void ShutdownModule() override;
+	TSharedPtr<FSpineAtlasAssetTypeActions> SpineAtlasAssetTypeActions;
+	TSharedPtr<FSpineSkeletonDataAssetTypeActions> SpineSkeletonDataAssetTypeActions;
 };
 };
 
 
 IMPLEMENT_MODULE(FSpineEditorPlugin, SpineEditorPlugin)
 IMPLEMENT_MODULE(FSpineEditorPlugin, SpineEditorPlugin)
 
 
 void FSpineEditorPlugin::StartupModule() {
 void FSpineEditorPlugin::StartupModule() {
+	SpineAtlasAssetTypeActions = MakeShared<FSpineAtlasAssetTypeActions>();
+	FAssetToolsModule::GetModule().Get().RegisterAssetTypeActions(SpineAtlasAssetTypeActions.ToSharedRef());
+	SpineSkeletonDataAssetTypeActions = MakeShared<FSpineSkeletonDataAssetTypeActions>();
+	FAssetToolsModule::GetModule().Get().RegisterAssetTypeActions(SpineSkeletonDataAssetTypeActions.ToSharedRef());
 }
 }
 
 
 void FSpineEditorPlugin::ShutdownModule() {
 void FSpineEditorPlugin::ShutdownModule() {
+	if (!FModuleManager::Get().IsModuleLoaded("AssetTools")) return;
+	FAssetToolsModule::GetModule().Get().UnregisterAssetTypeActions(SpineAtlasAssetTypeActions.ToSharedRef());
+	FAssetToolsModule::GetModule().Get().UnregisterAssetTypeActions(SpineSkeletonDataAssetTypeActions.ToSharedRef());
 }
 }

+ 1 - 4
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) {
 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<USpineSkeletonDataAsset>(InParent, InClass, FName(*name), Flags);
+	USpineSkeletonDataAsset *asset = NewObject<USpineSkeletonDataAsset>(InParent, InClass, InName, Flags);
 	TArray<uint8> rawData;
 	TArray<uint8> rawData;
 	if (!FFileHelper::LoadFileToArray(rawData, *Filename, 0)) {
 	if (!FFileHelper::LoadFileToArray(rawData, *Filename, 0)) {
 		return nullptr;
 		return nullptr;

+ 0 - 4
spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Private/SpineAtlasAsset.cpp

@@ -53,10 +53,6 @@ void USpineAtlasAsset::PostInitProperties() {
 }
 }
 
 
 void USpineAtlasAsset::GetAssetRegistryTags(TArray<FAssetRegistryTag> &OutTags) const {
 void USpineAtlasAsset::GetAssetRegistryTags(TArray<FAssetRegistryTag> &OutTags) const {
-	if (importData) {
-		OutTags.Add(FAssetRegistryTag(SourceFileTagName(), importData->GetSourceData().ToJson(), FAssetRegistryTag::TT_Hidden));
-	}
-
 	Super::GetAssetRegistryTags(OutTags);
 	Super::GetAssetRegistryTags(OutTags);
 }
 }
 
 

+ 1 - 1
spine-ue4/Plugins/SpinePlugin/Source/SpinePlugin/Public/SpineSkeletonDataAsset.h

@@ -111,7 +111,7 @@ public:
 
 
 protected:
 protected:
 	UPROPERTY(VisibleAnywhere, Instanced, Category = ImportSettings)
 	UPROPERTY(VisibleAnywhere, Instanced, Category = ImportSettings)
-	class UAssetImportData *importData;
+	class UAssetImportData *importData = nullptr;
 
 
 	virtual void PostInitProperties() override;
 	virtual void PostInitProperties() override;
 	virtual void GetAssetRegistryTags(TArray<FAssetRegistryTag> &OutTags) const override;
 	virtual void GetAssetRegistryTags(TArray<FAssetRegistryTag> &OutTags) const override;

+ 2 - 2
spine-ue4/README.md

@@ -1,5 +1,5 @@
 # spine-ue4
 # 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
 ## Licensing
 
 
@@ -34,7 +34,7 @@ See the [Spine Runtimes documentation](http://esotericsoftware.com/spine-documen
 ## Example
 ## Example
 ### [Please see the spine-ue4 guide for full documentation](http://esotericsoftware.com/spine-ue4)
 ### [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.
 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
 2. Open the SpineUE4.uproject file with Unreal Editor

+ 1 - 1
spine-ue4/SpineUE4.uproject

@@ -1,6 +1,6 @@
 {
 {
 	"FileVersion": 3,
 	"FileVersion": 3,
-	"EngineAssociation": "5.2",
+	"EngineAssociation": "5.3",
 	"Category": "",
 	"Category": "",
 	"Description": "",
 	"Description": "",
 	"Modules": [
 	"Modules": [

+ 5 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/AtlasAssetBase.cs

@@ -71,6 +71,11 @@ namespace Spine.Unity {
 				onDemandTextureLoader.RequestLoadMaterialTextures(material, ref overrideMaterial);
 				onDemandTextureLoader.RequestLoadMaterialTextures(material, ref overrideMaterial);
 		}
 		}
 
 
+		public virtual void RequireTextureLoaded (Texture placeholderTexture, ref Texture replacementTexture, System.Action<Texture> onTextureLoaded) {
+			if (onDemandTextureLoader)
+				onDemandTextureLoader.RequestLoadTexture(placeholderTexture, ref replacementTexture, onTextureLoaded);
+		}
+
 		[SerializeField] protected LoadingMode textureLoadingMode = LoadingMode.Normal;
 		[SerializeField] protected LoadingMode textureLoadingMode = LoadingMode.Normal;
 		[SerializeField] protected OnDemandTextureLoader onDemandTextureLoader = null;
 		[SerializeField] protected OnDemandTextureLoader onDemandTextureLoader = null;
 #endif
 #endif

+ 32 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/OnDemandTextureLoader.cs

@@ -51,6 +51,27 @@ namespace Spine.Unity {
 		/// <param name="placeholderMaterials">A newly created list of materials which has a placeholder texture assigned.</param>
 		/// <param name="placeholderMaterials">A newly created list of materials which has a placeholder texture assigned.</param>
 		/// <returns>True, if any placeholder texture is assigned at a Material of the associated AtlasAssetBase.</returns>
 		/// <returns>True, if any placeholder texture is assigned at a Material of the associated AtlasAssetBase.</returns>
 		public abstract bool HasPlaceholderTexturesAssigned (out List<Material> placeholderMaterials);
 		public abstract bool HasPlaceholderTexturesAssigned (out List<Material> placeholderMaterials);
+
+		/// <summary>
+		/// Returns whether any main texture is null at a Material of the associated AtlasAssetBase.
+		/// </summary>
+		/// <param name="nullTextureMaterials">A newly created list of materials which has a null main texture assigned.</param>
+		/// <returns>True, if any null main texture is assigned at a Material of the associated AtlasAssetBase.</returns>
+		public virtual bool HasNullMainTexturesAssigned (out List<Material> 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<Material>();
+					nullTextureMaterials.Add(material);
+				}
+			}
+			return anyNullTexture;
+		}
+
 		/// <summary>
 		/// <summary>
 		/// Assigns previously setup target textures at each Material where placeholder textures are setup.</summary>
 		/// Assigns previously setup target textures at each Material where placeholder textures are setup.</summary>
 		/// <returns>True on success, false if the target texture could not be assigned at any of the
 		/// <returns>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 void EndCustomTextureLoading ();
 		public abstract bool HasPlaceholderAssigned (Material material);
 		public abstract bool HasPlaceholderAssigned (Material material);
 		public abstract void RequestLoadMaterialTextures (Material material, ref Material overrideMaterial);
 		public abstract void RequestLoadMaterialTextures (Material material, ref Material overrideMaterial);
+		public abstract void RequestLoadTexture (Texture placeholderTexture, ref Texture replacementTexture,
+			System.Action<Texture> onTextureLoaded = null);
 		public abstract void Clear (bool clearAtlasAsset = false);
 		public abstract void Clear (bool clearAtlasAsset = false);
 
 
 		#region Event delegates
 		#region Event delegates
 		public delegate void TextureLoadDelegate (OnDemandTextureLoader loader, Material material, int textureIndex);
 		public delegate void TextureLoadDelegate (OnDemandTextureLoader loader, Material material, int textureIndex);
+		protected event TextureLoadDelegate onTextureRequested;
 		protected event TextureLoadDelegate onTextureLoaded;
 		protected event TextureLoadDelegate onTextureLoaded;
 		protected event TextureLoadDelegate onTextureUnloaded;
 		protected event TextureLoadDelegate onTextureUnloaded;
 
 
+		public event TextureLoadDelegate TextureRequested {
+			add { onTextureRequested += value; }
+			remove { onTextureRequested -= value; }
+		}
 		public event TextureLoadDelegate TextureLoaded {
 		public event TextureLoadDelegate TextureLoaded {
 			add { onTextureLoaded += value; }
 			add { onTextureLoaded += value; }
 			remove { onTextureLoaded -= value; }
 			remove { onTextureLoaded -= value; }
@@ -76,6 +104,10 @@ namespace Spine.Unity {
 			remove { onTextureUnloaded -= value; }
 			remove { onTextureUnloaded -= value; }
 		}
 		}
 
 
+		protected void OnTextureRequested (Material material, int textureIndex) {
+			if (onTextureRequested != null)
+				onTextureRequested(this, material, textureIndex);
+		}
 		protected void OnTextureLoaded (Material material, int textureIndex) {
 		protected void OnTextureLoaded (Material material, int textureIndex) {
 			if (onTextureLoaded != null)
 			if (onTextureLoaded != null)
 				onTextureLoaded(this, material, textureIndex);
 				onTextureLoaded(this, material, textureIndex);

+ 4 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonAnimation.cs

@@ -273,8 +273,12 @@ namespace Spine.Unity {
 		}
 		}
 
 
 		public override void LateUpdate () {
 		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.
 			// instantiation can happen from Update() after this component, leading to a missing Update() call.
 			if (!wasUpdatedAfterInit) Update(0);
 			if (!wasUpdatedAfterInit) Update(0);
+
 			base.LateUpdate();
 			base.LateUpdate();
 		}
 		}
 
 

+ 52 - 3
spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs

@@ -35,6 +35,8 @@
 #define HAS_CULL_TRANSPARENT_MESH
 #define HAS_CULL_TRANSPARENT_MESH
 #endif
 #endif
 
 
+#define SPINE_OPTIONAL_ON_DEMAND_LOADING
+
 using System.Collections.Generic;
 using System.Collections.Generic;
 using UnityEngine;
 using UnityEngine;
 using UnityEngine.UI;
 using UnityEngine.UI;
@@ -415,6 +417,9 @@ namespace Spine.Unity {
 			if (freeze) return;
 			if (freeze) return;
 			if (updateMode != UpdateMode.FullUpdate) return;
 			if (updateMode != UpdateMode.FullUpdate) return;
 
 
+			if (updateTiming == UpdateTiming.InLateUpdate)
+				Update(unscaledTime ? Time.unscaledDeltaTime : Time.deltaTime);
+
 			PrepareInstructionsAndRenderers();
 			PrepareInstructionsAndRenderers();
 
 
 			SetVerticesDirty(); // triggers Rebuild and avoids potential double-update in a single frame
 			SetVerticesDirty(); // triggers Rebuild and avoids potential double-update in a single frame
@@ -818,14 +823,22 @@ namespace Spine.Unity {
 			else
 			else
 				canvasRenderer.SetMesh(null);
 				canvasRenderer.SetMesh(null);
 
 
+			bool assignTexture = false;
 			if (currentInstructions.submeshInstructions.Count > 0) {
 			if (currentInstructions.submeshInstructions.Count > 0) {
 				Material material = currentInstructions.submeshInstructions.Items[0].material;
 				Material material = currentInstructions.submeshInstructions.Items[0].material;
 				if (material != null && baseTexture != material.mainTexture) {
 				if (material != null && baseTexture != material.mainTexture) {
 					baseTexture = material.mainTexture;
 					baseTexture = material.mainTexture;
 					if (overrideTexture == null && assignAtCanvasRenderer)
 					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) {
 		protected void UpdateMaterialsMultipleCanvasRenderers (SkeletonRendererInstruction currentInstructions) {
@@ -896,7 +909,6 @@ namespace Spine.Unity {
 			bool pmaVertexColors = meshGenerator.settings.pmaVertexColors;
 			bool pmaVertexColors = meshGenerator.settings.pmaVertexColors;
 			Material[] usedMaterialItems = usedMaterials.Items;
 			Material[] usedMaterialItems = usedMaterials.Items;
 			Texture[] usedTextureItems = usedTextures.Items;
 			Texture[] usedTextureItems = usedTextures.Items;
-			bool assignAtCanvasRenderer = (assignMeshOverrideSingle == null || !disableMeshAssignmentOnOverride);
 			for (int i = 0; i < submeshCount; i++) {
 			for (int i = 0; i < submeshCount; i++) {
 				SubmeshInstruction submeshInstructionItem = currentInstructions.submeshInstructions.Items[i];
 				SubmeshInstruction submeshInstructionItem = currentInstructions.submeshInstructions.Items[i];
 				meshGenerator.Begin();
 				meshGenerator.Begin();
@@ -929,13 +941,50 @@ namespace Spine.Unity {
 #endif
 #endif
 				}
 				}
 				canvasRenderer.materialCount = 1;
 				canvasRenderer.materialCount = 1;
-				if (assignAtCanvasRenderer)
+			}
+
+#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]);
 					canvasRenderer.SetMaterial(usedMaterialItems[i], usedTextureItems[i]);
+				}
 			}
 			}
+
 			if (assignMeshOverrideMultiple != null)
 			if (assignMeshOverrideMultiple != null)
 				assignMeshOverrideMultiple(submeshCount, meshesItems, usedMaterialItems, usedTextureItems);
 				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) {
 		protected void EnsureCanvasRendererCount (int targetCount) {
 #if UNITY_EDITOR
 #if UNITY_EDITOR
 			RemoveNullCanvasRenderers();
 			RemoveNullCanvasRenderers();

+ 9 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs

@@ -109,6 +109,13 @@ namespace Spine.Unity {
 			UpdateAnimation();
 			UpdateAnimation();
 		}
 		}
 
 
+		/// <summary>Manual animation update. Required when <c>updateTiming</c> is set to <c>ManualUpdate</c>.</summary>
+		/// <param name="deltaTime">Ignored parameter.</param>
+		public virtual void Update (float deltaTime) {
+			if (!valid) return;
+			UpdateAnimation();
+		}
+
 		protected void UpdateAnimation () {
 		protected void UpdateAnimation () {
 			wasUpdatedAfterInit = true;
 			wasUpdatedAfterInit = true;
 
 
@@ -160,6 +167,8 @@ namespace Spine.Unity {
 		}
 		}
 
 
 		public override void LateUpdate () {
 		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.
 			// instantiation can happen from Update() after this component, leading to a missing Update() call.
 			if (!wasUpdatedAfterInit) Update();
 			if (!wasUpdatedAfterInit) Update();
 			base.LateUpdate();
 			base.LateUpdate();

+ 2 - 1
spine-unity/Assets/Spine/Runtime/spine-unity/ISkeletonAnimation.cs

@@ -40,7 +40,8 @@ namespace Spine.Unity {
 	public enum UpdateTiming {
 	public enum UpdateTiming {
 		ManualUpdate = 0,
 		ManualUpdate = 0,
 		InUpdate,
 		InUpdate,
-		InFixedUpdate
+		InFixedUpdate,
+		InLateUpdate
 	}
 	}
 
 
 	public delegate void ISkeletonAnimationDelegate (ISkeletonAnimation animated);
 	public delegate void ISkeletonAnimationDelegate (ISkeletonAnimation animated);

+ 1 - 1
spine-unity/Assets/Spine/package.json

@@ -2,7 +2,7 @@
 	"name": "com.esotericsoftware.spine.spine-unity",
 	"name": "com.esotericsoftware.spine.spine-unity",
 	"displayName": "spine-unity Runtime",
 	"displayName": "spine-unity Runtime",
 	"description": "This plugin provides the spine-unity runtime core.",
 	"description": "This plugin provides the spine-unity runtime core.",
-	"version": "4.1.25",
+	"version": "4.1.29",
 	"unity": "2018.3",
 	"unity": "2018.3",
 	"author": {
 	"author": {
 		"name": "Esoteric Software",
 		"name": "Esoteric Software",

+ 2 - 1
spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor/AddressablesTextureLoaderInspector.cs

@@ -47,9 +47,10 @@ namespace Spine.Unity.Editor {
 
 
 	[CustomEditor(typeof(AddressablesTextureLoader)), CanEditMultipleObjects]
 	[CustomEditor(typeof(AddressablesTextureLoader)), CanEditMultipleObjects]
 	public class AddressablesTextureLoaderInspector : GenericTextureLoaderInspector {
 	public class AddressablesTextureLoaderInspector : GenericTextureLoaderInspector {
-		public string LoaderSuffix { get { return "_Addressable"; } }
 
 
 		public class AddressablesMethodImplementations : StaticMethodImplementations {
 		public class AddressablesMethodImplementations : StaticMethodImplementations {
+			public override string LoaderSuffix { get { return "_Addressable"; } }
+
 			public override GenericTextureLoader GetOrCreateLoader (string loaderPath) {
 			public override GenericTextureLoader GetOrCreateLoader (string loaderPath) {
 				AddressablesTextureLoader loader = AssetDatabase.LoadAssetAtPath<AddressablesTextureLoader>(loaderPath);
 				AddressablesTextureLoader loader = AssetDatabase.LoadAssetAtPath<AddressablesTextureLoader>(loaderPath);
 				if (loader == null) {
 				if (loader == null) {

+ 6 - 2
spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime/AddressablesTextureLoader.cs

@@ -76,13 +76,17 @@ namespace Spine.Unity {
 	[System.Serializable]
 	[System.Serializable]
 	public class AddressablesTextureLoader : GenericOnDemandTextureLoader<AddressableTextureReference, AddressableRequest> {
 	public class AddressablesTextureLoader : GenericOnDemandTextureLoader<AddressableTextureReference, AddressableRequest> {
 		public override void CreateTextureRequest (AddressableTextureReference targetReference,
 		public override void CreateTextureRequest (AddressableTextureReference targetReference,
-			MaterialOnDemandData materialData, int textureIndex, Material materialToUpdate) {
+			MaterialOnDemandData materialData, int textureIndex, Material materialToUpdate,
+			System.Action<Texture> onTextureLoaded) {
 
 
+			OnTextureRequested(materialToUpdate, textureIndex);
 			materialData.textureRequests[textureIndex].handle = targetReference.assetReference.LoadAssetAsync<Texture>();
 			materialData.textureRequests[textureIndex].handle = targetReference.assetReference.LoadAssetAsync<Texture>();
 			materialData.textureRequests[textureIndex].handle.Completed += (obj) => {
 			materialData.textureRequests[textureIndex].handle.Completed += (obj) => {
 				if (obj.Status == AsyncOperationStatus.Succeeded) {
 				if (obj.Status == AsyncOperationStatus.Succeeded) {
-					materialToUpdate.mainTexture = (Texture)targetReference.assetReference.Asset;
+					Texture loadedTexture = (Texture)targetReference.assetReference.Asset;
+					materialToUpdate.mainTexture = loadedTexture;
 					OnTextureLoaded(materialToUpdate, textureIndex);
 					OnTextureLoaded(materialToUpdate, textureIndex);
+					if (onTextureLoaded != null) onTextureLoaded(loadedTexture);
 				}
 				}
 			};
 			};
 		}
 		}

+ 1 - 1
spine-unity/Modules/com.esotericsoftware.spine.addressables/package.json

@@ -2,7 +2,7 @@
 	"name": "com.esotericsoftware.spine.addressables",
 	"name": "com.esotericsoftware.spine.addressables",
 	"displayName": "Spine Addressables Extensions [Experimental]",
 	"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)",
 	"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",
 	"unity": "2018.3",
 	"author": {
 	"author": {
 		"name": "Esoteric Software",
 		"name": "Esoteric Software",

+ 16 - 6
spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor/GenericOnDemandTextureLoaderInspector.cs

@@ -37,6 +37,7 @@
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Linq;
 using UnityEditor;
 using UnityEditor;
 using UnityEngine;
 using UnityEngine;
 
 
@@ -92,7 +93,7 @@ namespace Spine.Unity.Editor {
 			/// When set to e.g. "_Addressable", the loader asset created for
 			/// When set to e.g. "_Addressable", the loader asset created for
 			/// the "Skeleton_Atlas" asset is named "Skeleton_Addressable".
 			/// the "Skeleton_Atlas" asset is named "Skeleton_Addressable".
 			/// </summary>
 			/// </summary>
-			public string LoaderSuffix { get; }
+			public virtual string LoaderSuffix { get { return "_Loader"; } }
 
 
 			public abstract bool SetupOnDemandLoadingReference (
 			public abstract bool SetupOnDemandLoadingReference (
 				ref TargetReference targetTextureReference, Texture targetTexture);
 				ref TargetReference targetTextureReference, Texture targetTexture);
@@ -237,7 +238,7 @@ namespace Spine.Unity.Editor {
 
 
 #if NEWPLAYMODECALLBACKS
 #if NEWPLAYMODECALLBACKS
 		static void OnPlaymodeChanged (PlayModeStateChange mode) {
 		static void OnPlaymodeChanged (PlayModeStateChange mode) {
-			bool assignTargetTextures = mode == PlayModeStateChange.ExitingPlayMode;
+			bool assignTargetTextures = mode == PlayModeStateChange.EnteredEditMode;
 #else
 #else
 		static void OnPlaymodeChanged () {
 		static void OnPlaymodeChanged () {
 			bool assignTargetTextures = !Application.isPlaying;
 			bool assignTargetTextures = !Application.isPlaying;
@@ -259,14 +260,23 @@ namespace Spine.Unity.Editor {
 
 
 		public static void AssignTargetTexturesAtLoader (OnDemandTextureLoader loader) {
 		public static void AssignTargetTexturesAtLoader (OnDemandTextureLoader loader) {
 			List<Material> placeholderMaterials;
 			List<Material> placeholderMaterials;
+			List<Material> nullTextureMaterials;
 			bool anyPlaceholdersAssigned = loader.HasPlaceholderTexturesAssigned(out placeholderMaterials);
 			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();
 				AssetDatabase.StartAssetEditing();
 				IEnumerable<Material> modifiedMaterials;
 				IEnumerable<Material> modifiedMaterials;
 				loader.AssignTargetTextures(out 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.StopAssetEditing();
 				AssetDatabase.SaveAssets();
 				AssetDatabase.SaveAssets();

+ 46 - 10
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<Material>();
 					if (placeholderMaterials == null) placeholderMaterials = new List<Material>();
 					placeholderMaterials.Add(material);
 					placeholderMaterials.Add(material);
 				}
 				}
+				materialIndex++;
 			}
 			}
 			return anyPlaceholderAssigned;
 			return anyPlaceholderAssigned;
 		}
 		}
@@ -180,8 +181,7 @@ namespace Spine.Unity {
 						atlasAsset, i + 1, targetMaterial), this);
 						atlasAsset, i + 1, targetMaterial), this);
 					return false;
 					return false;
 				}
 				}
-				Material ignoredArgument = null;
-				RequestLoadMaterialTextures(targetMaterial, ref ignoredArgument);
+				AssignTargetTextures(targetMaterial, i);
 				++i;
 				++i;
 			}
 			}
 			modifiedMaterials = atlasAsset.Materials;
 			modifiedMaterials = atlasAsset.Materials;
@@ -223,7 +223,7 @@ namespace Spine.Unity {
 
 
 			int foundMaterialIndex = Array.FindIndex(placeholderMap, entry => entry.textures[textureIndex].placeholderTexture == currentTexture);
 			int foundMaterialIndex = Array.FindIndex(placeholderMap, entry => entry.textures[textureIndex].placeholderTexture == currentTexture);
 			if (foundMaterialIndex >= 0)
 			if (foundMaterialIndex >= 0)
-				RequestLoadTexture(material, foundMaterialIndex, textureIndex);
+				RequestLoadTexture(material, foundMaterialIndex, textureIndex, null);
 
 
 			int loadedMaterialIndex = Array.FindIndex(loadedDataAtMaterial, entry =>
 			int loadedMaterialIndex = Array.FindIndex(loadedDataAtMaterial, entry =>
 				entry.textureRequests[textureIndex].WasRequested &&
 				entry.textureRequests[textureIndex].WasRequested &&
@@ -232,33 +232,69 @@ namespace Spine.Unity {
 				loadedDataAtMaterial[loadedMaterialIndex].lastFrameRequested = Time.frameCount;
 				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<Texture> 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<Texture> onTextureLoaded) {
+
 			PlaceholderTextureMapping[] placeholderTextures = placeholderMap[materialIndex].textures;
 			PlaceholderTextureMapping[] placeholderTextures = placeholderMap[materialIndex].textures;
 			TargetReference targetReference = placeholderTextures[textureIndex].targetTextureReference;
 			TargetReference targetReference = placeholderTextures[textureIndex].targetTextureReference;
 			loadedDataAtMaterial[materialIndex].lastFrameRequested = Time.frameCount;
 			loadedDataAtMaterial[materialIndex].lastFrameRequested = Time.frameCount;
 
 
 #if UNITY_EDITOR
 #if UNITY_EDITOR
 			if (!Application.isPlaying) {
 			if (!Application.isPlaying) {
-				if (targetReference.EditorTexture != null)
+				if (targetReference.EditorTexture != null) {
 					material.mainTexture = targetReference.EditorTexture;
 					material.mainTexture = targetReference.EditorTexture;
-				return;
+					if (onTextureLoaded != null) onTextureLoaded(targetReference.EditorTexture);
+				}
+				return targetReference.EditorTexture;
 			}
 			}
 #endif
 #endif
 			MaterialOnDemandData materialData = loadedDataAtMaterial[materialIndex];
 			MaterialOnDemandData materialData = loadedDataAtMaterial[materialIndex];
 			if (materialData.textureRequests[textureIndex].WasRequested) {
 			if (materialData.textureRequests[textureIndex].WasRequested) {
 				Texture loadedTexture = GetAlreadyLoadedTexture(materialIndex, textureIndex);
 				Texture loadedTexture = GetAlreadyLoadedTexture(materialIndex, textureIndex);
-				if (loadedTexture != null)
+				if (loadedTexture != null) {
 					material.mainTexture = loadedTexture;
 					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 Texture GetAlreadyLoadedTexture (int materialIndex, int textureIndex);
 
 
 		public abstract void CreateTextureRequest (TargetReference targetReference,
 		public abstract void CreateTextureRequest (TargetReference targetReference,
-			MaterialOnDemandData materialData, int textureIndex, Material materialToUpdate);
+			MaterialOnDemandData materialData, int textureIndex, Material materialToUpdate,
+			System.Action<Texture> onTextureLoaded);
 
 
 		public virtual void UnloadUnusedTextures () {
 		public virtual void UnloadUnusedTextures () {
 			int currentFrameCount = Time.frameCount;
 			int currentFrameCount = Time.frameCount;

+ 1 - 1
spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/package.json

@@ -2,7 +2,7 @@
 	"name": "com.esotericsoftware.spine.on-demand-loading",
 	"name": "com.esotericsoftware.spine.on-demand-loading",
 	"displayName": "Spine On-Demand Loading Extensions [Experimental]",
 	"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)",
 	"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",
 	"unity": "2018.3",
 	"author": {
 	"author": {
 		"name": "Esoteric Software",
 		"name": "Esoteric Software",