Эх сурвалжийг харах

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

badlogic 1 жил өмнө
parent
commit
1c9b263955
44 өөрчлөгдсөн 1458 нэмэгдсэн , 79 устгасан
  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:
           [
             {"tag": "4.0.4-stable", "version": "4.0.4.stable", "mono": false},
-            {"tag": "4.1.1-stable", "version": "4.1.1.stable", "mono": false},
-            {"tag": "4.1.1-stable", "version": "4.1.1.stable", "mono": true},
+            {"tag": "4.1.2-stable", "version": "4.1.2.stable", "mono": false},
+            {"tag": "4.1.2-stable", "version": "4.1.2.stable", "mono": true},
           ]
     uses: ./.github/workflows/spine-godot-v4.yml
     with:

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

@@ -12,8 +12,8 @@ env:
   AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
   AWS_EC2_METADATA_DISABLED: true
   EM_VERSION: 3.1.14
-  GODOT_TAG: 3.5.2-stable
-  GODOT_VERSION: 3.5.2.stable
+  GODOT_TAG: 3.5.3-stable
+  GODOT_VERSION: 3.5.3.stable
 
 jobs:
   godot-editor-windows:

+ 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.
   * Timeline extension package: Added static `EditorEvent` callback to allow editor scripts to react to animation events outside of play-mode. Register to the events via `Spine.Unity.Playables.SpineAnimationStateMixerBehaviour.EditorEvent += YourCallback;`.
   * URP Shaders: Added `Depth Write` property to shaders `Universal Render Pipeline/Spine/Skeleton` and `Universal Render Pipeline/Spine/Skeleton Lit`. Defaults to false to maintain existing behaviour.
+  * Added `Animation Update` mode (called `UpdateTiming` in code) `In Late Update` for `SkeletonAnimation`, `SkeletonMecanim` and `SkeletonGraphic`. This allows you to update the `SkeletonMecanim` skeleton in the same frame that the Mecanim Animator updated its state, which happens between `Update` and `LateUpdate`.
 
 * **Breaking changes**
   * Made `SkeletonGraphic.unscaledTime` parameter protected, use the new property `UnscaledTime` instead.

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

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

