mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-20 09:16:01 +08:00
[unity] SpineVisualElement improvements. Now supports settings reference mesh bounds via a different bounds animation.
This commit is contained in:
parent
d769c1a930
commit
54e463048a
@ -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;
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 020c4dfc4cd28f8409ea82818e31d040
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb5762c450311694e84304e790546805
|
||||
@ -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);
|
||||
|
||||
@ -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",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user