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

This commit is contained in:
Harald Csaszar 2024-08-29 17:27:07 +02:00
parent d769c1a930
commit 54e463048a
7 changed files with 251 additions and 25 deletions

View File

@ -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;

View File

@ -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",

View File

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

View File

@ -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;
}
}
}

View File

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

View File

@ -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)
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);
}
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);

View File

@ -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": "contact@esotericsoftware.com",
@ -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",