+ 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
 * 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 */ = {
 			isa = PBXProject;
 			attributes = {
-				LastUpgradeCheck = 1300;
+				LastUpgradeCheck = 1430;
 				ORGANIZATIONNAME = "";
 				TargetAttributes = {
 					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
     // and create a SpineComponent from them, scaled down and
     // centered on the screen
-    spineboy = await SpineComponent.fromAssets("assets/spineboy.atlas", "assets/spineboy-pro.skel",
+    spineboy = await SpineComponent.fromAssets("assets/spineboy.atlas", "assets/spineboy-pro.json",
         scale: Vector2(0.4, 0.4), anchor: Anchor.center, position: Vector2(size.x / 2, size.y / 2));
 
     // Set the "walk" animation on track 0 in looping mode
@@ -129,6 +129,36 @@ class SimpleFlameExample extends FlameGame {
   }
 }
 
+class DragonExample extends FlameGame {
+  late final Atlas cachedAtlas;
+  late final SkeletonData cachedSkeletonData;
+  late final SpineComponent dragon;
+
+  @override
+  Future<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 {
   late final SkeletonData cachedSkeletonData;
   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')),
       body: SpineWidget.fromAsset(
         "assets/dragon.atlas",
-        "assets/dragon-ess.skel",
+        "assets/dragon-ess.json",
         controller,
         boundsProvider: SkinAndAnimationBounds(animation: "flying"),
       ),

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

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

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

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

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

+ 1 - 1
spine-flutter/pubspec.yaml

@@ -1,6 +1,6 @@
 name: spine_flutter
 description: The official Spine Flutter Runtime to load, display and interact with Spine animations.
-version: 4.1.5
+version: 4.1.7
 homepage: https://esotericsoftware.com
 repository: https://github.com/esotericsoftware/spine-runtimes
 issue_tracker: https://github.com/esotericsoftware/spine-runtimes/issues

+ 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);
 }
 
+_spine_bounds tmp_bounds;
 spine_bounds spine_skeleton_get_bounds(spine_skeleton skeleton) {
-	_spine_bounds *bounds = (_spine_bounds *) malloc(sizeof(_spine_bounds));
+	_spine_bounds *bounds = &tmp_bounds;
 	if (skeleton == nullptr) return (spine_bounds) bounds;
 	Skeleton *_skeleton = (Skeleton *) skeleton;
 	Vector<float> vertices;
@@ -2128,8 +2129,9 @@ void spine_bone_set_to_setup_pose(spine_bone bone) {
 	_bone->setToSetupPose();
 }
 
+_spine_vector tmp_vector;
 spine_vector spine_bone_world_to_local(spine_bone bone, float worldX, float worldY) {
-	_spine_vector *coords = SpineExtension::calloc<_spine_vector>(1, __FILE__, __LINE__);
+	_spine_vector *coords = &tmp_vector;
 	if (bone == nullptr) return (spine_vector) coords;
 	Bone *_bone = (Bone *) bone;
 	_bone->worldToLocal(worldX, worldY, coords->x, coords->y);
@@ -2137,7 +2139,7 @@ spine_vector spine_bone_world_to_local(spine_bone bone, float worldX, float worl
 }
 
 spine_vector spine_bone_local_to_world(spine_bone bone, float localX, float localY) {
-	_spine_vector *coords = SpineExtension::calloc<_spine_vector>(1, __FILE__, __LINE__);
+	_spine_vector *coords = &tmp_vector;
 	if (bone == nullptr) return (spine_vector) coords;
 	Bone *_bone = (Bone *) bone;
 	_bone->localToWorld(localX, localY, coords->x, coords->y);
@@ -2521,7 +2523,7 @@ void spine_attachment_dispose(spine_attachment attachment) {
 
 // PointAttachment
 spine_vector spine_point_attachment_compute_world_position(spine_point_attachment attachment, spine_bone bone) {
-	_spine_vector *result = SpineExtension::calloc<_spine_vector>(1, __FILE__, __LINE__);
+	_spine_vector *result = &tmp_vector;
 	if (attachment == nullptr) return (spine_vector) result;
 	PointAttachment *_attachment = (PointAttachment *) attachment;
 	_attachment->computeWorldPosition(*(Bone *) bone, result->x, result->y);

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

+ 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())
 		{
-			Console.WriteLine(entry.GetSlotIndex() + " " + entry.GetName());
+			GD.Print(entry.GetSlotIndex() + " " + entry.GetName());
 		}
 
 		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::ARRAY, "textures"), "", "get_textures");
 	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "normal_maps"), "", "get_normal_maps");
+
+	ADD_SIGNAL(MethodInfo("skeleton_atlas_changed"));
 }
 
 SpineAtlasResource::SpineAtlasResource() : atlas(nullptr), texture_loader(nullptr), normal_map_prefix("n") {
@@ -230,6 +232,28 @@ Error SpineAtlasResource::save_to_file(const String &path) {
 	return OK;
 }
 
+#if VERSION_MAJOR > 3
+Error SpineAtlasResource::copy_from(const Ref<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
 RES SpineAtlasResourceFormatLoader::load(const String &path, const String &original_path, Error *error, bool use_sub_threads, float *progress, CacheMode cache_mode) {
 #else

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

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

+ 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_audio_path"), &SpineSkeletonDataResource::get_audio_path);
 	ClassDB::bind_method(D_METHOD("get_fps"), &SpineSkeletonDataResource::get_fps);
+	ClassDB::bind_method(D_METHOD("update_skeleton_data"), &SpineSkeletonDataResource::update_skeleton_data);
 
 	ADD_SIGNAL(MethodInfo("skeleton_data_changed"));
 	ADD_SIGNAL(MethodInfo("_internal_spine_objects_invalidated"));
@@ -190,6 +191,15 @@ bool SpineSkeletonDataResource::is_skeleton_data_loaded() const {
 
 void SpineSkeletonDataResource::set_atlas_res(const Ref<SpineAtlasResource> &atlas) {
 	atlas_res = atlas;
+	if (atlas_res.is_valid()) {
+#if VERSION_MAJOR > 3
+		if (!atlas_res->is_connected(SNAME("skeleton_atlas_changed"), callable_mp(this, &SpineSkeletonDataResource::update_skeleton_data)))
+			atlas_res->connect(SNAME("skeleton_atlas_changed"), callable_mp(this, &SpineSkeletonDataResource::update_skeleton_data));
+#else
+		if (!atlas_res->is_connected(SNAME("skeleton_atlas_changed"), this, SNAME("update_skeleton_data")))
+			atlas_res->connect(SNAME("skeleton_atlas_changed"), this, SNAME("update_skeleton_data"));
+#endif
+	}
 	update_skeleton_data();
 }
 
@@ -199,6 +209,15 @@ Ref<SpineAtlasResource> SpineSkeletonDataResource::get_atlas_res() {
 
 void SpineSkeletonDataResource::set_skeleton_file_res(const Ref<SpineSkeletonFileResource> &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();
 }
 

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

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

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

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

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

@@ -87,7 +87,7 @@ export class SpineCanvas {
 			update: () => { },
 			render: () => { },
 			error: () => { },
-			dispose: () => { },
+			dispose: () => { },
 		}
 		if (config.webglConfig) config.webglConfig = { alpha: true };
 
@@ -131,7 +131,7 @@ export class SpineCanvas {
 	}
 
 	/** Disposes the app, so the update() and render() functions are no longer called. Calls the dispose() callback.*/
-	dispose() {
+	dispose () {
 		if (this.config.app.dispose) this.config.app.dispose(this);
 		this.disposed = true;
 	}

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

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

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

@@ -28,16 +28,44 @@
  *****************************************************************************/
 
 #include "SpineEditorPlugin.h"
+#include "AssetTypeActions_Base.h"
+#include "SpineAtlasAsset.h"
+#include "SpineSkeletonDataAsset.h"
+
+class FSpineAtlasAssetTypeActions : public FAssetTypeActions_Base {
+public:
+	UClass *GetSupportedClass() const override { return USpineAtlasAsset::StaticClass(); };
+	FText GetName() const override { return INVTEXT("Spine atlas asset"); };
+	FColor GetTypeColor() const override { return FColor::Red; };
+	uint32 GetCategories() override { return EAssetTypeCategories::Misc; };
+};
+
+class FSpineSkeletonDataAssetTypeActions : public FAssetTypeActions_Base {
+public:
+	UClass *GetSupportedClass() const override { return USpineSkeletonDataAsset::StaticClass(); };
+	FText GetName() const override { return INVTEXT("Spine data asset"); };
+	FColor GetTypeColor() const override { return FColor::Red; };
+	uint32 GetCategories() override { return EAssetTypeCategories::Misc; };
+};
 
 class FSpineEditorPlugin : public ISpineEditorPlugin {
 	virtual void StartupModule() override;
 	virtual void ShutdownModule() override;
+	TSharedPtr<FSpineAtlasAssetTypeActions> SpineAtlasAssetTypeActions;
+	TSharedPtr<FSpineSkeletonDataAssetTypeActions> SpineSkeletonDataAssetTypeActions;
 };
 
 IMPLEMENT_MODULE(FSpineEditorPlugin, SpineEditorPlugin)
 
 void FSpineEditorPlugin::StartupModule() {
+	SpineAtlasAssetTypeActions = MakeShared<FSpineAtlasAssetTypeActions>();
+	FAssetToolsModule::GetModule().Get().RegisterAssetTypeActions(SpineAtlasAssetTypeActions.ToSharedRef());
+	SpineSkeletonDataAssetTypeActions = MakeShared<FSpineSkeletonDataAssetTypeActions>();
+	FAssetToolsModule::GetModule().Get().RegisterAssetTypeActions(SpineSkeletonDataAssetTypeActions.ToSharedRef());
 }
 
 void FSpineEditorPlugin::ShutdownModule() {
+	if (!FModuleManager::Get().IsModuleLoaded("AssetTools")) return;
+	FAssetToolsModule::GetModule().Get().UnregisterAssetTypeActions(SpineAtlasAssetTypeActions.ToSharedRef());
+	FAssetToolsModule::GetModule().Get().UnregisterAssetTypeActions(SpineSkeletonDataAssetTypeActions.ToSharedRef());
 }

+ 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) {
-	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;
 	if (!FFileHelper::LoadFileToArray(rawData, *Filename, 0)) {
 		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 {
-	if (importData) {
-		OutTags.Add(FAssetRegistryTag(SourceFileTagName(), importData->GetSourceData().ToJson(), FAssetRegistryTag::TT_Hidden));
-	}
-
 	Super::GetAssetRegistryTags(OutTags);
 }
 

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

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

+ 2 - 2
spine-ue4/README.md

@@ -1,5 +1,5 @@
 # spine-ue4
-The spine-ue4 runtime provides functionality to load, manipulate and render [Spine](http://esotericsoftware.com) skeletal animation data using [Unreal Engine 4.21+](https://www.unrealengine.com/). spine-ue4 is based on [spine-cpp](../spine-cpp).
+The spine-ue4 runtime provides functionality to load, manipulate and render [Spine](http://esotericsoftware.com) skeletal animation data using [Unreal Engine 4.27-5.2](https://www.unrealengine.com/). spine-ue4 is based on [spine-cpp](../spine-cpp).
 
 ## Licensing
 
@@ -34,7 +34,7 @@ See the [Spine Runtimes documentation](http://esotericsoftware.com/spine-documen
 ## Example
 ### [Please see the spine-ue4 guide for full documentation](http://esotericsoftware.com/spine-ue4)
 
-The Spine UE4 example works on all platforms supported by Unreal Engine. The samples require Unreal Engine 4.25+.
+The Spine UE4 example works on all platforms supported by Unreal Engine. The samples require Unreal Engine 4.27-5.2.
 
 1. Copy the `spine-cpp` folder from this repositories root directory to your `Plugins/SpinePlugin/Sources/SpinePlugin/Public/` directory. You can run the `setup.bat` or `setup.sh` scripts to accomplish this.
 2. Open the SpineUE4.uproject file with Unreal Editor

+ 1 - 1
spine-ue4/SpineUE4.uproject

@@ -1,6 +1,6 @@
 {
 	"FileVersion": 3,
-	"EngineAssociation": "5.2",
+	"EngineAssociation": "5.3",
 	"Category": "",
 	"Description": "",
 	"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);
 		}
 
+		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 OnDemandTextureLoader onDemandTextureLoader = null;
 #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>
 		/// <returns>True, if any placeholder texture is assigned at a Material of the associated AtlasAssetBase.</returns>
 		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>
 		/// 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
@@ -60,13 +81,20 @@ namespace Spine.Unity {
 		public abstract void EndCustomTextureLoading ();
 		public abstract bool HasPlaceholderAssigned (Material material);
 		public abstract void RequestLoadMaterialTextures (Material material, ref Material overrideMaterial);
+		public abstract void RequestLoadTexture (Texture placeholderTexture, ref Texture replacementTexture,
+			System.Action<Texture> onTextureLoaded = null);
 		public abstract void Clear (bool clearAtlasAsset = false);
 
 		#region Event delegates
 		public delegate void TextureLoadDelegate (OnDemandTextureLoader loader, Material material, int textureIndex);
+		protected event TextureLoadDelegate onTextureRequested;
 		protected event TextureLoadDelegate onTextureLoaded;
 		protected event TextureLoadDelegate onTextureUnloaded;
 
+		public event TextureLoadDelegate TextureRequested {
+			add { onTextureRequested += value; }
+			remove { onTextureRequested -= value; }
+		}
 		public event TextureLoadDelegate TextureLoaded {
 			add { onTextureLoaded += value; }
 			remove { onTextureLoaded -= value; }
@@ -76,6 +104,10 @@ namespace Spine.Unity {
 			remove { onTextureUnloaded -= value; }
 		}
 
+		protected void OnTextureRequested (Material material, int textureIndex) {
+			if (onTextureRequested != null)
+				onTextureRequested(this, material, textureIndex);
+		}
 		protected void OnTextureLoaded (Material material, int textureIndex) {
 			if (onTextureLoaded != null)
 				onTextureLoaded(this, material, textureIndex);

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

@@ -273,8 +273,12 @@ namespace Spine.Unity {
 		}
 
 		public override void LateUpdate () {
+			if (updateTiming == UpdateTiming.InLateUpdate && valid)
+				Update(unscaledTime ? Time.unscaledDeltaTime : Time.deltaTime);
+
 			// instantiation can happen from Update() after this component, leading to a missing Update() call.
 			if (!wasUpdatedAfterInit) Update(0);
+
 			base.LateUpdate();
 		}
 

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

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

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

@@ -109,6 +109,13 @@ namespace Spine.Unity {
 			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 () {
 			wasUpdatedAfterInit = true;
 
@@ -160,6 +167,8 @@ namespace Spine.Unity {
 		}
 
 		public override void LateUpdate () {
+			if (updateTiming == UpdateTiming.InLateUpdate && valid && translator != null && translator.Animator != null)
+				UpdateAnimation();
 			// instantiation can happen from Update() after this component, leading to a missing Update() call.
 			if (!wasUpdatedAfterInit) Update();
 			base.LateUpdate();

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

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

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

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

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

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

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

@@ -76,13 +76,17 @@ namespace Spine.Unity {
 	[System.Serializable]
 	public class AddressablesTextureLoader : GenericOnDemandTextureLoader<AddressableTextureReference, AddressableRequest> {
 		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.Completed += (obj) => {
 				if (obj.Status == AsyncOperationStatus.Succeeded) {
-					materialToUpdate.mainTexture = (Texture)targetReference.assetReference.Asset;
+					Texture loadedTexture = (Texture)targetReference.assetReference.Asset;
+					materialToUpdate.mainTexture = loadedTexture;
 					OnTextureLoaded(materialToUpdate, textureIndex);
+					if (onTextureLoaded != null) onTextureLoaded(loadedTexture);
 				}
 			};
 		}

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

@@ -2,7 +2,7 @@
 	"name": "com.esotericsoftware.spine.addressables",
 	"displayName": "Spine Addressables Extensions [Experimental]",
 	"description": "This experimental plugin provides integration of Addressables on-demand texture loading for the spine-unity runtime.\nPlease be sure to test this package first and create backups of your project before using.\n\nUsage: First declare your target Material textures as addressable. Then select the SpineAtlasAsset, right-click the SpineAtlasAsset Inspector heading and select 'Add Addressables Loader'. This generates an 'AddressableTextureLoader' asset providing configuration parameters and sets up low-resolution placeholder textures which are automatically assigned in a pre-build step when building your game executable.\n\nPrerequisites:\nIt requires a working installation of the spine-unity runtime (via the spine-unity unitypackage), version 4.1.\n(See http://esotericsoftware.com/git/spine-runtimes/spine-unity)",
-	"version": "4.1.0-preview.1",
+	"version": "4.1.0-preview.2",
 	"unity": "2018.3",
 	"author": {
 		"name": "Esoteric Software",

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

@@ -37,6 +37,7 @@
 
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using UnityEditor;
 using UnityEngine;
 
@@ -92,7 +93,7 @@ namespace Spine.Unity.Editor {
 			/// When set to e.g. "_Addressable", the loader asset created for
 			/// the "Skeleton_Atlas" asset is named "Skeleton_Addressable".
 			/// </summary>
-			public string LoaderSuffix { get; }
+			public virtual string LoaderSuffix { get { return "_Loader"; } }
 
 			public abstract bool SetupOnDemandLoadingReference (
 				ref TargetReference targetTextureReference, Texture targetTexture);
@@ -237,7 +238,7 @@ namespace Spine.Unity.Editor {
 
 #if NEWPLAYMODECALLBACKS
 		static void OnPlaymodeChanged (PlayModeStateChange mode) {
-			bool assignTargetTextures = mode == PlayModeStateChange.ExitingPlayMode;
+			bool assignTargetTextures = mode == PlayModeStateChange.EnteredEditMode;
 #else
 		static void OnPlaymodeChanged () {
 			bool assignTargetTextures = !Application.isPlaying;
@@ -259,14 +260,23 @@ namespace Spine.Unity.Editor {
 
 		public static void AssignTargetTexturesAtLoader (OnDemandTextureLoader loader) {
 			List<Material> placeholderMaterials;
+			List<Material> nullTextureMaterials;
 			bool anyPlaceholdersAssigned = loader.HasPlaceholderTexturesAssigned(out placeholderMaterials);
-			if (anyPlaceholdersAssigned) {
-				Debug.Log("OnDemandTextureLoader detected placeholders assigned at one or more materials. Resetting to target textures.", loader);
+			bool anyMaterialNull = loader.HasNullMainTexturesAssigned(out nullTextureMaterials);
+			if (anyPlaceholdersAssigned || anyMaterialNull) {
+				Debug.Log("OnDemandTextureLoader detected placeholders assigned or null main textures at one or more materials. Resetting to target textures.", loader);
 				AssetDatabase.StartAssetEditing();
 				IEnumerable<Material> modifiedMaterials;
 				loader.AssignTargetTextures(out modifiedMaterials);
-				foreach (Material placeholderMaterial in placeholderMaterials) {
-					EditorUtility.SetDirty(placeholderMaterial);
+				if (placeholderMaterials != null) {
+					foreach (Material placeholderMaterial in placeholderMaterials) {
+						EditorUtility.SetDirty(placeholderMaterial);
+					}
+				}
+				if (nullTextureMaterials != null) {
+					foreach (Material nullTextureMaterial in nullTextureMaterials) {
+						EditorUtility.SetDirty(nullTextureMaterial);
+					}
 				}
 				AssetDatabase.StopAssetEditing();
 				AssetDatabase.SaveAssets();

+ 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>();
 					placeholderMaterials.Add(material);
 				}
+				materialIndex++;
 			}
 			return anyPlaceholderAssigned;
 		}
@@ -180,8 +181,7 @@ namespace Spine.Unity {
 						atlasAsset, i + 1, targetMaterial), this);
 					return false;
 				}
-				Material ignoredArgument = null;
-				RequestLoadMaterialTextures(targetMaterial, ref ignoredArgument);
+				AssignTargetTextures(targetMaterial, i);
 				++i;
 			}
 			modifiedMaterials = atlasAsset.Materials;
@@ -223,7 +223,7 @@ namespace Spine.Unity {
 
 			int foundMaterialIndex = Array.FindIndex(placeholderMap, entry => entry.textures[textureIndex].placeholderTexture == currentTexture);
 			if (foundMaterialIndex >= 0)
-				RequestLoadTexture(material, foundMaterialIndex, textureIndex);
+				RequestLoadTexture(material, foundMaterialIndex, textureIndex, null);
 
 			int loadedMaterialIndex = Array.FindIndex(loadedDataAtMaterial, entry =>
 				entry.textureRequests[textureIndex].WasRequested &&
@@ -232,33 +232,69 @@ namespace Spine.Unity {
 				loadedDataAtMaterial[loadedMaterialIndex].lastFrameRequested = Time.frameCount;
 		}
 
-		protected virtual void RequestLoadTexture (Material material, int materialIndex, int textureIndex) {
+		public override void RequestLoadTexture (Texture placeholderTexture, ref Texture replacementTexture,
+			System.Action<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;
 			TargetReference targetReference = placeholderTextures[textureIndex].targetTextureReference;
 			loadedDataAtMaterial[materialIndex].lastFrameRequested = Time.frameCount;
 
 #if UNITY_EDITOR
 			if (!Application.isPlaying) {
-				if (targetReference.EditorTexture != null)
+				if (targetReference.EditorTexture != null) {
 					material.mainTexture = targetReference.EditorTexture;
-				return;
+					if (onTextureLoaded != null) onTextureLoaded(targetReference.EditorTexture);
+				}
+				return targetReference.EditorTexture;
 			}
 #endif
 			MaterialOnDemandData materialData = loadedDataAtMaterial[materialIndex];
 			if (materialData.textureRequests[textureIndex].WasRequested) {
 				Texture loadedTexture = GetAlreadyLoadedTexture(materialIndex, textureIndex);
-				if (loadedTexture != null)
+				if (loadedTexture != null) {
 					material.mainTexture = loadedTexture;
-				return;
+					if (onTextureLoaded != null) onTextureLoaded(loadedTexture);
+				}
+				return loadedTexture;
 			}
 
-			CreateTextureRequest(targetReference, materialData, textureIndex, material);
+			CreateTextureRequest(targetReference, materialData, textureIndex, material, onTextureLoaded);
+			return null;
 		}
 
 		public abstract Texture GetAlreadyLoadedTexture (int materialIndex, int textureIndex);
 
 		public abstract void CreateTextureRequest (TargetReference targetReference,
-			MaterialOnDemandData materialData, int textureIndex, Material materialToUpdate);
+			MaterialOnDemandData materialData, int textureIndex, Material materialToUpdate,
+			System.Action<Texture> onTextureLoaded);
 
 		public virtual void UnloadUnusedTextures () {
 			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",
 	"displayName": "Spine On-Demand Loading Extensions [Experimental]",
 	"description": "This experimental plugin provides a generic basic implementation of on-demand texture loading for the spine-unity runtime. You might want to use the available com.esotericsoftware.spine.addressables package which depends on this package.\nPlease be sure to test this package first and create backups of your project before using.\n\nPrerequisites:\nIt requires a working installation of the spine-unity runtime (via the spine-unity unitypackage), version 4.1.\n(See http://esotericsoftware.com/git/spine-runtimes/spine-unity)",
-	"version": "4.1.0",
+	"version": "4.1.0-preview.2",
 	"unity": "2018.3",
 	"author": {
 		"name": "Esoteric Software",