Преглед на файлове

[unity] SpineVisualElement improvements. Now supports settings reference mesh bounds via a different bounds animation.

Harald Csaszar преди 1 година
родител
ревизия
54e463048a

+ 11 - 1
spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineInspectorUtility.cs

@@ -149,10 +149,20 @@ namespace Spine.Unity.Editor {
 							}
 						}
 					}
-
 					newPropertyPath = propertyPath.Remove(propertyPath.Length - localPathLength, localPathLength) + propertyName;
 					relativeProperty = property.serializedObject.FindProperty(newPropertyPath);
 				}
+				// If this fails as well, try at any base property up the hierarchy
+				if (relativeProperty == null) {
+					int dotIndex = propertyPath.Length - property.name.Length - 1;
+					while (relativeProperty == null) {
+						dotIndex = propertyPath.LastIndexOf('.', dotIndex - 1);
+						if (dotIndex < 0)
+							break;
+						newPropertyPath = propertyPath.Remove(dotIndex +  1) + propertyName;
+						relativeProperty = property.serializedObject.FindProperty(newPropertyPath);
+					}
+				}
 			}
 
 			return relativeProperty;

+ 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.2.81",
+	"version": "4.2.82",
 	"unity": "2018.3",
 	"author": {
 		"name": "Esoteric Software",

+ 8 - 0
spine-unity/Modules/com.esotericsoftware.spine.ui-toolkit/Editor.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 020c4dfc4cd28f8409ea82818e31d040
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 119 - 0
spine-unity/Modules/com.esotericsoftware.spine.ui-toolkit/Editor/SpineVisualElementAttributeDrawers.cs

@@ -0,0 +1,119 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated July 28, 2023. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2024, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software or
+ * otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
+ * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
+ * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+//#define CHANGE_BOUNDS_ON_ANIMATION_CHANGE
+
+using UnityEditor;
+using UnityEditor.UIElements;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+namespace Spine.Unity.Editor {
+
+	[CustomPropertyDrawer(typeof(BoundsFromAnimationAttribute))]
+	public class BoundsFromAnimationAttributeDrawer : PropertyDrawer {
+
+		protected BoundsFromAnimationAttribute TargetAttribute { get { return (BoundsFromAnimationAttribute)attribute; } }
+
+		public override VisualElement CreatePropertyGUI (SerializedProperty boundsProperty) {
+			var container = new VisualElement();
+			PropertyField referenceMeshBounds = new PropertyField();
+			referenceMeshBounds.BindProperty(boundsProperty);
+
+			var parentPropertyPath = boundsProperty.propertyPath.Substring(0, boundsProperty.propertyPath.LastIndexOf('.'));
+			var parent = boundsProperty.serializedObject.FindProperty(parentPropertyPath);
+			SerializedProperty animationProperty = parent.FindPropertyRelative(TargetAttribute.animationField);
+			SerializedProperty skeletonDataProperty = parent.FindPropertyRelative(TargetAttribute.dataField);
+			SerializedProperty skinProperty = parent.FindPropertyRelative(TargetAttribute.skinField);
+
+#if !CHANGE_BOUNDS_ON_ANIMATION_CHANGE
+			Button updateBoundsButton = new Button(() => {
+				UpdateMeshBounds(boundsProperty, animationProperty.stringValue,
+					(SkeletonDataAsset)skeletonDataProperty.objectReferenceValue, skinProperty.stringValue);
+			});
+			updateBoundsButton.text = "Update Bounds";
+			container.Add(updateBoundsButton);
+#else
+			referenceMeshBounds.TrackPropertyValue(animationProperty, prop => {
+				UpdateMeshBounds(boundsProperty, animationProperty.stringValue,
+					(SkeletonDataAsset)skeletonDataProperty.objectReferenceValue, skinProperty.stringValue);
+			});
+#endif
+			container.Add(referenceMeshBounds);
+
+			container.Bind(boundsProperty.serializedObject);
+			return container;
+		}
+
+		protected void UpdateMeshBounds (SerializedProperty boundsProperty, string boundsAnimation,
+			SkeletonDataAsset skeletonDataAsset, string skin) {
+			if (!skeletonDataAsset)
+				return;
+
+			Bounds bounds = CalculateMeshBounds(boundsAnimation, skeletonDataAsset, skin);
+			if (bounds.extents.x == 0 || bounds.extents.y == 0) {
+				Debug.LogWarning("Please select different Initial Skin and Bounds Animation. Not setting reference " +
+					"bounds as current combination (likely no attachments visible) leads to zero Mesh bounds.");
+				bounds.center = Vector3.zero;
+				bounds.extents = Vector3.one * 2f;
+			}
+			boundsProperty.boundsValue = bounds;
+			boundsProperty.serializedObject.ApplyModifiedProperties();
+		}
+
+		protected Bounds CalculateMeshBounds (string animationName, SkeletonDataAsset skeletonDataAsset, string skin) {
+			SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(false);
+			Skeleton skeleton = new Skeleton(skeletonData);
+			if (!string.IsNullOrEmpty(skin) && !string.Equals(skin, "default", System.StringComparison.Ordinal))
+				skeleton.SetSkin(skin);
+			skeleton.SetSlotsToSetupPose();
+
+			Spine.Animation animation = skeletonData.FindAnimation(animationName);
+			if (animation != null)
+				animation.Apply(skeleton, -1, 0, false, null, 1.0f, MixBlend.First, MixDirection.In);
+
+			skeleton.Update(0f);
+			skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
+
+			float x, y, width, height;
+			SkeletonClipping clipper = new SkeletonClipping();
+			float[] vertexBuffer = null;
+			skeleton.GetBounds(out x, out y, out width, out height, ref vertexBuffer, clipper);
+			if (x == int.MaxValue) {
+				return new Bounds();
+			}
+
+			Bounds bounds = new Bounds();
+			Vector2 halfSize = new Vector2(width * 0.5f, height * 0.5f);
+			bounds.center = new Vector3(x + halfSize.x, -y - halfSize.y, 0.0f);
+			bounds.extents = new Vector3(halfSize.x, halfSize.y, 0.0f);
+			return bounds;
+		}
+	}
+}

+ 2 - 0
spine-unity/Modules/com.esotericsoftware.spine.ui-toolkit/Editor/SpineVisualElementAttributeDrawers.cs.meta

@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: eb5762c450311694e84304e790546805

+ 106 - 19
spine-unity/Modules/com.esotericsoftware.spine.ui-toolkit/Runtime/SpineVisualElement.cs

@@ -29,17 +29,42 @@
 
 using System;
 using Unity.Collections;
-using Unity.Jobs;
 using UnityEngine;
-using UnityEngine.Profiling;
 using UnityEngine.UIElements;
 using UIVertex = UnityEngine.UIElements.Vertex;
 
 namespace Spine.Unity {
 
+	public class BoundsFromAnimationAttribute : PropertyAttribute {
+
+		public readonly string animationField;
+		public readonly string dataField;
+		public readonly string skinField;
+
+		public BoundsFromAnimationAttribute (string animationField, string skinField, string dataField = "skeletonDataAsset") {
+			this.animationField = animationField;
+			this.skinField = skinField;
+			this.dataField = dataField;
+		}
+	}
+
 	[UxmlElement]
 	public partial class SpineVisualElement : VisualElement {
 
+		[UxmlAttribute]
+		public SkeletonDataAsset SkeletonDataAsset {
+			get { return skeletonDataAsset; }
+			set {
+				if (skeletonDataAsset == value) return;
+				skeletonDataAsset = value;
+#if UNITY_EDITOR
+				if (!Application.isPlaying)
+					Initialize(true);
+#endif
+			}
+		}
+		public SkeletonDataAsset skeletonDataAsset;
+
 		[SpineAnimation(dataField: "SkeletonDataAsset", avoidGenericMenu: true)]
 		[UxmlAttribute]
 		public string StartingAnimation {
@@ -55,7 +80,7 @@ namespace Spine.Unity {
 		}
 		public string startingAnimation = "";
 
-		[SpineSkin(dataField: "SkeletonDataAsset", avoidGenericMenu: true)]
+		[SpineSkin(dataField: "SkeletonDataAsset", defaultAsEmptyString: true, avoidGenericMenu: true)]
 		[UxmlAttribute]
 		public string InitialSkinName {
 			get { return initialSkinName; }
@@ -73,19 +98,42 @@ namespace Spine.Unity {
 		[UxmlAttribute] public bool startingLoop { get; set; } = true;
 		[UxmlAttribute] public float timeScale { get; set; } = 1.0f;
 
+		[SpineAnimation(dataField: "SkeletonDataAsset", avoidGenericMenu: true)]
 		[UxmlAttribute]
-		public SkeletonDataAsset SkeletonDataAsset {
-			get { return skeletonDataAsset; }
+		public string BoundsAnimation {
+			get { return boundsAnimation; }
 			set {
-				if (skeletonDataAsset == value) return;
-				skeletonDataAsset = value;
+				boundsAnimation = value;
 #if UNITY_EDITOR
-				if (!Application.isPlaying)
-					Initialize(true);
+				if (!Application.isPlaying) {
+					if (!this.IsValid)
+						Initialize(true);
+					else {
+						UpdateAnimation();
+					}
+				}
 #endif
 			}
 		}
-		public SkeletonDataAsset skeletonDataAsset;
+		public string boundsAnimation = "";
+
+		[UxmlAttribute]
+		[BoundsFromAnimation(animationField: "BoundsAnimation",
+			skinField: "InitialSkinName", dataField: "SkeletonDataAsset")]
+		public Bounds ReferenceBounds {
+			get { return referenceMeshBounds; }
+			set {
+				if (referenceMeshBounds == value) return;
+#if UNITY_EDITOR
+				if (!Application.isPlaying && (value.size.x == 0 || value.size.y == 0)) return;
+#endif
+				referenceMeshBounds = value;
+				if (!this.IsValid) return;
+
+				AdjustOffsetScaleToMeshBounds(rendererElement);
+			}
+		}
+		public Bounds referenceMeshBounds;
 
 		public AnimationState AnimationState {
 			get {
@@ -93,21 +141,22 @@ namespace Spine.Unity {
 				return state;
 			}
 		}
+		[UxmlAttribute]
 		public bool freeze { get; set; }
+		[UxmlAttribute]
 		public bool unscaledTime { get; set; }
 
 		/// <summary>Update mode to optionally limit updates to e.g. only apply animations but not update the mesh.</summary>
 		public UpdateMode UpdateMode { get { return updateMode; } set { updateMode = value; } }
 		protected UpdateMode updateMode = UpdateMode.FullUpdate;
 
-		protected AnimationState state;
-		protected Skeleton skeleton;
+		protected AnimationState state = null;
+		protected Skeleton skeleton = null;
 		protected SkeletonRendererInstruction currentInstructions = new();// to match existing code better
 		protected Spine.Unity.MeshGeneratorUIElements meshGenerator = new MeshGeneratorUIElements();
 
 		protected VisualElement rendererElement;
 		IVisualElementScheduledItem scheduledItem;
-		protected Bounds referenceMeshBounds;
 		protected float scale = 100;
 		protected float offsetX, offsetY;
 
@@ -131,6 +180,10 @@ namespace Spine.Unity {
 		}
 
 		void OnGeometryChanged (GeometryChangedEvent evt) {
+			if (!this.IsValid) return;
+			if (referenceMeshBounds.size.x == 0 || referenceMeshBounds.size.y == 0) {
+				AdjustReferenceMeshBounds();
+			}
 			AdjustOffsetScaleToMeshBounds(rendererElement);
 		}
 
@@ -154,7 +207,6 @@ namespace Spine.Unity {
 				return;
 			}
 #endif
-
 			if (freeze) return;
 			Update(unscaledTime ? Time.unscaledDeltaTime : Time.deltaTime);
 			rendererElement.MarkDirtyRepaint();
@@ -212,17 +264,44 @@ namespace Spine.Unity {
 			if (!string.IsNullOrEmpty(initialSkinName))
 				skeleton.SetSkin(initialSkinName);
 
-			if (!string.IsNullOrEmpty(startingAnimation)) {
-				var animationObject = SkeletonDataAsset.GetSkeletonData(false).FindAnimation(startingAnimation);
+			string displayedAnimation = Application.isPlaying ? startingAnimation : boundsAnimation;
+			if (!string.IsNullOrEmpty(displayedAnimation)) {
+				var animationObject = skeletonData.FindAnimation(displayedAnimation);
 				if (animationObject != null) {
 					state.SetAnimation(0, animationObject, startingLoop);
 				}
 			}
+			if (referenceMeshBounds.size.x == 0 || referenceMeshBounds.size.y == 0) {
+				AdjustReferenceMeshBounds();
+				AdjustOffsetScaleToMeshBounds(rendererElement);
+			}
 
-			AdjustReferenceMeshBounds();
 			if (scheduledItem == null)
 				scheduledItem = schedule.Execute(Update).Every(1);
 
+			if (!Application.isPlaying)
+				Update(0.0f);
+
+			rendererElement.MarkDirtyRepaint();
+		}
+
+		protected void UpdateAnimation () {
+			this.state.ClearTracks();
+			skeleton.SetToSetupPose();
+
+			string displayedAnimation = Application.isPlaying ? startingAnimation : boundsAnimation;
+			if (!string.IsNullOrEmpty(displayedAnimation)) {
+				var animationObject = SkeletonDataAsset.GetSkeletonData(false).FindAnimation(displayedAnimation);
+				if (animationObject != null) {
+					state.SetAnimation(0, animationObject, startingLoop);
+				}
+			}
+			if (referenceMeshBounds.size.x == 0 || referenceMeshBounds.size.y == 0) {
+				AdjustReferenceMeshBounds();
+				AdjustOffsetScaleToMeshBounds(rendererElement);
+			}
+			Update(0.0f);
+
 			rendererElement.MarkDirtyRepaint();
 		}
 
@@ -320,6 +399,8 @@ namespace Spine.Unity {
 		}
 
 		public void AdjustReferenceMeshBounds () {
+			if (skeleton == null)
+				return;
 
 			// Need one update to obtain valid mesh bounds
 			Update(0.0f);
@@ -332,12 +413,18 @@ namespace Spine.Unity {
 				var submeshInstructionItem = currentInstructions.submeshInstructions.Items[i];
 				meshGenerator.AddSubmesh(submeshInstructionItem);
 			}
-
-			referenceMeshBounds = meshGenerator.GetMeshBounds();
+			Bounds meshBounds = meshGenerator.GetMeshBounds();
+			if (meshBounds.extents.x == 0 || meshBounds.extents.y == 0) {
+				ReferenceBounds = new Bounds(Vector3.zero, Vector3.one * 2f);
+			} else {
+				ReferenceBounds = meshBounds;
+			}
 		}
 
 		void AdjustOffsetScaleToMeshBounds (VisualElement visualElement) {
 			Rect targetRect = visualElement.layout;
+			if (float.IsNaN(targetRect.width)) return;
+
 			float xScale = targetRect.width / referenceMeshBounds.size.x;
 			float yScale = targetRect.height / referenceMeshBounds.size.y;
 			this.scale = Math.Min(xScale, yScale);

+ 4 - 4
spine-unity/Modules/com.esotericsoftware.spine.ui-toolkit/package.json

@@ -1,9 +1,9 @@
 {
   "name": "com.esotericsoftware.spine.ui-toolkit",
   "displayName": "Spine UI Toolkit [Experimental]",
-  "description": "This plugin provides UI Toolkit integration for the spine-unity runtime.\n\nPrerequisites:\nIt requires a working installation of the spine-unity runtime, version 4.2.75 or newer.\n(See http://esotericsoftware.com/git/spine-runtimes/spine-unity)",
-  "version": "4.2.0-preview.1",
-  "unity": "2023.2",
+  "description": "This plugin provides UI Toolkit integration for the spine-unity runtime.\n\nPrerequisites:\nIt requires a working installation of the spine-unity runtime, version 4.2.82 or newer and Unity 6000.0.16 or newer (requires [this bugfix](https://issuetracker.unity3d.com/issues/some-default-uxmlconverters-are-dependent-on-the-current-culture)).\n(See http://esotericsoftware.com/git/spine-runtimes/spine-unity)",
+  "version": "4.2.0-preview.2",
+  "unity": "6000.0",
   "author": {
     "name": "Esoteric Software",
     "email": "[email protected]",
@@ -11,7 +11,7 @@
   },
   "dependencies": {
     "com.unity.modules.uielements": "1.0.0",
-    "com.esotericsoftware.spine.spine-unity": "4.2.75"
+    "com.esotericsoftware.spine.spine-unity": "4.2.82"
   },
   "keywords": [
     "spine",