From 6e9ad610d6464149ffc71f45a8c5501e9a2823f8 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Wed, 3 Jun 2020 18:12:55 +0200 Subject: [PATCH 01/24] [unity] Fixed Prefab with `SkeletonRenderSeparator` constantly loading in editor. Closes #1626. --- .../Editor/SkeletonRenderSeparatorInspector.cs | 17 ++++++++++++++++- .../SkeletonRenderSeparator.cs | 12 +++++++++--- .../spine-unity/Components/SkeletonRenderer.cs | 2 +- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderSeparatorInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderSeparatorInspector.cs index 9c5fd1bf0..19712597c 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderSeparatorInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderSeparatorInspector.cs @@ -27,6 +27,10 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ +#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER +#define NEW_PREFAB_SYSTEM +#endif + using UnityEngine; using UnityEditor; @@ -81,7 +85,18 @@ namespace Spine.Unity.Examples { // Restore mesh part for undo logic after undo of "Add Parts Renderer". // Triggers regeneration and assignment of the mesh filter's mesh. - if (component.GetComponent() && component.GetComponent().sharedMesh == null) { + + bool isMeshFilterAlwaysNull = false; + #if UNITY_EDITOR && NEW_PREFAB_SYSTEM + // Don't store mesh or material at the prefab, otherwise it will permanently reload + var prefabType = UnityEditor.PrefabUtility.GetPrefabAssetType(component); + if (UnityEditor.PrefabUtility.IsPartOfPrefabAsset(component) && + (prefabType == UnityEditor.PrefabAssetType.Regular || prefabType == UnityEditor.PrefabAssetType.Variant)) { + isMeshFilterAlwaysNull = true; + } + #endif + + if (!isMeshFilterAlwaysNull && component.GetComponent() && component.GetComponent().sharedMesh == null) { component.OnDisable(); component.OnEnable(); } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderSeparator/SkeletonRenderSeparator.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderSeparator/SkeletonRenderSeparator.cs index e3b88ccfc..673bd036f 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderSeparator/SkeletonRenderSeparator.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderSeparator/SkeletonRenderSeparator.cs @@ -191,8 +191,10 @@ namespace Spine.Unity { skeletonRenderer.LateUpdate(); - foreach (var s in partsRenderers) - s.ClearMesh(); + foreach (var partsRenderer in partsRenderers) { + if (partsRenderer != null) + partsRenderer.ClearMesh(); + } } MaterialPropertyBlock copiedBlock; @@ -221,6 +223,8 @@ namespace Spine.Unity { int rendererIndex = 0; var currentRenderer = partsRenderers[rendererIndex]; for (int si = 0, start = 0; si <= lastSubmeshInstruction; si++) { + if (currentRenderer == null) + continue; if (submeshInstructionsItems[si].forceSeparate || si == lastSubmeshInstruction) { // Apply properties var meshGenerator = currentRenderer.MeshGenerator; @@ -245,7 +249,9 @@ namespace Spine.Unity { // Clear extra renderers if they exist. for (; rendererIndex < rendererCount; rendererIndex++) { - partsRenderers[rendererIndex].ClearMesh(); + currentRenderer = partsRenderers[rendererIndex]; + if (currentRenderer != null) + partsRenderers[rendererIndex].ClearMesh(); } } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs index 3e98eef8f..e75c00682 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs @@ -529,7 +529,7 @@ namespace Spine.Unity { separatorSlots.Add(slot); } #if UNITY_EDITOR - else + else if (!string.IsNullOrEmpty(separatorSlotNames[i])) { Debug.LogWarning(separatorSlotNames[i] + " is not a slot in " + skeletonDataAsset.skeletonJSON.name); } From 54b8039f1a014161379f776c4dcb269f4651dd19 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Fri, 5 Jun 2020 21:28:51 +0200 Subject: [PATCH 02/24] [libgdx] Fixed a clipping bug when there are two clipping meshes and clipping-end-slot is a skin bone that is disabled (Clipping.endClipping not called when slot.bone.active==false). See #1694. --- .../esotericsoftware/spine/SkeletonRenderer.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java index 31fbb2169..49ee9e80e 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/SkeletonRenderer.java @@ -91,7 +91,10 @@ public class SkeletonRenderer { Array drawOrder = skeleton.drawOrder; for (int i = 0, n = drawOrder.size; i < n; i++) { Slot slot = drawOrder.get(i); - if (!slot.bone.active) continue; + if (!slot.bone.active) { + clipper.clipEnd(slot); + continue; + } Attachment attachment = slot.attachment; if (attachment instanceof RegionAttachment) { RegionAttachment region = (RegionAttachment)attachment; @@ -170,7 +173,10 @@ public class SkeletonRenderer { Array drawOrder = skeleton.drawOrder; for (int i = 0, n = drawOrder.size; i < n; i++) { Slot slot = drawOrder.get(i); - if (!slot.bone.active) continue; + if (!slot.bone.active) { + clipper.clipEnd(slot); + continue; + } Texture texture = null; int vertexSize = clipper.isClipping() ? 2 : 5; Attachment attachment = slot.attachment; @@ -294,7 +300,10 @@ public class SkeletonRenderer { Array drawOrder = skeleton.drawOrder; for (int i = 0, n = drawOrder.size; i < n; i++) { Slot slot = drawOrder.get(i); - if (!slot.bone.active) continue; + if (!slot.bone.active) { + clipper.clipEnd(slot); + continue; + } Texture texture = null; int vertexSize = clipper.isClipping() ? 2 : 6; Attachment attachment = slot.attachment; From 1707c8ce428cb3fb257921de5e94df0261b8cd2f Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Fri, 5 Jun 2020 21:21:08 +0200 Subject: [PATCH 03/24] [unity] Fixed a clipping bug when there are two clipping meshes and clipping-end-slot is a skin bone that is disabled (Clipping.endClipping not called when slot.bone.active==false). See #1694. --- .../Runtime/spine-unity/Mesh Generation/MeshGenerator.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs index 153f94e6f..87da59f34 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs @@ -503,7 +503,10 @@ namespace Spine.Unity { for (int slotIndex = instruction.startSlot; slotIndex < instruction.endSlot; slotIndex++) { var slot = drawOrderItems[slotIndex]; - if (!slot.bone.active) continue; + if (!slot.bone.active) { + clipper.ClipEnd(slot); + continue; + } var attachment = slot.attachment; float z = zSpacing * slotIndex; From eadce3188c788ae78ccec487c3a0a290d0ec18d9 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Mon, 8 Jun 2020 20:18:11 +0200 Subject: [PATCH 04/24] [unity] Fixed BoundingBoxFollower exception in editor upon loading. Closes #1696. --- .../BoundingBoxFollowerInspector.cs | 10 +++- .../Following/BoundingBoxFollower.cs | 55 +++++++++---------- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoundingBoxFollowerInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoundingBoxFollowerInspector.cs index 260a90150..294882945 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoundingBoxFollowerInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoundingBoxFollowerInspector.cs @@ -55,7 +55,7 @@ namespace Spine.Unity.Editor { } } - void OnEnable () { + void InitializeEditor () { skeletonRenderer = serializedObject.FindProperty("skeletonRenderer"); slotName = serializedObject.FindProperty("slotName"); isTrigger = serializedObject.FindProperty("isTrigger"); @@ -64,12 +64,17 @@ namespace Spine.Unity.Editor { } public override void OnInspectorGUI () { + #if !NEW_PREFAB_SYSTEM bool isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab); #else bool isInspectingPrefab = false; #endif + // Note: when calling InitializeEditor() in OnEnable, it throws exception + // "SerializedObjectNotCreatableException: Object at index 0 is null". + InitializeEditor(); + // Try to auto-assign SkeletonRenderer field. if (skeletonRenderer.objectReferenceValue == null) { var foundSkeletonRenderer = follower.GetComponentInParent(); @@ -80,6 +85,7 @@ namespace Spine.Unity.Editor { skeletonRenderer.objectReferenceValue = foundSkeletonRenderer; serializedObject.ApplyModifiedProperties(); + InitializeEditor(); } var skeletonRendererValue = skeletonRenderer.objectReferenceValue as SkeletonRenderer; @@ -101,6 +107,7 @@ namespace Spine.Unity.Editor { EditorGUILayout.PropertyField(slotName, new GUIContent("Slot")); if (EditorGUI.EndChangeCheck()) { serializedObject.ApplyModifiedProperties(); + InitializeEditor(); #if !NEW_PREFAB_SYSTEM if (!isInspectingPrefab) rebuildRequired = true; @@ -118,6 +125,7 @@ namespace Spine.Unity.Editor { if (clearStateChanged || triggerChanged) { serializedObject.ApplyModifiedProperties(); + InitializeEditor(); if (triggerChanged) foreach (var col in follower.colliderTable.Values) col.isTrigger = isTrigger.boolValue; diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoundingBoxFollower.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoundingBoxFollower.cs index 63e5e30d9..570a2cc78 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoundingBoxFollower.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoundingBoxFollower.cs @@ -106,7 +106,12 @@ namespace Spine.Unity { ) return; - DisposeColliders(); + slot = null; + currentAttachment = null; + currentAttachmentName = null; + currentCollider = null; + colliderTable.Clear(); + nameTable.Clear(); var skeleton = skeletonRenderer.skeleton; slot = skeleton.FindSlot(slotName); @@ -118,13 +123,16 @@ namespace Spine.Unity { return; } + int requiredCollidersCount = 0; + var colliders = GetComponents(); if (this.gameObject.activeInHierarchy) { foreach (var skin in skeleton.Data.Skins) - AddSkin(skin, slotIndex); + AddCollidersForSkin(skin, slotIndex, colliders, ref requiredCollidersCount); if (skeleton.skin != null) - AddSkin(skeleton.skin, slotIndex); + AddCollidersForSkin(skeleton.skin, slotIndex, colliders, ref requiredCollidersCount); } + DisposeExcessCollidersAfter(requiredCollidersCount); if (BoundingBoxFollower.DebugMessages) { bool valid = colliderTable.Count != 0; @@ -137,7 +145,7 @@ namespace Spine.Unity { } } - void AddSkin (Skin skin, int slotIndex) { + void AddCollidersForSkin (Skin skin, int slotIndex, PolygonCollider2D[] previousColliders, ref int collidersCount) { if (skin == null) return; var skinEntries = new List(); skin.GetAttachments(slotIndex, skinEntries); @@ -151,8 +159,11 @@ namespace Spine.Unity { if (boundingBoxAttachment != null) { if (!colliderTable.ContainsKey(boundingBoxAttachment)) { - var bbCollider = SkeletonUtility.AddBoundingBoxAsComponent(boundingBoxAttachment, slot, gameObject, isTrigger); - + var bbCollider = collidersCount < previousColliders.Length ? + previousColliders[collidersCount] : gameObject.AddComponent(); + ++collidersCount; + SkeletonUtility.SetColliderPointsLocal(bbCollider, slot, boundingBoxAttachment); + bbCollider.isTrigger = isTrigger; bbCollider.enabled = false; bbCollider.hideFlags = HideFlags.NotEditable; bbCollider.isTrigger = IsTrigger; @@ -178,33 +189,21 @@ namespace Spine.Unity { currentCollider = null; } - void DisposeColliders () { + void DisposeExcessCollidersAfter (int requiredCount) { var colliders = GetComponents(); if (colliders.Length == 0) return; - if (Application.isEditor) { - if (Application.isPlaying) { - foreach (var c in colliders) { - if (c != null) - Destroy(c); - } - } else { - foreach (var c in colliders) - if (c != null) - DestroyImmediate(c); + for (int i = requiredCount; i < colliders.Length; ++i) { + var collider = colliders[i]; + if (collider != null) { +#if UNITY_EDITOR + if (Application.isEditor && !Application.isPlaying) + DestroyImmediate(collider); + else +#endif + Destroy(collider); } - } else { - foreach (PolygonCollider2D c in colliders) - if (c != null) - Destroy(c); } - - slot = null; - currentAttachment = null; - currentAttachmentName = null; - currentCollider = null; - colliderTable.Clear(); - nameTable.Clear(); } void LateUpdate () { From a7e2c9d52a106d18a20c2fd00d84d7db34384650 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Fri, 12 Jun 2020 11:35:13 +0200 Subject: [PATCH 05/24] [csharp] SkeletonBinary: Optimization of ReadFloat and ReadInt performance, overall ~10% performance gain. Closes #1698. --- spine-csharp/src/SkeletonBinary.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/spine-csharp/src/SkeletonBinary.cs b/spine-csharp/src/SkeletonBinary.cs index e4ce5538c..810c22b72 100644 --- a/spine-csharp/src/SkeletonBinary.cs +++ b/spine-csharp/src/SkeletonBinary.cs @@ -883,6 +883,7 @@ namespace Spine { internal class SkeletonInput { private byte[] chars = new byte[32]; + private byte[] bytesBigEndian = new byte[4]; internal ExposedList strings; Stream input; @@ -905,15 +906,20 @@ namespace Spine { } public float ReadFloat () { - chars[3] = (byte)input.ReadByte(); - chars[2] = (byte)input.ReadByte(); - chars[1] = (byte)input.ReadByte(); - chars[0] = (byte)input.ReadByte(); + input.Read(bytesBigEndian, 0, 4); + chars[3] = bytesBigEndian[0]; + chars[2] = bytesBigEndian[1]; + chars[1] = bytesBigEndian[2]; + chars[0] = bytesBigEndian[3]; return BitConverter.ToSingle(chars, 0); } public int ReadInt () { - return (input.ReadByte() << 24) + (input.ReadByte() << 16) + (input.ReadByte() << 8) + input.ReadByte(); + input.Read(bytesBigEndian, 0, 4); + return (bytesBigEndian[0] << 24) + + (bytesBigEndian[1] << 16) + + (bytesBigEndian[2] << 8) + + bytesBigEndian[3]; } public int ReadInt (bool optimizePositive) { From af8691c98ebbc627baab01b4c781c4eeee7b6ab0 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Tue, 16 Jun 2020 21:07:01 +0200 Subject: [PATCH 06/24] [cpp] Fixed incorrect computeHold computations (occurring under rare conditions). Closes #1702. --- spine-cpp/spine-cpp/src/spine/AnimationState.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spine-cpp/spine-cpp/src/spine/AnimationState.cpp b/spine-cpp/spine-cpp/src/spine/AnimationState.cpp index 657770396..845c5e206 100644 --- a/spine-cpp/spine-cpp/src/spine/AnimationState.cpp +++ b/spine-cpp/spine-cpp/src/spine/AnimationState.cpp @@ -1031,9 +1031,9 @@ void AnimationState::computeHold(TrackEntry *entry) { } else { for (TrackEntry *next = to->_mixingTo; next != NULL; next = next->_mixingTo) { if (next->_animation->hasTimeline(id)) continue; - if (entry->_mixDuration > 0) { + if (next->_mixDuration > 0) { timelineMode[i] = HoldMix; - timelineHoldMix[i] = entry; + timelineHoldMix[i] = next; i++; goto continue_outer; // continue outer; } From 3a1dcd651ad7923adad7822836b9dde82cc89099 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Wed, 17 Jun 2020 21:31:51 +0200 Subject: [PATCH 07/24] [unity] Fixed shader compile error with Unity 2019.4 and Urp 7.4.1. Closes #1700. Shader cleanup, removal of unused instancing vars and separate alpha texture variable. --- .../Sprite/SpritesPixelLit-Outline.shader | 2 -- .../Sprite/SpritesUnlit-Outline.shader | 2 -- .../Sprite/SpritesVertexLit-Outline.shader | 2 -- .../Sprite/CGIncludes/ShaderShared.cginc | 34 ------------------- .../Shaders/Sprite/SpritesPixelLit.shader | 2 -- .../Shaders/Sprite/SpritesUnlit.shader | 2 -- .../Shaders/Sprite/SpritesVertexLit.shader | 2 -- .../Shaders/Spine-Sprite-LW.shader | 2 -- .../Shaders/2D/Spine-Sprite-URP-2D.shader | 2 -- .../Shaders/Spine-Sprite-URP.shader | 2 -- 10 files changed, 52 deletions(-) diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesPixelLit-Outline.shader b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesPixelLit-Outline.shader index 1cb3e0175..844a990e4 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesPixelLit-Outline.shader +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesPixelLit-Outline.shader @@ -11,8 +11,6 @@ Shader "Spine/Outline/Sprite/Pixel Lit" _BumpMap ("Normal Map", 2D) = "bump" {} [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0 - [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {} - [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0 _EmissionColor("Color", Color) = (0,0,0,0) _EmissionMap("Emission", 2D) = "white" {} diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesUnlit-Outline.shader b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesUnlit-Outline.shader index b687e0130..756f7da33 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesUnlit-Outline.shader +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesUnlit-Outline.shader @@ -8,8 +8,6 @@ Shader "Spine/Outline/Sprite/Unlit" _Color ("Color", Color) = (1,1,1,1) [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0 - [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {} - [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0 _ZWrite ("Depth Write", Float) = 0.0 _Cutoff ("Depth alpha cutoff", Range(0,1)) = 0.0 diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesVertexLit-Outline.shader b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesVertexLit-Outline.shader index 3611d7956..6e087dd04 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesVertexLit-Outline.shader +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Outline/Sprite/SpritesVertexLit-Outline.shader @@ -11,8 +11,6 @@ Shader "Spine/Outline/Sprite/Vertex Lit" _BumpMap ("Normal Map", 2D) = "bump" {} [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0 - [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {} - [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0 _EmissionColor("Color", Color) = (0,0,0,0) _EmissionMap("Emission", 2D) = "white" {} diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/CGIncludes/ShaderShared.cginc b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/CGIncludes/ShaderShared.cginc index c8918952e..34f8c0b47 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/CGIncludes/ShaderShared.cginc +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/CGIncludes/ShaderShared.cginc @@ -1,7 +1,5 @@ // Upgrade NOTE: upgraded instancing buffer 'PerDrawSprite' to new syntax. -// Upgrade NOTE: upgraded instancing buffer 'PerDrawSprite' to new syntax. - #ifndef SHADER_SHARED_INCLUDED #define SHADER_SHARED_INCLUDED @@ -13,28 +11,6 @@ #include "UnityCG.cginc" #endif -#ifdef UNITY_INSTANCING_ENABLED - - UNITY_INSTANCING_BUFFER_START(PerDrawSprite) - // SpriteRenderer.Color while Non-Batched/Instanced. - fixed4 unity_SpriteRendererColorArray[UNITY_INSTANCED_ARRAY_SIZE]; - // this could be smaller but that's how bit each entry is regardless of type - float4 unity_SpriteFlipArray[UNITY_INSTANCED_ARRAY_SIZE]; - UNITY_INSTANCING_BUFFER_END(PerDrawSprite) - - #define _RendererColor unity_SpriteRendererColorArray[unity_InstanceID] - #define _Flip unity_SpriteFlipArray[unity_InstanceID] - -#endif // instancing - -CBUFFER_START(UnityPerDrawSprite) -#ifndef UNITY_INSTANCING_ENABLED - fixed4 _RendererColor; - float4 _Flip; -#endif - float _EnableExternalAlpha; -CBUFFER_END - //////////////////////////////////////// // Space functions // @@ -376,11 +352,6 @@ inline fixed4 applyFog(fixed4 pixel, float fogCoordOrFactorAtLWRP) uniform sampler2D _MainTex; -#if ETC1_EXTERNAL_ALPHA -//External alpha texture for ETC1 compression -uniform sampler2D _AlphaTex; -#endif //ETC1_EXTERNAL_ALPHA - #if _TEXTURE_BLEND uniform sampler2D _BlendTex; uniform float _BlendAmount; @@ -401,11 +372,6 @@ inline fixed4 calculateTexturePixel(float2 texcoord) pixel = tex2D(_MainTex, texcoord); #endif // !_TEXTURE_BLEND -#if ETC1_EXTERNAL_ALPHA - fixed4 alpha = tex2D (_AlphaTex, texcoord); - pixel.a = lerp (pixel.a, alpha.r, _EnableExternalAlpha); -#endif - #if defined(_COLOR_ADJUST) pixel = adjustColor(pixel); #endif // _COLOR_ADJUST diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesPixelLit.shader b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesPixelLit.shader index acd4e5e33..7bdd1ab02 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesPixelLit.shader +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesPixelLit.shader @@ -9,8 +9,6 @@ Shader "Spine/Sprite/Pixel Lit" _BumpMap ("Normal Map", 2D) = "bump" {} [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0 - [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {} - [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0 _EmissionColor("Color", Color) = (0,0,0,0) _EmissionMap("Emission", 2D) = "white" {} diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesUnlit.shader b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesUnlit.shader index 66c166055..39ae8d994 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesUnlit.shader +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesUnlit.shader @@ -6,8 +6,6 @@ Shader "Spine/Sprite/Unlit" _Color ("Color", Color) = (1,1,1,1) [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0 - [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {} - [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0 _ZWrite ("Depth Write", Float) = 0.0 _Cutoff ("Depth alpha cutoff", Range(0,1)) = 0.0 diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesVertexLit.shader b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesVertexLit.shader index b58ad42a5..366e75aef 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesVertexLit.shader +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Shaders/Sprite/SpritesVertexLit.shader @@ -9,8 +9,6 @@ Shader "Spine/Sprite/Vertex Lit" _BumpMap ("Normal Map", 2D) = "bump" {} [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0 - [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {} - [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0 _EmissionColor("Color", Color) = (0,0,0,0) _EmissionMap("Emission", 2D) = "white" {} diff --git a/spine-unity/Modules/com.esotericsoftware.spine.lwrp-shaders/Shaders/Spine-Sprite-LW.shader b/spine-unity/Modules/com.esotericsoftware.spine.lwrp-shaders/Shaders/Spine-Sprite-LW.shader index aca16c403..6c9bb44db 100644 --- a/spine-unity/Modules/com.esotericsoftware.spine.lwrp-shaders/Shaders/Spine-Sprite-LW.shader +++ b/spine-unity/Modules/com.esotericsoftware.spine.lwrp-shaders/Shaders/Spine-Sprite-LW.shader @@ -9,8 +9,6 @@ Shader "Lightweight Render Pipeline/Spine/Sprite" _BumpMap("Normal Map", 2D) = "bump" {} [MaterialToggle] PixelSnap("Pixel snap", Float) = 0 - [PerRendererData] _AlphaTex("External Alpha", 2D) = "white" {} - [PerRendererData] _EnableExternalAlpha("Enable External Alpha", Float) = 0 _EmissionColor("Color", Color) = (0,0,0,0) _EmissionMap("Emission", 2D) = "white" {} diff --git a/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/2D/Spine-Sprite-URP-2D.shader b/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/2D/Spine-Sprite-URP-2D.shader index 7c42f712d..e1b08b626 100644 --- a/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/2D/Spine-Sprite-URP-2D.shader +++ b/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/2D/Spine-Sprite-URP-2D.shader @@ -10,8 +10,6 @@ Shader "Universal Render Pipeline/2D/Spine/Sprite" _BumpMap("Normal Map", 2D) = "bump" {} [MaterialToggle] PixelSnap("Pixel snap", Float) = 0 - [PerRendererData] _AlphaTex("External Alpha", 2D) = "white" {} - [PerRendererData] _EnableExternalAlpha("Enable External Alpha", Float) = 0 _EmissionColor("Color", Color) = (0,0,0,0) _EmissionMap("Emission", 2D) = "white" {} diff --git a/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/Spine-Sprite-URP.shader b/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/Spine-Sprite-URP.shader index 79402b040..41c3f7aeb 100644 --- a/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/Spine-Sprite-URP.shader +++ b/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/Spine-Sprite-URP.shader @@ -9,8 +9,6 @@ Shader "Universal Render Pipeline/Spine/Sprite" _BumpMap("Normal Map", 2D) = "bump" {} [MaterialToggle] PixelSnap("Pixel snap", Float) = 0 - [PerRendererData] _AlphaTex("External Alpha", 2D) = "white" {} - [PerRendererData] _EnableExternalAlpha("Enable External Alpha", Float) = 0 _EmissionColor("Color", Color) = (0,0,0,0) _EmissionMap("Emission", 2D) = "white" {} From 06bd6a90bdb4c042687e27d83c987ad6b9b27c37 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Thu, 18 Jun 2020 17:41:50 +0200 Subject: [PATCH 08/24] [unity] Added SkeletonDataAsset 'Reload' button for SkeletonGraphic. See #1704. --- .../Components/SkeletonGraphicInspector.cs | 37 ++++++++- .../Components/SkeletonRendererInspector.cs | 78 ++++--------------- .../Editor/Utility/SpineEditorUtilities.cs | 49 ++++++++++++ 3 files changed, 99 insertions(+), 65 deletions(-) diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs index 97b8235bc..61044ed31 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs @@ -33,7 +33,6 @@ using UnityEngine; using UnityEditor; -using Spine; namespace Spine.Unity.Editor { using Icons = SpineEditorUtilities.Icons; @@ -44,6 +43,11 @@ namespace Spine.Unity.Editor { public class SkeletonGraphicInspector : UnityEditor.Editor { const string SeparatorSlotNamesFieldName = "separatorSlotNames"; + const string ReloadButtonString = "Reload"; + protected GUIContent SkeletonDataAssetLabel; + static GUILayoutOption reloadButtonWidth; + static GUILayoutOption ReloadButtonWidth { get { return reloadButtonWidth = reloadButtonWidth ?? GUILayout.Width(GUI.skin.label.CalcSize(new GUIContent(ReloadButtonString)).x + 20); } } + static GUIStyle ReloadButtonStyle { get { return EditorStyles.miniButton; } } SerializedProperty material, color; SerializedProperty skeletonDataAsset, initialSkinName; @@ -56,6 +60,7 @@ namespace Spine.Unity.Editor { SkeletonGraphic thisSkeletonGraphic; protected bool isInspectingPrefab; protected bool slotsReapplyRequired = false; + protected bool forceReloadQueued = false; protected bool TargetIsValid { get { @@ -80,6 +85,10 @@ namespace Spine.Unity.Editor { #else isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab); #endif + SpineEditorUtilities.ConfirmInitialization(); + + // Labels + SkeletonDataAssetLabel = new GUIContent("SkeletonData Asset", Icons.spine); var so = this.serializedObject; thisSkeletonGraphic = target as SkeletonGraphic; @@ -115,10 +124,34 @@ namespace Spine.Unity.Editor { } public override void OnInspectorGUI () { + + if (UnityEngine.Event.current.type == EventType.Layout) { + if (forceReloadQueued) { + forceReloadQueued = false; + foreach (var c in targets) { + SpineEditorUtilities.ReloadSkeletonDataAssetAndComponent(c as SkeletonGraphic); + } + } + else { + foreach (var c in targets) { + var component = c as SkeletonGraphic; + if (!component.IsValid) { + SpineEditorUtilities.ReinitializeComponent(component); + if (!component.IsValid) continue; + } + } + } + } + bool wasChanged = false; EditorGUI.BeginChangeCheck(); - EditorGUILayout.PropertyField(skeletonDataAsset); + using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox)) { + SpineInspectorUtility.PropertyFieldFitLabel(skeletonDataAsset, SkeletonDataAssetLabel); + if (GUILayout.Button(ReloadButtonString, ReloadButtonStyle, ReloadButtonWidth)) + forceReloadQueued = true; + } + EditorGUILayout.PropertyField(material); EditorGUILayout.PropertyField(color); diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRendererInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRendererInspector.cs index 15f8d46b8..4ada78b48 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRendererInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRendererInspector.cs @@ -82,7 +82,7 @@ namespace Spine.Unity.Editor { const string ReloadButtonString = "Reload"; static GUILayoutOption reloadButtonWidth; static GUILayoutOption ReloadButtonWidth { get { return reloadButtonWidth = reloadButtonWidth ?? GUILayout.Width(GUI.skin.label.CalcSize(new GUIContent(ReloadButtonString)).x + 20); } } - static GUIStyle ReloadButtonStyle { get { return EditorStyles.miniButtonRight; } } + static GUIStyle ReloadButtonStyle { get { return EditorStyles.miniButton; } } protected bool TargetIsValid { get { @@ -174,9 +174,9 @@ namespace Spine.Unity.Editor { AreAnyMaskMaterialsMissing()) { if (!Application.isPlaying) { if (multi) { - foreach (var o in targets) EditorForceInitializeComponent((SkeletonRenderer)o); + foreach (var o in targets) SpineEditorUtilities.ReinitializeComponent((SkeletonRenderer)o); } else { - EditorForceInitializeComponent((SkeletonRenderer)target); + SpineEditorUtilities.ReinitializeComponent((SkeletonRenderer)target); } SceneView.RepaintAll(); } @@ -188,25 +188,16 @@ namespace Spine.Unity.Editor { if (Event.current.type == EventType.Layout) { if (forceReloadQueued) { forceReloadQueued = false; - if (multi) { - foreach (var c in targets) - EditorForceReloadSkeletonDataAssetAndComponent(c as SkeletonRenderer); - } else { - EditorForceReloadSkeletonDataAssetAndComponent(target as SkeletonRenderer); + foreach (var c in targets) { + SpineEditorUtilities.ReloadSkeletonDataAssetAndComponent(c as SkeletonRenderer); } } else { - if (multi) { - foreach (var c in targets) { - var component = c as SkeletonRenderer; - if (!component.valid) { - EditorForceInitializeComponent(component); - if (!component.valid) continue; - } + foreach (var c in targets) { + var component = c as SkeletonRenderer; + if (!component.valid) { + SpineEditorUtilities.ReinitializeComponent(component); + if (!component.valid) continue; } - } else { - var component = (SkeletonRenderer)target; - if (!component.valid) - EditorForceInitializeComponent(component); } } @@ -241,15 +232,8 @@ namespace Spine.Unity.Editor { #if NO_PREFAB_MESH if (isInspectingPrefab) { - if (multi) { - foreach (var c in targets) { - var component = (SkeletonRenderer)c; - MeshFilter meshFilter = component.GetComponent(); - if (meshFilter != null && meshFilter.sharedMesh != null) - meshFilter.sharedMesh = null; - } - } else { - var component = (SkeletonRenderer)target; + foreach (var c in targets) { + var component = (SkeletonRenderer)c; MeshFilter meshFilter = component.GetComponent(); if (meshFilter != null && meshFilter.sharedMesh != null) meshFilter.sharedMesh = null; @@ -286,7 +270,7 @@ namespace Spine.Unity.Editor { return; } - if (!SkeletonDataAssetIsValid(component.skeletonDataAsset)) { + if (!SpineEditorUtilities.SkeletonDataAssetIsValid(component.skeletonDataAsset)) { EditorGUILayout.HelpBox("Skeleton Data Asset error. Please check Skeleton Data Asset.", MessageType.Error); return; } @@ -535,38 +519,6 @@ namespace Spine.Unity.Editor { return false; } - static void EditorForceReloadSkeletonDataAssetAndComponent (SkeletonRenderer component) { - if (component == null) return; - - // Clear all and reload. - if (component.skeletonDataAsset != null) { - foreach (AtlasAssetBase aa in component.skeletonDataAsset.atlasAssets) { - if (aa != null) aa.Clear(); - } - component.skeletonDataAsset.Clear(); - } - component.skeletonDataAsset.GetSkeletonData(true); - - // Reinitialize. - EditorForceInitializeComponent(component); - } - - static void EditorForceInitializeComponent (SkeletonRenderer component) { - if (component == null) return; - if (!SkeletonDataAssetIsValid(component.SkeletonDataAsset)) return; - component.Initialize(true); - - #if BUILT_IN_SPRITE_MASK_COMPONENT - SpineMaskUtilities.EditorAssignSpriteMaskMaterials(component); - #endif - - component.LateUpdate(); - } - - static bool SkeletonDataAssetIsValid (SkeletonDataAsset asset) { - return asset != null && asset.GetSkeletonData(quiet: true) != null; - } - bool AreAnyMaskMaterialsMissing() { #if BUILT_IN_SPRITE_MASK_COMPONENT foreach (var o in targets) { @@ -584,13 +536,13 @@ namespace Spine.Unity.Editor { static void EditorSetMaskMaterials(SkeletonRenderer component, SpriteMaskInteraction maskType) { if (component == null) return; - if (!SkeletonDataAssetIsValid(component.SkeletonDataAsset)) return; + if (!SpineEditorUtilities.SkeletonDataAssetIsValid(component.SkeletonDataAsset)) return; SpineMaskUtilities.EditorInitMaskMaterials(component, component.maskMaterials, maskType); } static void EditorDeleteMaskMaterials(SkeletonRenderer component, SpriteMaskInteraction maskType) { if (component == null) return; - if (!SkeletonDataAssetIsValid(component.SkeletonDataAsset)) return; + if (!SpineEditorUtilities.SkeletonDataAssetIsValid(component.SkeletonDataAsset)) return; SpineMaskUtilities.EditorDeleteMaskMaterials(component.maskMaterials, maskType); } #endif diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineEditorUtilities.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineEditorUtilities.cs index 877cd3ae6..73f098cde 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineEditorUtilities.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineEditorUtilities.cs @@ -46,6 +46,10 @@ #define NEW_PREFERENCES_SETTINGS_PROVIDER #endif +#if UNITY_2017_1_OR_NEWER +#define BUILT_IN_SPRITE_MASK_COMPONENT +#endif + using UnityEngine; using UnityEditor; using System.Collections.Generic; @@ -189,6 +193,51 @@ namespace Spine.Unity.Editor { } } + public static void ReloadSkeletonDataAssetAndComponent (SkeletonRenderer component) { + if (component == null) return; + ReloadSkeletonDataAsset(component.skeletonDataAsset); + ReinitializeComponent(component); + } + + public static void ReloadSkeletonDataAssetAndComponent (SkeletonGraphic component) { + if (component == null) return; + ReloadSkeletonDataAsset(component.skeletonDataAsset); + // Reinitialize. + ReinitializeComponent(component); + } + + public static void ReloadSkeletonDataAsset (SkeletonDataAsset skeletonDataAsset) { + if (skeletonDataAsset != null) { + foreach (AtlasAssetBase aa in skeletonDataAsset.atlasAssets) { + if (aa != null) aa.Clear(); + } + skeletonDataAsset.Clear(); + } + skeletonDataAsset.GetSkeletonData(true); + } + + public static void ReinitializeComponent (SkeletonRenderer component) { + if (component == null) return; + if (!SkeletonDataAssetIsValid(component.SkeletonDataAsset)) return; + component.Initialize(true); + + #if BUILT_IN_SPRITE_MASK_COMPONENT + SpineMaskUtilities.EditorAssignSpriteMaskMaterials(component); + #endif + component.LateUpdate(); + } + + public static void ReinitializeComponent (SkeletonGraphic component) { + if (component == null) return; + if (!SkeletonDataAssetIsValid(component.SkeletonDataAsset)) return; + component.Initialize(true); + component.LateUpdate(); + } + + public static bool SkeletonDataAssetIsValid (SkeletonDataAsset asset) { + return asset != null && asset.GetSkeletonData(quiet: true) != null; + } + public static bool IssueWarningsForUnrecommendedTextureSettings(string texturePath) { TextureImporter texImporter = (TextureImporter)TextureImporter.GetAtPath(texturePath); From 254baf4d75850a2a733f631ede33941f686d78be Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Mon, 22 Jun 2020 14:38:33 +0200 Subject: [PATCH 09/24] [cocos2d-x] Fixed broken mesh indices when RegionAttachment alpha == 0. Closes #1708. --- spine-cocos2dx/src/spine/SkeletonRenderer.cpp | 73 ++++++++----------- 1 file changed, 29 insertions(+), 44 deletions(-) diff --git a/spine-cocos2dx/src/spine/SkeletonRenderer.cpp b/spine-cocos2dx/src/spine/SkeletonRenderer.cpp index 569c69d1a..7c589364b 100644 --- a/spine-cocos2dx/src/spine/SkeletonRenderer.cpp +++ b/spine-cocos2dx/src/spine/SkeletonRenderer.cpp @@ -48,6 +48,7 @@ namespace spine { bool cullRectangle(Renderer* renderer, const Mat4& transform, const cocos2d::Rect& rect); Color4B ColorToColor4B(const Color& color); bool slotIsOutRange(Slot& slot, int startSlotIndex, int endSlotIndex); + bool nothingToDraw(Slot& slot, int startSlotIndex, int endSlotIndex); } // C Variable length array @@ -301,18 +302,7 @@ namespace spine { for (int i = 0, n = _skeleton->getSlots().size(); i < n; ++i) { Slot* slot = _skeleton->getDrawOrder()[i];; - if (slotIsOutRange(*slot, _startSlotIndex, _endSlotIndex)) { - _clipper->clipEnd(*slot); - continue; - } - - if (!slot->getAttachment()) { - _clipper->clipEnd(*slot); - continue; - } - - // Early exit if slot is invisible - if (slot->getColor().a == 0 || !slot->getBone().isActive()) { + if (nothingToDraw(*slot, _startSlotIndex, _endSlotIndex)) { _clipper->clipEnd(*slot); continue; } @@ -324,12 +314,6 @@ namespace spine { RegionAttachment* attachment = static_cast(slot->getAttachment()); attachmentVertices = static_cast(attachment->getRendererObject()); - // Early exit if attachment is invisible - if (attachment->getColor().a == 0) { - _clipper->clipEnd(*slot); - continue; - } - float* dstTriangleVertices = nullptr; int dstStride = 0; // in floats if (hasSingleTint) { @@ -556,7 +540,7 @@ namespace spine { } } } else { - + #if COCOS2D_VERSION < 0x00040000 TwoColorTrianglesCommand* batchedTriangles = lastTwoColorTrianglesCommand = twoColorBatch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture->getName(), _glProgramState, blendFunc, trianglesTwoColor, transform, transformFlags); #else @@ -641,7 +625,7 @@ namespace spine { #endif DrawNode* drawNode = DrawNode::create(); - drawNode->setGlobalZOrder(getGlobalZOrder()); + drawNode->setGlobalZOrder(getGlobalZOrder()); // Draw bounding rectangle if (_debugBoundingRect) { @@ -935,21 +919,28 @@ namespace spine { return startSlotIndex > index || endSlotIndex < index; } + bool nothingToDraw(Slot& slot, int startSlotIndex, int endSlotIndex) { + Attachment *attachment = slot.getAttachment(); + if (!attachment || + slotIsOutRange(slot, startSlotIndex, endSlotIndex) || + !slot.getBone().isActive() || + slot.getColor().a == 0) + return true; + if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) { + if (static_cast(attachment)->getColor().a == 0) + return true; + } + return false; + } + int computeTotalCoordCount(Skeleton& skeleton, int startSlotIndex, int endSlotIndex) { int coordCount = 0; for (size_t i = 0; i < skeleton.getSlots().size(); ++i) { Slot& slot = *skeleton.getSlots()[i]; + if (nothingToDraw(slot, startSlotIndex, endSlotIndex)) { + continue; + } Attachment* const attachment = slot.getAttachment(); - if (!attachment) { - continue; - } - if (slotIsOutRange(slot, startSlotIndex, endSlotIndex)) { - continue; - } - // Early exit if slot is invisible - if (slot.getColor().a == 0) { - continue; - } if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) { coordCount += 8; } @@ -969,16 +960,10 @@ namespace spine { #endif for (size_t i = 0; i < skeleton.getSlots().size(); ++i) { /*const*/ Slot& slot = *skeleton.getDrawOrder()[i]; // match the draw order of SkeletonRenderer::Draw + if (nothingToDraw(slot, startSlotIndex, endSlotIndex)) { + continue; + } Attachment* const attachment = slot.getAttachment(); - if (!attachment) { - continue; - } - if (slotIsOutRange(slot, startSlotIndex, endSlotIndex)) { - continue; - } - if (slot.getColor().a == 0) { - continue; - } if (attachment->getRTTI().isExactly(RegionAttachment::rtti)) { RegionAttachment* const regionAttachment = static_cast(attachment); assert(dstPtr + 8 <= dstEnd); @@ -1010,7 +995,7 @@ namespace spine { BlendFunc makeBlendFunc(BlendMode blendMode, bool premultipliedAlpha) { BlendFunc blendFunc; - + #if COCOS2D_VERSION < 0x00040000 switch (blendMode) { case BlendMode_Additive: @@ -1056,15 +1041,15 @@ namespace spine { bool cullRectangle(Renderer* renderer, const Mat4& transform, const cocos2d::Rect& rect) { if (Camera::getVisitingCamera() == nullptr) return false; - + auto director = Director::getInstance(); auto scene = director->getRunningScene(); - + if (!scene || (scene && Camera::getDefaultCamera() != Camera::getVisitingCamera())) return false; Rect visibleRect(director->getVisibleOrigin(), director->getVisibleSize()); - + // transform center point to screen space float hSizeX = rect.size.width/2; float hSizeY = rect.size.height/2; @@ -1075,7 +1060,7 @@ namespace spine { // convert content size to world coordinates float wshw = std::max(fabsf(hSizeX * transform.m[0] + hSizeY * transform.m[4]), fabsf(hSizeX * transform.m[0] - hSizeY * transform.m[4])); float wshh = std::max(fabsf(hSizeX * transform.m[1] + hSizeY * transform.m[5]), fabsf(hSizeX * transform.m[1] - hSizeY * transform.m[5])); - + // enlarge visible rect half size in screen coord visibleRect.origin.x -= wshw; visibleRect.origin.y -= wshh; From 46a0ffce502536891f954ddbe66be49f1859175a Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Mon, 22 Jun 2020 16:48:14 +0200 Subject: [PATCH 10/24] [cocos2d-x] Added early out check for MeshAttachment alpha == 0. Related to commit 254baf4. See #1708. --- spine-cocos2dx/src/spine/SkeletonRenderer.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spine-cocos2dx/src/spine/SkeletonRenderer.cpp b/spine-cocos2dx/src/spine/SkeletonRenderer.cpp index 7c589364b..d7331158f 100644 --- a/spine-cocos2dx/src/spine/SkeletonRenderer.cpp +++ b/spine-cocos2dx/src/spine/SkeletonRenderer.cpp @@ -930,6 +930,10 @@ namespace spine { if (static_cast(attachment)->getColor().a == 0) return true; } + else if (attachment->getRTTI().isExactly(MeshAttachment::rtti)) { + if (static_cast(attachment)->getColor().a == 0) + return true; + } return false; } From 5b65f3f1558a36a36198339317e9abdcaa41fc81 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Wed, 24 Jun 2020 11:44:10 +0200 Subject: [PATCH 11/24] [csharp] Avoiding reallocations in Animation ctor by adding all HashSet entries at once. Closes #1709. --- spine-csharp/src/Animation.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/spine-csharp/src/Animation.cs b/spine-csharp/src/Animation.cs index e0107fb84..70c1b33ee 100644 --- a/spine-csharp/src/Animation.cs +++ b/spine-csharp/src/Animation.cs @@ -43,9 +43,13 @@ namespace Spine { public Animation (string name, ExposedList timelines, float duration) { if (name == null) throw new ArgumentNullException("name", "name cannot be null."); if (timelines == null) throw new ArgumentNullException("timelines", "timelines cannot be null."); - this.timelineIds = new HashSet(); - foreach (Timeline timeline in timelines) - timelineIds.Add(timeline.PropertyId); + // Note: avoiding reallocations by adding all hash set entries at + // once (EnsureCapacity() is only available in newer .Net versions). + int[] propertyIDs = new int[timelines.Count]; + for (int i = 0; i < timelines.Count; ++i) { + propertyIDs[i] = timelines.Items[i].PropertyId; + } + this.timelineIds = new HashSet(propertyIDs); this.name = name; this.timelines = timelines; this.duration = duration; From 7ecc3b6f95f7c953efa0fe32e120461a934b90ea Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Wed, 24 Jun 2020 16:04:36 +0200 Subject: [PATCH 12/24] [unity] Reload during Play mode now copies registered event subscribers from before reload. Closes #1704. --- spine-csharp/src/AnimationState.cs | 19 +++++++++++++++++++ .../Editor/Utility/SpineEditorUtilities.cs | 13 ++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/spine-csharp/src/AnimationState.cs b/spine-csharp/src/AnimationState.cs index 67c66a8d1..808373047 100644 --- a/spine-csharp/src/AnimationState.cs +++ b/spine-csharp/src/AnimationState.cs @@ -85,6 +85,25 @@ namespace Spine { public delegate void TrackEntryEventDelegate (TrackEntry trackEntry, Event e); public event TrackEntryEventDelegate Event; + + public void AssignEventSubscribersFrom (AnimationState src) { + Event = src.Event; + Start = src.Start; + Interrupt = src.Interrupt; + End = src.End; + Dispose = src.Dispose; + Complete = src.Complete; + } + + public void AddEventSubscribersFrom (AnimationState src) { + Event += src.Event; + Start += src.Start; + Interrupt += src.Interrupt; + End += src.End; + Dispose += src.Dispose; + Complete += src.Complete; + } + // end of difference private readonly EventQueue queue; // Initialized by constructor. private readonly HashSet propertyIDs = new HashSet(); diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineEditorUtilities.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineEditorUtilities.cs index 73f098cde..fe841c71c 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineEditorUtilities.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineEditorUtilities.cs @@ -219,7 +219,18 @@ namespace Spine.Unity.Editor { public static void ReinitializeComponent (SkeletonRenderer component) { if (component == null) return; if (!SkeletonDataAssetIsValid(component.SkeletonDataAsset)) return; - component.Initialize(true); + + var stateComponent = component as IAnimationStateComponent; + AnimationState oldAnimationState = null; + if (stateComponent != null) { + oldAnimationState = stateComponent.AnimationState; + } + + component.Initialize(true); // implicitly clears any subscribers + + if (oldAnimationState != null) { + stateComponent.AnimationState.AssignEventSubscribersFrom(oldAnimationState); + } #if BUILT_IN_SPRITE_MASK_COMPONENT SpineMaskUtilities.EditorAssignSpriteMaskMaterials(component); From f946fd8b561f80e3931fca287db6151ad742a540 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Thu, 25 Jun 2020 15:58:39 +0200 Subject: [PATCH 13/24] [unity] Fixed SkeletonGraphic Mesh memory leak on Initialize(). Closes #1714. --- .../spine-unity/Components/SkeletonGraphic.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs index 2956a56d6..84034da24 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs @@ -426,7 +426,7 @@ namespace Spine.Unity { ScaleY = this.initialFlipY ? -1 : 1 }; - meshBuffers = new DoubleBuffered(); + InitMeshBuffers(); baseTexture = skeletonDataAsset.atlasAssets[0].PrimaryMaterial.mainTexture; canvasRenderer.SetTexture(this.mainTexture); // Needed for overwriting initializations. @@ -478,6 +478,16 @@ namespace Spine.Unity { } #endregion + protected void InitMeshBuffers () { + if (meshBuffers != null) { + meshBuffers.GetNext().Clear(); + meshBuffers.GetNext().Clear(); + } + else { + meshBuffers = new DoubleBuffered(); + } + } + protected void UpdateMeshSingleCanvasRenderer () { if (canvasRenderers.Count > 0) DisableUnusedCanvasRenderers(usedCount : 0); From f8574f1392530cad968b54281677f437660ebeec Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Tue, 30 Jun 2020 21:00:15 +0200 Subject: [PATCH 14/24] [unity] Fixed a bug at SkeletonGraphic where child renderers were still shown although the parent is disabled when `Multiple CanvasRenders` is enabled. Closes #1715. --- .../Runtime/spine-unity/Components/SkeletonGraphic.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs index 84034da24..3ea70fbea 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs @@ -210,6 +210,13 @@ namespace Spine.Unity { if (allowMultipleCanvasRenderers) canvasRenderer.Clear(); } + protected override void OnDisable () { + base.OnDisable(); + foreach (var canvasRenderer in canvasRenderers) { + canvasRenderer.Clear(); + } + } + public virtual void Update () { #if UNITY_EDITOR if (!Application.isPlaying) { From 528ab0cff27d74715d35e48513baab5d2f238e08 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Fri, 3 Jul 2020 14:54:23 +0200 Subject: [PATCH 15/24] [unity] Added Root Motion support for `SkeletonAnimation`, `SkeletonMecanim` and `SkeletonGraphic`. See #1417. --- CHANGELOG.md | 1 + .../Components/SkeletonAnimationInspector.cs | 2 + .../Components/SkeletonGraphicInspector.cs | 2 + .../Components/SkeletonMecanimInspector.cs | 104 +++++++++- .../SkeletonMecanimRootMotionInspector.cs | 81 ++++++++ ...SkeletonMecanimRootMotionInspector.cs.meta | 12 ++ .../Components/SkeletonRendererInspector.cs | 43 +++- .../SkeletonRootMotionBaseInspector.cs | 92 +++++++++ .../SkeletonRootMotionBaseInspector.cs.meta | 12 ++ .../Components/SkeletonRootMotionInspector.cs | 79 ++++++++ .../SkeletonRootMotionInspector.cs.meta | 12 ++ .../spine-unity/Components/RootMotion.meta | 9 + .../RootMotion/SkeletonMecanimRootMotion.cs | 100 ++++++++++ .../SkeletonMecanimRootMotion.cs.meta | 12 ++ .../RootMotion/SkeletonRootMotion.cs | 143 ++++++++++++++ .../RootMotion/SkeletonRootMotion.cs.meta | 12 ++ .../RootMotion/SkeletonRootMotionBase.cs | 184 ++++++++++++++++++ .../RootMotion/SkeletonRootMotionBase.cs.meta | 12 ++ .../spine-unity/Components/SkeletonMecanim.cs | 162 ++++++++++----- 19 files changed, 1020 insertions(+), 54 deletions(-) create mode 100644 spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimRootMotionInspector.cs create mode 100644 spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimRootMotionInspector.cs.meta create mode 100644 spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionBaseInspector.cs create mode 100644 spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionBaseInspector.cs.meta create mode 100644 spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionInspector.cs create mode 100644 spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionInspector.cs.meta create mode 100644 spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion.meta create mode 100644 spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonMecanimRootMotion.cs create mode 100644 spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonMecanimRootMotion.cs.meta create mode 100644 spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotion.cs create mode 100644 spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotion.cs.meta create mode 100644 spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs create mode 100644 spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs.meta diff --git a/CHANGELOG.md b/CHANGELOG.md index eca45dc80..3ca2ad615 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -231,6 +231,7 @@ * Added support for **multiple atlas textures at `SkeletonGraphic`**. You can enable this feature by enabling the parameter `Multiple CanvasRenders` in the `Advanced` section of the `SkeletonGraphic` Inspector. This automatically creates the required number of child `CanvasRenderer` GameObjects for each required draw call (submesh). * Added support for **Render Separator Slots** at `SkeletonGraphic`. Render separation can be enabled directly in the `Advanced` section of the `SkeletonGraphic` Inspector, it does not require any additional components (like `SkeletonRenderSeparator` or `SkeletonPartsRenderer` for `SkeletonRenderer` components). When enabled, additional separator GameObjects will be created automatically for each separation part, and `CanvasRenderer` GameObjects re-parented to them accordingly. The separator GameObjects can be moved around and re-parented in the hierarchy according to your requirements to achieve the desired draw order within your `Canvas`. A usage example can be found in the updated `Spine Examples/Other Examples/SkeletonRenderSeparator` scene. * Added `SkeletonGraphicCustomMaterials` component, providing functionality to override materials and textures of a `SkeletonGraphic`, similar to `SkeletonRendererCustomMaterials`. Note: overriding materials or textures per slot is not provided due to structural limitations. + * Added **Root Motion support** for `SkeletonAnimation`, `SkeletonMecanim` and `SkeletonGraphic` via new components `SkeletonRootMotion` and `SkeletonMecanimRootMotion`. The `SkeletonAnimation` and `SkeletonGraphic` component Inspector now provides a line `Root Motion` with `Add Component` and `Remove Component` buttons to add/remove the new `SkeletonRootMotion` component to your GameObject. The `SkeletonMecanim` Inspector detects whether root motion is enabled at the `Animator` component and adds a `SkeletonMecanimRootMotion` component automatically. * **Changes of default values** * `SkeletonMecanim`'s `Layer Mix Mode` now defaults to `MixMode.MixNext` instead of `MixMode.MixAlways`. diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonAnimationInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonAnimationInspector.cs index 328766fda..c87ee6262 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonAnimationInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonAnimationInspector.cs @@ -72,7 +72,9 @@ namespace Spine.Unity.Editor { var component = o as SkeletonAnimation; component.timeScale = Mathf.Max(component.timeScale, 0); } + EditorGUILayout.Space(); + SkeletonRootMotionParameter(); if (!isInspectingPrefab) { if (requireRepaint) { diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs index 61044ed31..5f9c5942a 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonGraphicInspector.cs @@ -234,6 +234,8 @@ namespace Spine.Unity.Editor { EditorGUILayout.Space(); EditorGUILayout.PropertyField(freeze); EditorGUILayout.Space(); + SkeletonRendererInspector.SkeletonRootMotionParameter(targets); + EditorGUILayout.Space(); EditorGUILayout.LabelField("UI", EditorStyles.boldLabel); EditorGUILayout.PropertyField(raycastTarget); diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimInspector.cs index 4ecce5385..9f75dee10 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimInspector.cs @@ -30,21 +30,119 @@ // Contributed by: Mitch Thompson using UnityEditor; +using UnityEngine; namespace Spine.Unity.Editor { [CustomEditor(typeof(SkeletonMecanim))] [CanEditMultipleObjects] public class SkeletonMecanimInspector : SkeletonRendererInspector { - protected SerializedProperty mecanimTranslator; + public static bool mecanimSettingsFoldout; + + protected SerializedProperty autoReset; + protected SerializedProperty layerMixModes; + protected SerializedProperty layerBlendModes; protected override void OnEnable () { base.OnEnable(); - mecanimTranslator = serializedObject.FindProperty("translator"); + SerializedProperty mecanimTranslator = serializedObject.FindProperty("translator"); + autoReset = mecanimTranslator.FindPropertyRelative("autoReset"); + layerMixModes = mecanimTranslator.FindPropertyRelative("layerMixModes"); + layerBlendModes = mecanimTranslator.FindPropertyRelative("layerBlendModes"); } protected override void DrawInspectorGUI (bool multi) { + + AddRootMotionComponentIfEnabled(); + base.DrawInspectorGUI(multi); - EditorGUILayout.PropertyField(mecanimTranslator, true); + + using (new SpineInspectorUtility.BoxScope()) { + mecanimSettingsFoldout = EditorGUILayout.Foldout(mecanimSettingsFoldout, "Mecanim Translator"); + if (mecanimSettingsFoldout) { + EditorGUILayout.PropertyField(autoReset, new GUIContent("Auto Reset", + "When set to true, the skeleton state is mixed out to setup-" + + "pose when an animation finishes, according to the " + + "animation's keyed items.")); + + EditorGUILayout.Space(); + DrawLayerSettings(); + EditorGUILayout.Space(); + } + } + } + + protected void AddRootMotionComponentIfEnabled () { + foreach (var t in targets) { + var component = t as Component; + var animator = component.GetComponent(); + if (animator != null && animator.applyRootMotion) { + if (component.GetComponent() == null) { + component.gameObject.AddComponent(); + } + } + } + } + + protected void DrawLayerSettings () { + string[] layerNames = GetLayerNames(); + float widthLayerColumn = 140; + float widthMixColumn = 84; + + using (new GUILayout.HorizontalScope()) { + var rect = GUILayoutUtility.GetRect(EditorGUIUtility.currentViewWidth, EditorGUIUtility.singleLineHeight); + rect.width = widthLayerColumn; + EditorGUI.LabelField(rect, SpineInspectorUtility.TempContent("Mecanim Layer"), EditorStyles.boldLabel); + + var savedIndent = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; + + rect.position += new Vector2(rect.width, 0); + rect.width = widthMixColumn; + EditorGUI.LabelField(rect, SpineInspectorUtility.TempContent("Mix Mode"), EditorStyles.boldLabel); + + EditorGUI.indentLevel = savedIndent; + } + + using (new SpineInspectorUtility.IndentScope()) { + int layerCount = layerMixModes.arraySize; + for (int i = 0; i < layerCount; ++i) { + using (new GUILayout.HorizontalScope()) { + string layerName = i < layerNames.Length ? layerNames[i] : ("Layer " + i); + + var rect = GUILayoutUtility.GetRect(EditorGUIUtility.currentViewWidth, EditorGUIUtility.singleLineHeight); + rect.width = widthLayerColumn; + EditorGUI.PrefixLabel(rect, SpineInspectorUtility.TempContent(layerName)); + + var savedIndent = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; + + var mixMode = layerMixModes.GetArrayElementAtIndex(i); + var blendMode = layerBlendModes.GetArrayElementAtIndex(i); + rect.position += new Vector2(rect.width, 0); + rect.width = widthMixColumn; + EditorGUI.PropertyField(rect, mixMode, GUIContent.none); + + EditorGUI.indentLevel = savedIndent; + } + } + } + } + + protected string[] GetLayerNames () { + int maxLayerCount = 0; + int maxIndex = 0; + for (int i = 0; i < targets.Length; ++i) { + var skeletonMecanim = ((SkeletonMecanim)targets[i]); + int count = skeletonMecanim.Translator.MecanimLayerCount; + if (count > maxLayerCount) { + maxLayerCount = count; + maxIndex = i; + } + } + if (maxLayerCount == 0) + return new string[0]; + var skeletonMecanimMaxLayers = ((SkeletonMecanim)targets[maxIndex]); + return skeletonMecanimMaxLayers.Translator.MecanimLayerNames; } } } diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimRootMotionInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimRootMotionInspector.cs new file mode 100644 index 000000000..bcdbbaab3 --- /dev/null +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimRootMotionInspector.cs @@ -0,0 +1,81 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, 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. + *****************************************************************************/ + +using UnityEditor; +using UnityEngine; + +namespace Spine.Unity.Editor { + [CustomEditor(typeof(SkeletonMecanimRootMotion))] + [CanEditMultipleObjects] + public class SkeletonMecanimRootMotionInspector : SkeletonRootMotionBaseInspector { + protected SerializedProperty mecanimLayerFlags; + + protected GUIContent mecanimLayersLabel; + + protected override void OnEnable () { + base.OnEnable(); + mecanimLayerFlags = serializedObject.FindProperty("mecanimLayerFlags"); + + mecanimLayersLabel = new UnityEngine.GUIContent("Mecanim Layers", "Mecanim layers to apply root motion at. Defaults to the first Mecanim layer."); + } + + override public void OnInspectorGUI () { + + base.MainPropertyFields(); + MecanimLayerMaskPropertyField(); + + base.OptionalPropertyFields(); + serializedObject.ApplyModifiedProperties(); + } + + protected string[] GetLayerNames () { + int maxLayerCount = 0; + int maxIndex = 0; + for (int i = 0; i < targets.Length; ++i) { + var skeletonMecanim = ((SkeletonMecanimRootMotion)targets[i]).SkeletonMecanim; + int count = skeletonMecanim.Translator.MecanimLayerCount; + if (count > maxLayerCount) { + maxLayerCount = count; + maxIndex = i; + } + } + if (maxLayerCount == 0) + return new string[0]; + var skeletonMecanimMaxLayers = ((SkeletonMecanimRootMotion)targets[maxIndex]).SkeletonMecanim; + return skeletonMecanimMaxLayers.Translator.MecanimLayerNames; + } + + protected void MecanimLayerMaskPropertyField () { + string[] layerNames = GetLayerNames(); + if (layerNames.Length > 0) + mecanimLayerFlags.intValue = EditorGUILayout.MaskField( + mecanimLayersLabel, mecanimLayerFlags.intValue, layerNames); + } + } +} diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimRootMotionInspector.cs.meta b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimRootMotionInspector.cs.meta new file mode 100644 index 000000000..04c6dcfc9 --- /dev/null +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimRootMotionInspector.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 4613924c50d66cf458f0db803776dd2f +timeCreated: 1593175106 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRendererInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRendererInspector.cs index 4ada78b48..8942660dd 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRendererInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRendererInspector.cs @@ -106,7 +106,6 @@ namespace Spine.Unity.Editor { #else isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab); #endif - SpineEditorUtilities.ConfirmInitialization(); // Labels @@ -398,6 +397,48 @@ namespace Spine.Unity.Editor { } } + protected void SkeletonRootMotionParameter() { + SkeletonRootMotionParameter(targets); + } + + public static void SkeletonRootMotionParameter(Object[] targets) { + int rootMotionComponentCount = 0; + foreach (var t in targets) { + var component = t as Component; + if (component.GetComponent() != null) { + ++rootMotionComponentCount; + } + } + bool allHaveRootMotion = rootMotionComponentCount == targets.Length; + bool anyHaveRootMotion = rootMotionComponentCount > 0; + + using (new GUILayout.HorizontalScope()) { + EditorGUILayout.PrefixLabel("Root Motion"); + + if (!allHaveRootMotion) { + if (GUILayout.Button(SpineInspectorUtility.TempContent("Add Component", Icons.constraintTransform), GUILayout.MaxWidth(130), GUILayout.Height(18))) { + foreach (var t in targets) { + var component = t as Component; + if (component.GetComponent() == null) { + component.gameObject.AddComponent(); + } + } + } + } + if (anyHaveRootMotion) { + if (GUILayout.Button(SpineInspectorUtility.TempContent("Remove Component", Icons.constraintTransform), GUILayout.MaxWidth(140), GUILayout.Height(18))) { + foreach (var t in targets) { + var component = t as Component; + var rootMotionComponent = component.GetComponent(); + if (rootMotionComponent != null) { + DestroyImmediate(rootMotionComponent); + } + } + } + } + } + } + public static void SetSeparatorSlotNames (SkeletonRenderer skeletonRenderer, string[] newSlotNames) { var field = SpineInspectorUtility.GetNonPublicField(typeof(SkeletonRenderer), SeparatorSlotNamesFieldName); field.SetValue(skeletonRenderer, newSlotNames); diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionBaseInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionBaseInspector.cs new file mode 100644 index 000000000..dd622e479 --- /dev/null +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionBaseInspector.cs @@ -0,0 +1,92 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, 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. + *****************************************************************************/ + +using UnityEditor; +using UnityEngine; + +namespace Spine.Unity.Editor { + [CustomEditor(typeof(SkeletonRootMotionBase))] + [CanEditMultipleObjects] + public class SkeletonRootMotionBaseInspector : UnityEditor.Editor { + protected SerializedProperty rootMotionBoneName; + protected SerializedProperty transformPositionX; + protected SerializedProperty transformPositionY; + protected SerializedProperty rigidBody2D; + protected SerializedProperty rigidBody; + + protected GUIContent rootMotionBoneNameLabel; + protected GUIContent transformPositionXLabel; + protected GUIContent transformPositionYLabel; + protected GUIContent rigidBody2DLabel; + protected GUIContent rigidBodyLabel; + + protected virtual void OnEnable () { + + rootMotionBoneName = serializedObject.FindProperty("rootMotionBoneName"); + transformPositionX = serializedObject.FindProperty("transformPositionX"); + transformPositionY = serializedObject.FindProperty("transformPositionY"); + rigidBody2D = serializedObject.FindProperty("rigidBody2D"); + rigidBody = serializedObject.FindProperty("rigidBody"); + + rootMotionBoneNameLabel = new UnityEngine.GUIContent("Root Motion Bone", "The bone to take the motion from."); + transformPositionXLabel = new UnityEngine.GUIContent("X", "Root transform position (X)"); + transformPositionYLabel = new UnityEngine.GUIContent("Y", "Use the Y-movement of the bone."); + rigidBody2DLabel = new UnityEngine.GUIContent("Rigidbody2D", + "Optional Rigidbody2D: Assign a Rigidbody2D here if you want " + + " to apply the root motion to the rigidbody instead of the Transform." + + "\n\n" + + "Note that animation and physics updates are not always in sync." + + "Some jitter may result at certain framerates."); + rigidBodyLabel = new UnityEngine.GUIContent("Rigidbody", + "Optional Rigidbody: Assign a Rigidbody here if you want " + + " to apply the root motion to the rigidbody instead of the Transform." + + "\n\n" + + "Note that animation and physics updates are not always in sync." + + "Some jitter may result at certain framerates."); + } + + public override void OnInspectorGUI () { + MainPropertyFields(); + OptionalPropertyFields(); + serializedObject.ApplyModifiedProperties(); + } + + protected virtual void MainPropertyFields () { + EditorGUILayout.PropertyField(rootMotionBoneName, rootMotionBoneNameLabel); + EditorGUILayout.PropertyField(transformPositionX, transformPositionXLabel); + EditorGUILayout.PropertyField(transformPositionY, transformPositionYLabel); + } + + protected virtual void OptionalPropertyFields () { + //EditorGUILayout.LabelField("Optional", EditorStyles.boldLabel); + EditorGUILayout.PropertyField(rigidBody2D, rigidBody2DLabel); + EditorGUILayout.PropertyField(rigidBody, rigidBodyLabel); + } + } +} diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionBaseInspector.cs.meta b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionBaseInspector.cs.meta new file mode 100644 index 000000000..030444885 --- /dev/null +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionBaseInspector.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: f2cba83baf6afdf44a996e40017c6325 +timeCreated: 1593175106 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionInspector.cs new file mode 100644 index 000000000..2c9650656 --- /dev/null +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionInspector.cs @@ -0,0 +1,79 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, 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. + *****************************************************************************/ + +using UnityEditor; +using UnityEngine; + +namespace Spine.Unity.Editor { + [CustomEditor(typeof(SkeletonRootMotion))] + [CanEditMultipleObjects] + public class SkeletonRootMotionInspector : SkeletonRootMotionBaseInspector { + protected SerializedProperty animationTrackFlags; + protected GUIContent animationTrackFlagsLabel; + + string[] TrackNames; + + protected override void OnEnable () { + base.OnEnable(); + + animationTrackFlags = serializedObject.FindProperty("animationTrackFlags"); + animationTrackFlagsLabel = new UnityEngine.GUIContent("Animation Tracks", + "Animation tracks to apply root motion at. Defaults to the first" + + " animation track (index 0)."); + } + + override public void OnInspectorGUI () { + + base.MainPropertyFields(); + AnimationTracksPropertyField(); + + base.OptionalPropertyFields(); + serializedObject.ApplyModifiedProperties(); + } + + protected void AnimationTracksPropertyField () { + + if (TrackNames == null) { + InitTrackNames(); + + } + + animationTrackFlags.intValue = EditorGUILayout.MaskField( + animationTrackFlagsLabel, animationTrackFlags.intValue, TrackNames); + } + + protected void InitTrackNames () { + int numEntries = 32; + TrackNames = new string[numEntries]; + for (int i = 0; i < numEntries; ++i) { + TrackNames[i] = string.Format("Track {0}", i); + } + } + } +} diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionInspector.cs.meta b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionInspector.cs.meta new file mode 100644 index 000000000..7c357a899 --- /dev/null +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRootMotionInspector.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: e4836100aed984c4a9af11d39c63cb6b +timeCreated: 1593183609 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion.meta b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion.meta new file mode 100644 index 000000000..d8d3bd381 --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 2b957aa69dae9f948bacdeec549d28ea +folderAsset: yes +timeCreated: 1593173800 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonMecanimRootMotion.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonMecanimRootMotion.cs new file mode 100644 index 000000000..e49e8a486 --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonMecanimRootMotion.cs @@ -0,0 +1,100 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, 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. + *****************************************************************************/ + +using UnityEngine; +using System.Collections.Generic; +using Spine.Unity.AnimationTools; + +namespace Spine.Unity { + + /// + /// Add this component to a SkeletonMecanim GameObject + /// to turn motion of a selected root bone into Transform or RigidBody motion. + /// Local bone translation movement is used as motion. + /// All top-level bones of the skeleton are moved to compensate the root + /// motion bone location, keeping the distance relationship between bones intact. + /// + /// + /// Only compatible with SkeletonMecanim. + /// For SkeletonAnimation or SkeletonGraphic please use + /// SkeletonRootMotion instead. + /// + public class SkeletonMecanimRootMotion : SkeletonRootMotionBase { + #region Inspector + const int DefaultMecanimLayerFlags = -1; + public int mecanimLayerFlags = DefaultMecanimLayerFlags; + #endregion + + protected Vector2 movementDelta; + + SkeletonMecanim skeletonMecanim; + public SkeletonMecanim SkeletonMecanim { + get { + return skeletonMecanim ? skeletonMecanim : skeletonMecanim = GetComponent(); + } + } + + protected override void Reset () { + base.Reset(); + mecanimLayerFlags = DefaultMecanimLayerFlags; + } + + protected override void Start () { + base.Start(); + skeletonMecanim = GetComponent(); + if (skeletonMecanim) { + skeletonMecanim.Translator.OnClipApplied -= OnClipApplied; + skeletonMecanim.Translator.OnClipApplied += OnClipApplied; + } + } + + void OnClipApplied(Spine.Animation clip, int layerIndex, float weight, + float time, float lastTime, bool playsBackward) { + + if (((mecanimLayerFlags & 1< + /// Add this component to a SkeletonAnimation or SkeletonGraphic GameObject + /// to turn motion of a selected root bone into Transform or RigidBody motion. + /// Local bone translation movement is used as motion. + /// All top-level bones of the skeleton are moved to compensate the root + /// motion bone location, keeping the distance relationship between bones intact. + /// + /// + /// Only compatible with SkeletonAnimation (or other components that implement + /// ISkeletonComponent, ISkeletonAnimation and IAnimationStateComponent). + /// For SkeletonMecanim please use + /// SkeletonMecanimRootMotion instead. + /// + public class SkeletonRootMotion : SkeletonRootMotionBase { + #region Inspector + const int DefaultAnimationTrackFlags = -1; + public int animationTrackFlags = DefaultAnimationTrackFlags; + #endregion + + AnimationState animationState; + Canvas canvas; + + protected override float AdditionalScale { + get { + return canvas ? canvas.referencePixelsPerUnit: 1.0f; + } + } + + protected override void Reset () { + base.Reset(); + animationTrackFlags = DefaultAnimationTrackFlags; + } + + protected override void Start () { + base.Start(); + var animstateComponent = skeletonComponent as IAnimationStateComponent; + this.animationState = (animstateComponent != null) ? animstateComponent.AnimationState : null; + + if (this.GetComponent() != null) { + canvas = this.GetComponentInParent(); + } + } + + protected override Vector2 CalculateAnimationsMovementDelta () { + Vector2 localDelta = Vector2.zero; + int trackCount = animationState.Tracks.Count; + + for (int trackIndex = 0; trackIndex < trackCount; ++trackIndex) { + // note: animationTrackFlags != -1 below covers trackIndex >= 32, + // with -1 corresponding to entry "everything" of the dropdown list. + if (animationTrackFlags != -1 && (animationTrackFlags & 1 << trackIndex) == 0) + continue; + + TrackEntry track = animationState.GetCurrent(trackIndex); + TrackEntry next = null; + while (track != null) { + var animation = track.Animation; + var timeline = animation.FindTranslateTimelineForBone(rootMotionBoneIndex); + if (timeline != null) { + var currentDelta = GetTrackMovementDelta(track, timeline, animation, next); + localDelta += currentDelta; + } + // Traverse mixingFrom chain. + next = track; + track = track.mixingFrom; + } + } + return localDelta; + } + + Vector2 GetTrackMovementDelta (TrackEntry track, TranslateTimeline timeline, + Animation animation, TrackEntry next) { + + float start = track.animationLast; + float end = track.AnimationTime; + Vector2 currentDelta = GetTimelineMovementDelta(start, end, timeline, animation); + + ApplyMixAlphaToDelta(ref currentDelta, next, track); + return currentDelta; + } + + void ApplyMixAlphaToDelta (ref Vector2 currentDelta, TrackEntry next, TrackEntry track) { + // Apply mix alpha to the delta position (based on AnimationState.cs). + float mix; + if (next != null) { + if (next.mixDuration == 0) { // Single frame mix to undo mixingFrom changes. + mix = 1; + } + else { + mix = next.mixTime / next.mixDuration; + if (mix > 1) mix = 1; + } + float mixAndAlpha = track.alpha * next.interruptAlpha * (1 - mix); + currentDelta *= mixAndAlpha; + } + else { + if (track.mixDuration == 0) { + mix = 1; + } + else { + mix = track.alpha * (track.mixTime / track.mixDuration); + if (mix > 1) mix = 1; + } + currentDelta *= mix; + } + } + } +} diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotion.cs.meta b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotion.cs.meta new file mode 100644 index 000000000..bef9e2e4f --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotion.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: f21c9538588898a45a3da22bf4779ab3 +timeCreated: 1591121072 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs new file mode 100644 index 000000000..5b1e50dbb --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs @@ -0,0 +1,184 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, 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. + *****************************************************************************/ + +using UnityEngine; +using System.Collections.Generic; +using Spine.Unity.AnimationTools; + +namespace Spine.Unity { + + /// + /// Base class for skeleton root motion components. + /// + abstract public class SkeletonRootMotionBase : MonoBehaviour { + #region Inspector + + [SpineBone] + [SerializeField] + protected string rootMotionBoneName = "root"; + public bool transformPositionX = true; + public bool transformPositionY = true; + + [Header("Optional")] + public Rigidbody2D rigidBody2D; + public Rigidbody rigidBody; + + public bool UsesRigidbody { + get { return rigidBody != null || rigidBody2D != null; } + } + #endregion + + protected ISkeletonComponent skeletonComponent; + protected Bone rootMotionBone; + protected int rootMotionBoneIndex; + protected List topLevelBones = new List(); + protected Vector2 rigidbodyDisplacement; + + protected virtual void Reset () { + FindRigidbodyComponent(); + } + + protected virtual void Start () { + skeletonComponent = GetComponent(); + GatherTopLevelBones(); + SetRootMotionBone(rootMotionBoneName); + + var skeletonAnimation = skeletonComponent as ISkeletonAnimation; + if (skeletonAnimation != null) + skeletonAnimation.UpdateLocal += HandleUpdateLocal; + } + + abstract protected Vector2 CalculateAnimationsMovementDelta (); + + protected virtual float AdditionalScale { get { return 1.0f; } } + + protected Vector2 GetTimelineMovementDelta (float startTime, float endTime, + TranslateTimeline timeline, Animation animation) { + + Vector2 currentDelta; + if (startTime > endTime) // Looped + currentDelta = (timeline.Evaluate(animation.duration) - timeline.Evaluate(startTime)) + + (timeline.Evaluate(endTime) - timeline.Evaluate(0)); + else if (startTime != endTime) // Non-looped + currentDelta = timeline.Evaluate(endTime) - timeline.Evaluate(startTime); + else + currentDelta = Vector2.zero; + return currentDelta; + } + + void GatherTopLevelBones () { + topLevelBones.Clear(); + var skeleton = skeletonComponent.Skeleton; + foreach (var bone in skeleton.Bones) { + if (bone.Parent == null) + topLevelBones.Add(bone); + } + } + + public void SetRootMotionBone (string name) { + var skeleton = skeletonComponent.Skeleton; + int index = skeleton.FindBoneIndex(name); + if (index >= 0) { + this.rootMotionBoneIndex = index; + this.rootMotionBone = skeleton.bones.Items[index]; + } + else { + Debug.Log("Bone named \"" + name + "\" could not be found."); + this.rootMotionBoneIndex = 0; + this.rootMotionBone = skeleton.RootBone; + } + } + + void HandleUpdateLocal (ISkeletonAnimation animatedSkeletonComponent) { + if (!this.isActiveAndEnabled) + return; // Root motion is only applied when component is enabled. + + var movementDelta = CalculateAnimationsMovementDelta(); + AdjustMovementDeltaToConfiguration(ref movementDelta, animatedSkeletonComponent.Skeleton); + ApplyRootMotion(movementDelta); + } + + void AdjustMovementDeltaToConfiguration (ref Vector2 localDelta, Skeleton skeleton) { + if (skeleton.ScaleX < 0) localDelta.x = -localDelta.x; + if (skeleton.ScaleY < 0) localDelta.y = -localDelta.y; + if (!transformPositionX) localDelta.x = 0f; + if (!transformPositionY) localDelta.y = 0f; + } + + void ApplyRootMotion (Vector2 localDelta) { + localDelta *= AdditionalScale; + // Apply root motion to Transform or RigidBody; + if (UsesRigidbody) { + rigidbodyDisplacement += (Vector2)transform.TransformVector(localDelta); + // Accumulated displacement is applied on the next Physics update (FixedUpdate) + } + else { + + transform.position += transform.TransformVector(localDelta); + } + + // Move top level bones in opposite direction of the root motion bone + foreach (var topLevelBone in topLevelBones) { + if (transformPositionX) topLevelBone.x -= rootMotionBone.x; + if (transformPositionY) topLevelBone.y -= rootMotionBone.y; + } + } + + protected virtual void FixedUpdate () { + if (!this.isActiveAndEnabled) + return; // Root motion is only applied when component is enabled. + + if(rigidBody2D != null) { + rigidBody2D.MovePosition(new Vector2(transform.position.x, transform.position.y) + + rigidbodyDisplacement); + } + if (rigidBody != null) { + rigidBody.MovePosition(transform.position + + new Vector3(rigidbodyDisplacement.x, rigidbodyDisplacement.y, 0)); + } + rigidbodyDisplacement = Vector2.zero; + } + + protected virtual void OnDisable () { + rigidbodyDisplacement = Vector2.zero; + } + + protected void FindRigidbodyComponent () { + rigidBody2D = this.GetComponent(); + if (!rigidBody2D) + rigidBody = this.GetComponent(); + + if (!rigidBody2D && !rigidBody) { + rigidBody2D = this.GetComponentInParent(); + if (!rigidBody2D) + rigidBody = this.GetComponentInParent(); + } + } + } +} diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs.meta b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs.meta new file mode 100644 index 000000000..719fd629f --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/RootMotion/SkeletonRootMotionBase.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: fc23a4f220b20024ab0592719f92587d +timeCreated: 1592849332 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs index bfc993b35..83c6de8b0 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs @@ -129,6 +129,12 @@ namespace Spine.Unity { public MixBlend[] layerBlendModes = new MixBlend[0]; #endregion + public delegate void OnClipAppliedDelegate (Spine.Animation clip, int layerIndex, float weight, + float time, float lastTime, bool playsBackward); + protected event OnClipAppliedDelegate _OnClipApplied; + + public event OnClipAppliedDelegate OnClipApplied { add { _OnClipApplied += value; } remove { _OnClipApplied -= value; } } + public enum MixMode { AlwaysMix, MixNext, Hard } readonly Dictionary animationTable = new Dictionary(IntEqualityComparer.Instance); @@ -157,6 +163,26 @@ namespace Spine.Unity { Animator animator; public Animator Animator { get { return this.animator; } } + public int MecanimLayerCount { + get { + if (!animator) + return 0; + return animator.layerCount; + } + } + + public string[] MecanimLayerNames { + get { + if (!animator) + return new string[0]; + string[] layerNames = new string[animator.layerCount]; + for (int i = 0; i < animator.layerCount; ++i) { + layerNames[i] = animator.GetLayerName(i); + } + return layerNames; + } + } + public void Initialize(Animator animator, SkeletonDataAsset skeletonDataAsset) { this.animator = animator; @@ -171,10 +197,71 @@ namespace Spine.Unity { ClearClipInfosForLayers(); } + private bool ApplyAnimation (Skeleton skeleton, AnimatorClipInfo info, AnimatorStateInfo stateInfo, + int layerIndex, float layerWeight, MixBlend layerBlendMode, bool useWeight1 = false) { + float weight = info.weight * layerWeight; + if (weight == 0) + return false; + + var clip = GetAnimation(info.clip); + if (clip == null) + return false; + + var time = AnimationTime(stateInfo.normalizedTime, info.clip.length, + info.clip.isLooping, stateInfo.speed < 0); + weight = useWeight1 ? 1.0f : weight; + clip.Apply(skeleton, 0, time, info.clip.isLooping, null, + weight, layerBlendMode, MixDirection.In); + if (_OnClipApplied != null) + OnClipAppliedCallback(clip, stateInfo, layerIndex, time, info.clip.isLooping, weight); + return true; + } + + private bool ApplyInterruptionAnimation (Skeleton skeleton, + bool interpolateWeightTo1, AnimatorClipInfo info, AnimatorStateInfo stateInfo, + int layerIndex, float layerWeight, MixBlend layerBlendMode, float interruptingClipTimeAddition, + bool useWeight1 = false) { + + float clipWeight = interpolateWeightTo1 ? (info.weight + 1.0f) * 0.5f : info.weight; + float weight = clipWeight * layerWeight; + if (weight == 0) + return false; + + var clip = GetAnimation(info.clip); + if (clip == null) + return false; + + var time = AnimationTime(stateInfo.normalizedTime + interruptingClipTimeAddition, + info.clip.length, stateInfo.speed < 0); + weight = useWeight1 ? 1.0f : weight; + clip.Apply(skeleton, 0, time, info.clip.isLooping, null, + weight, layerBlendMode, MixDirection.In); + if (_OnClipApplied != null) { + OnClipAppliedCallback(clip, stateInfo, layerIndex, time, info.clip.isLooping, weight); + } + return true; + } + + private void OnClipAppliedCallback (Spine.Animation clip, AnimatorStateInfo stateInfo, + int layerIndex, float time, bool isLooping, float weight) { + + float clipDuration = clip.duration == 0 ? 1 : clip.duration; + float speedFactor = stateInfo.speedMultiplier * stateInfo.speed; + float lastTime = time - (Time.deltaTime * speedFactor); + if (isLooping && clip.duration != 0) { + time %= clip.duration; + lastTime %= clip.duration; + } + _OnClipApplied(clip, layerIndex, weight, time, lastTime, speedFactor < 0); + } + public void Apply (Skeleton skeleton) { if (layerMixModes.Length < animator.layerCount) { + int oldSize = layerMixModes.Length; System.Array.Resize(ref layerMixModes, animator.layerCount); - layerMixModes[animator.layerCount-1] = MixMode.MixNext; + for (int layer = oldSize; layer < animator.layerCount; ++layer) { + layerMixModes[layer] = layer == 0 ? MixMode.MixNext : MixMode.AlwaysMix; + } } #if UNITY_EDITOR @@ -257,56 +344,41 @@ namespace Spine.Unity { int clipInfoCount, nextClipInfoCount, interruptingClipInfoCount; IList clipInfo, nextClipInfo, interruptingClipInfo; - bool shallInterpolateWeightTo1; + bool interpolateWeightTo1; GetAnimatorClipInfos(layer, out isInterruptionActive, out clipInfoCount, out nextClipInfoCount, out interruptingClipInfoCount, - out clipInfo, out nextClipInfo, out interruptingClipInfo, out shallInterpolateWeightTo1); + out clipInfo, out nextClipInfo, out interruptingClipInfo, out interpolateWeightTo1); MixMode mode = layerMixModes[layer]; MixBlend layerBlendMode = (layer < layerBlendModes.Length) ? layerBlendModes[layer] : MixBlend.Replace; if (mode == MixMode.AlwaysMix) { // Always use Mix instead of Applying the first non-zero weighted clip. for (int c = 0; c < clipInfoCount; c++) { - var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; - var clip = GetAnimation(info.clip); - if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, info.clip.isLooping, stateInfo.speed < 0), info.clip.isLooping, null, weight, layerBlendMode, MixDirection.In); + ApplyAnimation(skeleton, clipInfo[c], stateInfo, layer, layerWeight, layerBlendMode); } if (hasNext) { for (int c = 0; c < nextClipInfoCount; c++) { - var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; - var clip = GetAnimation(info.clip); - if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime, info.clip.length, nextStateInfo.speed < 0), info.clip.isLooping, null, weight, layerBlendMode, MixDirection.In); + ApplyAnimation(skeleton, nextClipInfo[c], nextStateInfo, layer, layerWeight, layerBlendMode); } } if (isInterruptionActive) { for (int c = 0; c < interruptingClipInfoCount; c++) { - var info = interruptingClipInfo[c]; - float clipWeight = shallInterpolateWeightTo1 ? (info.weight + 1.0f) * 0.5f : info.weight; - float weight = clipWeight * layerWeight; if (weight == 0) continue; - var clip = GetAnimation(info.clip); - if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(interruptingStateInfo.normalizedTime + interruptingClipTimeAddition, info.clip.length, interruptingStateInfo.speed < 0), - info.clip.isLooping, null, weight, layerBlendMode, MixDirection.In); + ApplyInterruptionAnimation(skeleton, interpolateWeightTo1, + interruptingClipInfo[c], interruptingStateInfo, + layer, layerWeight, layerBlendMode, interruptingClipTimeAddition); } } } else { // case MixNext || Hard // Apply first non-zero weighted clip int c = 0; for (; c < clipInfoCount; c++) { - var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; - var clip = GetAnimation(info.clip); - if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, info.clip.isLooping, stateInfo.speed < 0), info.clip.isLooping, null, 1f, layerBlendMode, MixDirection.In); + if (!ApplyAnimation(skeleton, clipInfo[c], stateInfo, layer, layerWeight, layerBlendMode, useWeight1:true)) + continue; ++c; break; } // Mix the rest for (; c < clipInfoCount; c++) { - var info = clipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; - var clip = GetAnimation(info.clip); - if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(stateInfo.normalizedTime, info.clip.length, info.clip.isLooping, stateInfo.speed < 0), info.clip.isLooping, null, weight, layerBlendMode, MixDirection.In); + ApplyAnimation(skeleton, clipInfo[c], stateInfo, layer, layerWeight, layerBlendMode); } c = 0; @@ -314,19 +386,15 @@ namespace Spine.Unity { // Apply next clip directly instead of mixing (ie: no crossfade, ignores mecanim transition weights) if (mode == MixMode.Hard) { for (; c < nextClipInfoCount; c++) { - var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; - var clip = GetAnimation(info.clip); - if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime, info.clip.length, nextStateInfo.speed < 0), info.clip.isLooping, null, 1f, layerBlendMode, MixDirection.In); + if (!ApplyAnimation(skeleton, nextClipInfo[c], nextStateInfo, layer, layerWeight, layerBlendMode, useWeight1:true)) + continue; ++c; break; } } // Mix the rest for (; c < nextClipInfoCount; c++) { - var info = nextClipInfo[c]; float weight = info.weight * layerWeight; if (weight == 0) continue; - var clip = GetAnimation(info.clip); - if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(nextStateInfo.normalizedTime, info.clip.length, nextStateInfo.speed < 0), info.clip.isLooping, null, weight, layerBlendMode, MixDirection.In); + if (!ApplyAnimation(skeleton, nextClipInfo[c], nextStateInfo, layer, layerWeight, layerBlendMode)) + continue; } } @@ -335,23 +403,19 @@ namespace Spine.Unity { // Apply next clip directly instead of mixing (ie: no crossfade, ignores mecanim transition weights) if (mode == MixMode.Hard) { for (; c < interruptingClipInfoCount; c++) { - var info = interruptingClipInfo[c]; - float clipWeight = shallInterpolateWeightTo1 ? (info.weight + 1.0f) * 0.5f : info.weight; - float weight = clipWeight * layerWeight; if (weight == 0) continue; - var clip = GetAnimation(info.clip); - if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(interruptingStateInfo.normalizedTime + interruptingClipTimeAddition, info.clip.length, interruptingStateInfo.speed < 0), info.clip.isLooping, null, 1f, layerBlendMode, MixDirection.In); - ++c; break; + if (ApplyInterruptionAnimation(skeleton, interpolateWeightTo1, + interruptingClipInfo[c], interruptingStateInfo, + layer, layerWeight, layerBlendMode, interruptingClipTimeAddition, useWeight1:true)) { + + ++c; break; + } } } // Mix the rest for (; c < interruptingClipInfoCount; c++) { - var info = interruptingClipInfo[c]; - float clipWeight = shallInterpolateWeightTo1 ? (info.weight + 1.0f) * 0.5f : info.weight; - float weight = clipWeight * layerWeight; if (weight == 0) continue; - var clip = GetAnimation(info.clip); - if (clip != null) - clip.Apply(skeleton, 0, AnimationTime(interruptingStateInfo.normalizedTime + interruptingClipTimeAddition, info.clip.length, interruptingStateInfo.speed < 0), info.clip.isLooping, null, weight, layerBlendMode, MixDirection.In); + ApplyInterruptionAnimation(skeleton, interpolateWeightTo1, + interruptingClipInfo[c], interruptingStateInfo, + layer, layerWeight, layerBlendMode, interruptingClipTimeAddition); } } } @@ -359,9 +423,7 @@ namespace Spine.Unity { } static float AnimationTime (float normalizedTime, float clipLength, bool loop, bool reversed) { - if (reversed) - normalizedTime = (1 - normalizedTime + (int)normalizedTime) + (int)normalizedTime; - float time = normalizedTime * clipLength; + float time = AnimationTime(normalizedTime, clipLength, reversed); if (loop) return time; const float EndSnapEpsilon = 1f / 30f; // Workaround for end-duration keys not being applied. return (clipLength - time < EndSnapEpsilon) ? clipLength : time; // return a time snapped to clipLength; From 981a3015fae553f01da0b197c02f526c72f28544 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Fri, 3 Jul 2020 15:23:27 +0200 Subject: [PATCH 16/24] [unity] Added file missing in last commit 528ab0c. See #1417. --- .../spine-unity/Utility/TimelineExtensions.cs | 91 +++++++++++++++++++ .../Utility/TimelineExtensions.cs.meta | 12 +++ 2 files changed, 103 insertions(+) create mode 100644 spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs create mode 100644 spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs.meta diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs new file mode 100644 index 000000000..03f14aa55 --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs @@ -0,0 +1,91 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, 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. + *****************************************************************************/ + +using UnityEngine; +using System.Collections.Generic; +using System.Collections; + +namespace Spine.Unity.AnimationTools { + public static class TimelineExtensions { + + /// Evaluates the resulting value of a TranslateTimeline at a given time. + /// SkeletonData can be accessed from Skeleton.Data or from SkeletonDataAsset.GetSkeletonData. + /// If no SkeletonData is given, values are computed relative to setup pose instead of local-absolute. + public static Vector2 Evaluate (this TranslateTimeline timeline, float time, SkeletonData skeletonData = null) { + const int PREV_TIME = -3, PREV_X = -2, PREV_Y = -1; + const int X = 1, Y = 2; + + var frames = timeline.frames; + if (time < frames[0]) return Vector2.zero; + + float x, y; + if (time >= frames[frames.Length - TranslateTimeline.ENTRIES]) { // Time is after last frame. + x = frames[frames.Length + PREV_X]; + y = frames[frames.Length + PREV_Y]; + } + else { + // Interpolate between the previous frame and the current frame. + int frame = Animation.BinarySearch(frames, time, TranslateTimeline.ENTRIES); + x = frames[frame + PREV_X]; + y = frames[frame + PREV_Y]; + float frameTime = frames[frame]; + float percent = timeline.GetCurvePercent(frame / TranslateTimeline.ENTRIES - 1, + 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime)); + + x += (frames[frame + X] - x) * percent; + y += (frames[frame + Y] - y) * percent; + } + + Vector2 xy = new Vector2(x, y); + if (skeletonData == null) { + return xy; + } + else { + var boneData = skeletonData.bones.Items[timeline.boneIndex]; + return xy + new Vector2(boneData.x, boneData.y); + } + } + + /// Gets the translate timeline for a given boneIndex. + /// You can get the boneIndex using SkeletonData.FindBoneIndex. + /// The root bone is always boneIndex 0. + /// This will return null if a TranslateTimeline is not found. + public static TranslateTimeline FindTranslateTimelineForBone (this Animation a, int boneIndex) { + foreach (var timeline in a.timelines) { + if (timeline.GetType().IsSubclassOf(typeof(TranslateTimeline))) + continue; + + var translateTimeline = timeline as TranslateTimeline; + if (translateTimeline != null && translateTimeline.boneIndex == boneIndex) + return translateTimeline; + } + return null; + } + } +} diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs.meta b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs.meta new file mode 100644 index 000000000..6198f84aa --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/TimelineExtensions.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 07807fefbff25484ba41b1d16911fb0e +timeCreated: 1591974498 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From a062dd7b5ab918b61ee7ab60eab0a152ccc8de4f Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Mon, 6 Jul 2020 20:06:15 +0200 Subject: [PATCH 17/24] [unity] BoundingBoxFollower `Add Bone Follower` button was always disabled. Also calling BoundingBoxFollower.Initialize() explicitly in editor code now (might resolve some problems with recent Unity versions). Closes #1717. --- .../Editor/Components/BoundingBoxFollowerInspector.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoundingBoxFollowerInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoundingBoxFollowerInspector.cs index 294882945..7a0e87cf2 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoundingBoxFollowerInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoundingBoxFollowerInspector.cs @@ -160,6 +160,8 @@ namespace Spine.Unity.Editor { } + if (follower.Slot == null) + follower.Initialize(false); bool hasBoneFollower = follower.GetComponent() != null; if (!hasBoneFollower) { bool buttonDisabled = follower.Slot == null; From 7626f3b58ac50ac6cd8606fa9823f22e90990049 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Wed, 8 Jul 2020 20:04:09 +0200 Subject: [PATCH 18/24] [unity] Fixed SkeletonMecanim blend result weights at mode `MixNext`, no longer bahaving unexpectedly. At additive layers, `MixMode.MixNext` is always set to `MixMode.AlwaysMix` since `MixNext` makes no sense there. Closes #1718. --- .../spine-unity/Components/SkeletonMecanim.cs | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs index 83c6de8b0..a15d93e30 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs @@ -198,7 +198,7 @@ namespace Spine.Unity { } private bool ApplyAnimation (Skeleton skeleton, AnimatorClipInfo info, AnimatorStateInfo stateInfo, - int layerIndex, float layerWeight, MixBlend layerBlendMode, bool useWeight1 = false) { + int layerIndex, float layerWeight, MixBlend layerBlendMode, bool useClipWeight1 = false) { float weight = info.weight * layerWeight; if (weight == 0) return false; @@ -209,7 +209,7 @@ namespace Spine.Unity { var time = AnimationTime(stateInfo.normalizedTime, info.clip.length, info.clip.isLooping, stateInfo.speed < 0); - weight = useWeight1 ? 1.0f : weight; + weight = useClipWeight1 ? layerWeight : weight; clip.Apply(skeleton, 0, time, info.clip.isLooping, null, weight, layerBlendMode, MixDirection.In); if (_OnClipApplied != null) @@ -220,7 +220,7 @@ namespace Spine.Unity { private bool ApplyInterruptionAnimation (Skeleton skeleton, bool interpolateWeightTo1, AnimatorClipInfo info, AnimatorStateInfo stateInfo, int layerIndex, float layerWeight, MixBlend layerBlendMode, float interruptingClipTimeAddition, - bool useWeight1 = false) { + bool useClipWeight1 = false) { float clipWeight = interpolateWeightTo1 ? (info.weight + 1.0f) * 0.5f : info.weight; float weight = clipWeight * layerWeight; @@ -233,7 +233,7 @@ namespace Spine.Unity { var time = AnimationTime(stateInfo.normalizedTime + interruptingClipTimeAddition, info.clip.length, stateInfo.speed < 0); - weight = useWeight1 ? 1.0f : weight; + weight = useClipWeight1 ? layerWeight : weight; clip.Apply(skeleton, 0, time, info.clip.isLooping, null, weight, layerBlendMode, MixDirection.In); if (_OnClipApplied != null) { @@ -256,26 +256,28 @@ namespace Spine.Unity { } public void Apply (Skeleton skeleton) { - if (layerMixModes.Length < animator.layerCount) { - int oldSize = layerMixModes.Length; - System.Array.Resize(ref layerMixModes, animator.layerCount); - for (int layer = oldSize; layer < animator.layerCount; ++layer) { - layerMixModes[layer] = layer == 0 ? MixMode.MixNext : MixMode.AlwaysMix; - } - } - #if UNITY_EDITOR if (!Application.isPlaying) { GetLayerBlendModes(); } #endif + + if (layerMixModes.Length < animator.layerCount) { + int oldSize = layerMixModes.Length; + System.Array.Resize(ref layerMixModes, animator.layerCount); + for (int layer = oldSize; layer < animator.layerCount; ++layer) { + bool isAdditiveLayer = false; + if (layer < layerBlendModes.Length) + isAdditiveLayer = layerBlendModes[layer] == MixBlend.Add; + layerMixModes[layer] = isAdditiveLayer ? MixMode.MixNext : MixMode.AlwaysMix; + } + } + InitClipInfosForLayers(); for (int layer = 0, n = animator.layerCount; layer < n; layer++) { GetStateUpdatesFromAnimator(layer); } - //skeleton.Update(Time.deltaTime); // Doesn't actually do anything, currently. (Spine 3.6). - // Clear Previous if (autoReset) { var previousAnimations = this.previousAnimations; @@ -348,8 +350,15 @@ namespace Spine.Unity { GetAnimatorClipInfos(layer, out isInterruptionActive, out clipInfoCount, out nextClipInfoCount, out interruptingClipInfoCount, out clipInfo, out nextClipInfo, out interruptingClipInfo, out interpolateWeightTo1); - MixMode mode = layerMixModes[layer]; MixBlend layerBlendMode = (layer < layerBlendModes.Length) ? layerBlendModes[layer] : MixBlend.Replace; + MixMode mode = layerMixModes[layer]; + // Note: at additive blending it makes no sense to use constant weight 1 at a fadeout anim add1 as + // with override layers, so we use AlwaysMix instead to use the proper weights. + // AlwaysMix leads to the expected result = lower_layer + lerp(add1, add2, transition_weight). + if (layerBlendMode == MixBlend.Add && mode == MixMode.MixNext) { + mode = MixMode.AlwaysMix; + layerMixModes[layer] = mode; + } if (mode == MixMode.AlwaysMix) { // Always use Mix instead of Applying the first non-zero weighted clip. for (int c = 0; c < clipInfoCount; c++) { @@ -372,7 +381,7 @@ namespace Spine.Unity { // Apply first non-zero weighted clip int c = 0; for (; c < clipInfoCount; c++) { - if (!ApplyAnimation(skeleton, clipInfo[c], stateInfo, layer, layerWeight, layerBlendMode, useWeight1:true)) + if (!ApplyAnimation(skeleton, clipInfo[c], stateInfo, layer, layerWeight, layerBlendMode, useClipWeight1:true)) continue; ++c; break; } @@ -386,7 +395,7 @@ namespace Spine.Unity { // Apply next clip directly instead of mixing (ie: no crossfade, ignores mecanim transition weights) if (mode == MixMode.Hard) { for (; c < nextClipInfoCount; c++) { - if (!ApplyAnimation(skeleton, nextClipInfo[c], nextStateInfo, layer, layerWeight, layerBlendMode, useWeight1:true)) + if (!ApplyAnimation(skeleton, nextClipInfo[c], nextStateInfo, layer, layerWeight, layerBlendMode, useClipWeight1:true)) continue; ++c; break; } @@ -405,7 +414,7 @@ namespace Spine.Unity { for (; c < interruptingClipInfoCount; c++) { if (ApplyInterruptionAnimation(skeleton, interpolateWeightTo1, interruptingClipInfo[c], interruptingStateInfo, - layer, layerWeight, layerBlendMode, interruptingClipTimeAddition, useWeight1:true)) { + layer, layerWeight, layerBlendMode, interruptingClipTimeAddition, useClipWeight1:true)) { ++c; break; } From 8a444681d229de72f2eee342aae5c125fe9a772b Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Fri, 10 Jul 2020 14:46:06 +0200 Subject: [PATCH 19/24] [unity] `SkeletonMecanim` now provides additional parameter `Custom MixMode` which can be disabled to use recommended MixMode settings. By default the parameter is enabled to maintain current 3.8 behaviour. --- CHANGELOG.md | 1 + .../Components/SkeletonMecanimInspector.cs | 12 ++++++-- .../Components/SkeletonRendererInspector.cs | 7 ++--- .../spine-unity/Components/SkeletonMecanim.cs | 29 +++++++++++++------ 4 files changed, 32 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ca2ad615..55ba29640 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -232,6 +232,7 @@ * Added support for **Render Separator Slots** at `SkeletonGraphic`. Render separation can be enabled directly in the `Advanced` section of the `SkeletonGraphic` Inspector, it does not require any additional components (like `SkeletonRenderSeparator` or `SkeletonPartsRenderer` for `SkeletonRenderer` components). When enabled, additional separator GameObjects will be created automatically for each separation part, and `CanvasRenderer` GameObjects re-parented to them accordingly. The separator GameObjects can be moved around and re-parented in the hierarchy according to your requirements to achieve the desired draw order within your `Canvas`. A usage example can be found in the updated `Spine Examples/Other Examples/SkeletonRenderSeparator` scene. * Added `SkeletonGraphicCustomMaterials` component, providing functionality to override materials and textures of a `SkeletonGraphic`, similar to `SkeletonRendererCustomMaterials`. Note: overriding materials or textures per slot is not provided due to structural limitations. * Added **Root Motion support** for `SkeletonAnimation`, `SkeletonMecanim` and `SkeletonGraphic` via new components `SkeletonRootMotion` and `SkeletonMecanimRootMotion`. The `SkeletonAnimation` and `SkeletonGraphic` component Inspector now provides a line `Root Motion` with `Add Component` and `Remove Component` buttons to add/remove the new `SkeletonRootMotion` component to your GameObject. The `SkeletonMecanim` Inspector detects whether root motion is enabled at the `Animator` component and adds a `SkeletonMecanimRootMotion` component automatically. + * `SkeletonMecanim` now provides an additional `Custom MixMode` parameter under `Mecanim Translator`. It is enabled by default in version 3.8 to maintain current behaviour, using the set `Mix Mode` for each Mecanim layer. When disabled, `SkeletonMecanim` will use the recommended `MixMode` according to the layer blend mode. Additional information can be found in the [Mecanim Translator section](http://esotericsoftware.com/spine-unity#Parameters-for-animation-blending-control) on the spine-unity documentation pages. * **Changes of default values** * `SkeletonMecanim`'s `Layer Mix Mode` now defaults to `MixMode.MixNext` instead of `MixMode.MixAlways`. diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimInspector.cs index 9f75dee10..65929f084 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonMecanimInspector.cs @@ -39,6 +39,7 @@ namespace Spine.Unity.Editor { public static bool mecanimSettingsFoldout; protected SerializedProperty autoReset; + protected SerializedProperty useCustomMixMode; protected SerializedProperty layerMixModes; protected SerializedProperty layerBlendModes; @@ -46,6 +47,7 @@ namespace Spine.Unity.Editor { base.OnEnable(); SerializedProperty mecanimTranslator = serializedObject.FindProperty("translator"); autoReset = mecanimTranslator.FindPropertyRelative("autoReset"); + useCustomMixMode = mecanimTranslator.FindPropertyRelative("useCustomMixMode"); layerMixModes = mecanimTranslator.FindPropertyRelative("layerMixModes"); layerBlendModes = mecanimTranslator.FindPropertyRelative("layerBlendModes"); } @@ -64,9 +66,13 @@ namespace Spine.Unity.Editor { "pose when an animation finishes, according to the " + "animation's keyed items.")); - EditorGUILayout.Space(); - DrawLayerSettings(); - EditorGUILayout.Space(); + EditorGUILayout.PropertyField(useCustomMixMode, new GUIContent("Custom MixMode", + "When disabled, the recommended MixMode is used according to the layer blend mode. Enable to specify a custom MixMode for each Mecanim layer.")); + + if (useCustomMixMode.hasMultipleDifferentValues || useCustomMixMode.boolValue == true) { + DrawLayerSettings(); + EditorGUILayout.Space(); + } } } } diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRendererInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRendererInspector.cs index 8942660dd..62f50ed22 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRendererInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/SkeletonRendererInspector.cs @@ -172,11 +172,8 @@ namespace Spine.Unity.Editor { if (serializedObject.ApplyModifiedProperties() || SpineInspectorUtility.UndoRedoPerformed(Event.current) || AreAnyMaskMaterialsMissing()) { if (!Application.isPlaying) { - if (multi) { - foreach (var o in targets) SpineEditorUtilities.ReinitializeComponent((SkeletonRenderer)o); - } else { - SpineEditorUtilities.ReinitializeComponent((SkeletonRenderer)target); - } + foreach (var o in targets) + SpineEditorUtilities.ReinitializeComponent((SkeletonRenderer)o); SceneView.RepaintAll(); } } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs index a15d93e30..d85390b18 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs @@ -125,6 +125,7 @@ namespace Spine.Unity { public class MecanimTranslator { #region Inspector public bool autoReset = true; + public bool useCustomMixMode = true; public MixMode[] layerMixModes = new MixMode[0]; public MixBlend[] layerBlendModes = new MixBlend[0]; #endregion @@ -351,14 +352,7 @@ namespace Spine.Unity { out clipInfo, out nextClipInfo, out interruptingClipInfo, out interpolateWeightTo1); MixBlend layerBlendMode = (layer < layerBlendModes.Length) ? layerBlendModes[layer] : MixBlend.Replace; - MixMode mode = layerMixModes[layer]; - // Note: at additive blending it makes no sense to use constant weight 1 at a fadeout anim add1 as - // with override layers, so we use AlwaysMix instead to use the proper weights. - // AlwaysMix leads to the expected result = lower_layer + lerp(add1, add2, transition_weight). - if (layerBlendMode == MixBlend.Add && mode == MixMode.MixNext) { - mode = MixMode.AlwaysMix; - layerMixModes[layer] = mode; - } + MixMode mode = GetMixMode(layer, layerBlendMode); if (mode == MixMode.AlwaysMix) { // Always use Mix instead of Applying the first non-zero weighted clip. for (int c = 0; c < clipInfoCount; c++) { @@ -469,7 +463,24 @@ namespace Spine.Unity { } } - #if UNITY_EDITOR + private MixMode GetMixMode (int layer, MixBlend layerBlendMode) { + if (useCustomMixMode) { + MixMode mode = layerMixModes[layer]; + // Note: at additive blending it makes no sense to use constant weight 1 at a fadeout anim add1 as + // with override layers, so we use AlwaysMix instead to use the proper weights. + // AlwaysMix leads to the expected result = lower_layer + lerp(add1, add2, transition_weight). + if (layerBlendMode == MixBlend.Add && mode == MixMode.MixNext) { + mode = MixMode.AlwaysMix; + layerMixModes[layer] = mode; + } + return mode; + } + else { + return layerBlendMode == MixBlend.Add ? MixMode.AlwaysMix : MixMode.MixNext; + } + } + +#if UNITY_EDITOR void GetLayerBlendModes() { if (layerBlendModes.Length < animator.layerCount) { System.Array.Resize(ref layerBlendModes, animator.layerCount); From 84443288b54166f793d7bcc9567f5cfd00ff5289 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Fri, 10 Jul 2020 18:37:36 +0200 Subject: [PATCH 20/24] [unity] Fixed exception when new Unity 2019.3 `Enter Play Mode` has disabled domain reload. Closes #1621. --- .../Runtime/spine-unity/Utility/AtlasUtilities.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AtlasUtilities.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AtlasUtilities.cs index ce0a60ebd..5abd195f9 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AtlasUtilities.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AtlasUtilities.cs @@ -27,10 +27,16 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ +#if UNITY_2019_3_OR_NEWER +#define CONFIGURABLE_ENTER_PLAY_MODE +#endif + + using UnityEngine; using System.Collections.Generic; using System; + namespace Spine.Unity.AttachmentTools { public static class AtlasUtilities { @@ -41,6 +47,14 @@ namespace Spine.Unity.AttachmentTools { const int NonrenderingRegion = -1; + #if CONFIGURABLE_ENTER_PLAY_MODE + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + static void Init () { + // handle disabled domain reload + AtlasUtilities.ClearCache(); + } + #endif + public static AtlasRegion ToAtlasRegion (this Texture2D t, Material materialPropertySource, float scale = DefaultScale) { return t.ToAtlasRegion(materialPropertySource.shader, scale, materialPropertySource); } From 844d13d1efc9e8e86f188f6c5568456835156d5e Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Tue, 14 Jul 2020 17:01:52 +0200 Subject: [PATCH 21/24] [unity] Fixed a warning occurring in newer Unity versions, two static extension method classes named `SkeletonExtensions` in different namespaces but the same file. --- .../Spine/Runtime/spine-unity/Utility/SkeletonExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/SkeletonExtensions.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/SkeletonExtensions.cs index 93bce0759..4ca34defb 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/SkeletonExtensions.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/SkeletonExtensions.cs @@ -442,7 +442,7 @@ namespace Spine { } } - public static class SkeletonExtensions { + public static class SpineSkeletonExtensions { public static bool IsWeighted (this VertexAttachment va) { return va.bones != null && va.bones.Length > 0; } From c6fb1795cf938969d2012de83a7c0f8c09fe4e28 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Tue, 14 Jul 2020 18:09:22 +0200 Subject: [PATCH 22/24] [unity] Fixed ZWrite NormalMap issue with Unity 2019.4.3f1 URP 7.4.1. Closes #1720. --- .../Shaders/2D/Spine-SkeletonLit-URP-2D.shader | 1 + .../Shaders/2D/Spine-Sprite-URP-2D.shader | 1 + 2 files changed, 2 insertions(+) diff --git a/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/2D/Spine-SkeletonLit-URP-2D.shader b/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/2D/Spine-SkeletonLit-URP-2D.shader index f129c2a77..7cf903eeb 100644 --- a/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/2D/Spine-SkeletonLit-URP-2D.shader +++ b/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/2D/Spine-SkeletonLit-URP-2D.shader @@ -119,6 +119,7 @@ Tags { "LightMode" = "NormalsRendering"} Blend SrcAlpha OneMinusSrcAlpha + ZWrite Off HLSLPROGRAM #pragma prefer_hlslcc gles diff --git a/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/2D/Spine-Sprite-URP-2D.shader b/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/2D/Spine-Sprite-URP-2D.shader index e1b08b626..d9f0ddbb9 100644 --- a/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/2D/Spine-Sprite-URP-2D.shader +++ b/spine-unity/Modules/com.esotericsoftware.spine.urp-shaders/Shaders/2D/Spine-Sprite-URP-2D.shader @@ -114,6 +114,7 @@ Shader "Universal Render Pipeline/2D/Spine/Sprite" Blend SrcAlpha OneMinusSrcAlpha Cull[_Cull] + ZWrite[_ZWrite] HLSLPROGRAM #pragma prefer_hlslcc gles From 498cf7ac6fccd64371c6cc4f71026936162c7a2b Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Fri, 17 Jul 2020 10:44:51 +0200 Subject: [PATCH 23/24] [unity] Changed `Spine/Sprite` shader parameter name from `Overlay Color` to `Overlay Color & Alpha`, as functionality looks broken if you miss that you have to set the alpha value to > 0. --- .../Editor/spine-unity/Editor/Shaders/SpineSpriteShaderGUI.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Shaders/SpineSpriteShaderGUI.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Shaders/SpineSpriteShaderGUI.cs index 679500e3e..bd1267232 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Shaders/SpineSpriteShaderGUI.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Shaders/SpineSpriteShaderGUI.cs @@ -169,7 +169,7 @@ public class SpineSpriteShaderGUI : SpineShaderWithOutlineGUI { static GUIContent _rimPowerText = new GUIContent("Rim Power"); static GUIContent _specularToggleText = new GUIContent("Specular", "Enable Specular."); static GUIContent _colorAdjustmentToggleText = new GUIContent("Color Adjustment", "Enable material color adjustment."); - static GUIContent _colorAdjustmentColorText = new GUIContent("Overlay Color"); + static GUIContent _colorAdjustmentColorText = new GUIContent("Overlay Color & Alpha"); static GUIContent _colorAdjustmentHueText = new GUIContent("Hue"); static GUIContent _colorAdjustmentSaturationText = new GUIContent("Saturation"); static GUIContent _colorAdjustmentBrightnessText = new GUIContent("Brightness"); From acc9e127d4590f65dacb0ef9bc711dc14de9d91a Mon Sep 17 00:00:00 2001 From: Nathan Sweet Date: Fri, 17 Jul 2020 19:50:39 +0200 Subject: [PATCH 24/24] Readme update, added barebones.html. --- spine-ts/README.md | 127 +++++++++++---------- spine-ts/webgl/demos/demos.js | 1 - spine-ts/webgl/example/barebones.html | 157 ++++++++++++++++++++++++++ spine-ts/webgl/example/index.html | 60 +++++----- 4 files changed, 257 insertions(+), 88 deletions(-) create mode 100644 spine-ts/webgl/example/barebones.html diff --git a/spine-ts/README.md b/spine-ts/README.md index d88497437..981c8286a 100644 --- a/spine-ts/README.md +++ b/spine-ts/README.md @@ -1,17 +1,18 @@ - # spine-ts +# spine-ts The spine-ts runtime provides functionality to load and manipulate [Spine](http://esotericsoftware.com) skeletal animation data using TypeScript and JavaScript. spine-ts is split up into multiple modules: -1. **Core**: `core/`, the core classes to load and process Spine models -1. **WebGL**: `webgl/`, a self-contained WebGL backend, build on the core classes -1. **Canvas**: `canvas/`, a self-contained Canvas backend, build on the core classes -1. **THREE.JS**: `threejs/`, a self-contained THREE.JS backend, build on the core classes -1. **Player**: `player/`, a self-contained player to easily display Spine animations on your website, build on core classes & WebGL backend. +1. **Core**: `core/`, the core classes to load and process Spine skeletons. +1. **WebGL**: `webgl/`, a self-contained WebGL backend, built on the core classes. +1. **Canvas**: `canvas/`, a self-contained Canvas backend, built on the core classes. +1. **THREE.JS**: `threejs/`, a self-contained THREE.JS backend, built on the core classes. +1. **Player**: `player/`, a self-contained player to easily display Spine animations on your website, built on core the classes and WebGL backend. -While the source code for the core library and backends is written in TypeScript, all code is compiled to easily consumable JavaScript. +While the source code for the core library and backends is written in TypeScript, all code is compiled to JavaScript. ## Licensing + You are welcome to evaluate the Spine Runtimes and the examples we provide in this repository free of charge. You can integrate the Spine Runtimes into your software free of charge, but users of your software must have their own [Spine license](https://esotericsoftware.com/spine-purchase). Please make your users aware of this requirement! This option is often chosen by those making development tools, such as an SDK, game toolkit, or software library. @@ -24,15 +25,14 @@ For the official legal terms governing the Spine Runtimes, please read the [Spin spine-ts works with data exported from Spine 3.8.xx. -spine-ts WebGL & players backends supports all Spine features. +The spine-ts WebGL and Player backends support all Spine features. -spine-ts Canvas does not support white space stripped texture atlases, color tinting, mesh attachments and clipping. Only the alpha channel from tint colors is applied. Experimental support for mesh attachments can be enabled by setting `spine.canvas.SkeletonRenderer.useTriangleRendering` to true. Note that this method is slow and may lead to artifacts on some browsers. +spine-ts Canvas does not support white space stripped texture atlases, color tinting, mesh attachments, or clipping. Only the alpha channel from tint colors is applied. Experimental support for mesh attachments can be enabled by setting `spine.canvas.SkeletonRenderer.useTriangleRendering` to true. Note that this experimental mesh rendering is slow and may lead to artifacts on some browsers. -spine-ts THREE.JS does not support two color tinting & blend modes. The THREE.JS backend provides `SkeletonMesh.zOffset` to avoid z-fighting. Adjust to your near/far plane settings. - -spine-ts does not yet support loading the binary format. +spine-ts THREE.JS does not support two color tinting or blend modes. The THREE.JS backend provides `SkeletonMesh.zOffset` to avoid z-fighting. Adjust to your near/far plane settings. ## Usage + 1. Download the Spine Runtimes source using [git](https://help.github.com/articles/set-up-git) or by downloading it as a zip via the download button above. 2. To use only the core library without rendering support, include the `build/spine-core.js` file in your project. 3. To use the WebGL backend, include the `build/spine-webgl.js` file in your project. @@ -42,91 +42,102 @@ spine-ts does not yet support loading the binary format. All `*.js` files are self-contained and include both the core and respective backend classes. -If you write your app with TypeScript, additionally copy the corresponding `build/spine-*.d.ts` file to your project. +If you write your app with TypeScript, additionally copy the corresponding `build/spine-*.d.ts` file into your project. -**Note:** If you are using the compiled `.js` files with ES6 or other module systems, you have to add +**Note:** If you are using the compiled `.js` files with ES6 or other module systems, you need to add: ``` export { spine }; ``` -At the bottom of the `.js` file you are using. You can then import the module as usual, e.g.: +At the bottom of the `.js` file you are using. You can then import the module as usual, for example: ``` import { spine } from './spine-webgl.js'; ``` ## Examples -To run the examples, the image, atlas, and JSON files must be served by a webserver, they can't be loaded from your local disk. Spawn a light-weight web server in the root of spine-ts, then navigate to the `index.html` file for the example you want to view. E.g.: + +To run the various examples found in each of the spine-ts backend folders, the image, atlas, and JSON files must be served by a webserver. For security reasons browsers will not load these files from your local disk. To work around this, you can spawn a lightweight web server in the spine-ts folder, then navigate to the `index.html` file for the example you want to view. For example: ``` cd spine-ts -python -m SimpleHTTPServer +python -m SimpleHTTPServer // for Python 2 +python -m http.server // for Python 3 ``` -Then open `http://localhost:8000/webgl/example`, `http://localhost:8000/canvas/example`, `https://localhost:8000/threejs/example` or `http://localhost:8000/player/example` in your browser. +Then open `http://localhost:8000/webgl/example`, `http://localhost:8000/canvas/example`, `https://localhost:8000/threejs/example`, or `http://localhost:8000/player/example` in your browser. -## WebGL Demos -The spine-ts WebGL demos load their image, atlas, and JSON files from our webserver and so can be run directly, without needing a webserver. The demos can be viewed [all on one page](http://esotericsoftware.com/spine-demos/) or in individual, standalone pages which are easy for you to explore and edit. See the [standalone demos source code](webgl/demos) and view the pages here: +### WebGL demos -- [Spine vs sprite sheets](http://rawgit.com/EsotericSoftware/spine-runtimes/3.6/spine-ts/webgl/demos/spritesheets.html) -- [Image changes](http://rawgit.com/EsotericSoftware/spine-runtimes/3.6/spine-ts/webgl/demos/imagechanges.html) -- [Transitions](http://rawgit.com/EsotericSoftware/spine-runtimes/3.6/spine-ts/webgl/demos/transitions.html) -- [Meshes](http://rawgit.com/EsotericSoftware/spine-runtimes/3.6/spine-ts/webgl/demos/meshes.html) -- [Skins](http://rawgit.com/EsotericSoftware/spine-runtimes/3.6/spine-ts/webgl/demos/skins.html) -- [Hoverboard](http://rawgit.com/EsotericSoftware/spine-runtimes/3.6/spine-ts/webgl/demos/hoverboard.html) -- [Vine](http://rawgit.com/EsotericSoftware/spine-runtimes/3.6/spine-ts/webgl/demos/vine.html) -- [Clipping](http://rawgit.com/EsotericSoftware/spine-runtimes/3.6/spine-ts/webgl/demos/clipping.html) -- [Stretchyman](http://rawgit.com/EsotericSoftware/spine-runtimes/3.6/spine-ts/webgl/demos/stretchyman.html) -- [Tank](http://rawgit.com/EsotericSoftware/spine-runtimes/3.6/spine-ts/webgl/demos/tank.html) -- [Transform constraints](http://rawgit.com/EsotericSoftware/spine-runtimes/3.6/spine-ts/webgl/demos/transforms.html) +The spine-ts WebGL demos can be viewed [all on one page](http://esotericsoftware.com/spine-demos/) or in individual, standalone pages which are easy for you to explore and edit. See the [standalone demos source code](webgl/demos) and view the pages here: + +- [Spine vs sprite sheets](http://rawgit.com/EsotericSoftware/spine-runtimes/3.8/spine-ts/webgl/demos/spritesheets.html) +- [Image changes](http://rawgit.com/EsotericSoftware/spine-runtimes/3.8/spine-ts/webgl/demos/imagechanges.html) +- [Transitions](http://rawgit.com/EsotericSoftware/spine-runtimes/3.8/spine-ts/webgl/demos/transitions.html) +- [Meshes](http://rawgit.com/EsotericSoftware/spine-runtimes/3.8/spine-ts/webgl/demos/meshes.html) +- [Skins](http://rawgit.com/EsotericSoftware/spine-runtimes/3.8/spine-ts/webgl/demos/skins.html) +- [Hoverboard](http://rawgit.com/EsotericSoftware/spine-runtimes/3.8/spine-ts/webgl/demos/hoverboard.html) +- [Vine](http://rawgit.com/EsotericSoftware/spine-runtimes/3.8/spine-ts/webgl/demos/vine.html) +- [Clipping](http://rawgit.com/EsotericSoftware/spine-runtimes/3.8/spine-ts/webgl/demos/clipping.html) +- [Stretchyman](http://rawgit.com/EsotericSoftware/spine-runtimes/3.8/spine-ts/webgl/demos/stretchyman.html) +- [Tank](http://rawgit.com/EsotericSoftware/spine-runtimes/3.8/spine-ts/webgl/demos/tank.html) +- [Transform constraints](http://rawgit.com/EsotericSoftware/spine-runtimes/3.8/spine-ts/webgl/demos/transforms.html) Please note that Chrome and possibly other browsers do not use the original CORS headers when loading cached resources. After the initial page load for a demo, you may need to forcefully refresh (hold `shift` and click refresh) or clear your browser cache. -## Development Setup -The spine-ts runtime and the various backends are implemented in TypeScript for greater maintainability and better tooling support. To -setup a development environment, follow these steps. +### WebGL examples -1. Install [NPM](https://nodejs.org/en/download/) and make sure it's available on the command line -2. On the command line, Install the TypeScript compiler via `npm install -g typescript` -3. Install [Visual Studio Code](https://code.visualstudio.com/) -4. On the command line, change into the `spine-ts` directory +The WebGL demos serve well as examples, showing various ways to use the APIs. We also provide a simple, self-contained example with UI to control the skeletons: + +- [WebGL example](http://rawgit.com/EsotericSoftware/spine-runtimes/3.8/spine-ts/webgl/example/index.html) + +A barebones example is also available. It doesn't use JQuery and shows the minimal code necessary to use spine-ts with WebGL to load and render a skeleton: + +- [WebGL barebones](http://rawgit.com/EsotericSoftware/spine-runtimes/3.8/spine-ts/webgl/example/barebones.html) + +## Development setup + +The spine-ts runtime and the various backends are implemented in TypeScript for greater maintainability and better tooling support. To setup a development environment, follow these steps: + +1. Install [NPM](https://nodejs.org/en/download/) and make sure it's available on the command line. +2. On the command line, Install the TypeScript compiler via `npm install -g typescript`. +3. Install [Visual Studio Code](https://code.visualstudio.com/). +4. On the command line, change into the `spine-ts` directory. 5. Start the TypeScript compiler in watcher mode for the backend you want to work on: - * **Core**: `tsc -w -p tsconfig.core.json`, builds `core/src`, outputs `build/spine-core.js|d.ts|js.map` - * **WebGL**: `tsc -w -p tsconfig.webgl.json`, builds `core/src` and `webgl/src`, outputs `build/spine-webgl.js|d.ts|js.map` - * **Canvas**: `tsc -w -p tsconfig.canvas.json`, builds `core/src` and `canvas/src`, outputs `build/spine-canvas.js|d.ts|js.map` - * **THREE.JS**: `tsc -w -p tsconfig.threejs.json`, builds `core/src` and `threejs/src`, outputs `build/spine-threejs.js|d.ts|js.map` - * **Player**: `tsc -w -p tsconfig.player.json`, builds `core/src` and `player/src`, outputs `build/spine-player.js|d.ts|js.map` -6. Open the `spine-ts` folder in Visual Studio Code. VS Code will use the `tsconfig.json` file all source files from core and all -backends for your development pleasure. The actual JavaScript output is still created by the command line TypeScript compiler process from the previous step. + * **Core**: `tsc -w -p tsconfig.core.json`, builds `core/src`, outputs `build/spine-core.js|d.ts|js.map`. + * **WebGL**: `tsc -w -p tsconfig.webgl.json`, builds `core/src` and `webgl/src`, outputs `build/spine-webgl.js|d.ts|js.map`. + * **Canvas**: `tsc -w -p tsconfig.canvas.json`, builds `core/src` and `canvas/src`, outputs `build/spine-canvas.js|d.ts|js.map`. + * **THREE.JS**: `tsc -w -p tsconfig.threejs.json`, builds `core/src` and `threejs/src`, outputs `build/spine-threejs.js|d.ts|js.map`. + * **Player**: `tsc -w -p tsconfig.player.json`, builds `core/src` and `player/src`, outputs `build/spine-player.js|d.ts|js.map`. +6. Open the `spine-ts` folder in Visual Studio Code. VS Code will use the `tsconfig.json` file to find all source files for your development pleasure. The actual JavaScript output is still created by the command line TypeScript compiler process from the previous step. -Each backend contains an `example/` folder with an `index.html` file that demonstrates the respective backend. For development, we -suggest to run a HTTP server in the root of `spine-ts`, e.g. +Each backend contains an `example/` folder with an `index.html` file that demonstrates the respective backend. For development, we suggest to run a HTTP server in the root of `spine-ts`, for example: ``` cd spine-ts -python -m SimpleHTTPServer +python -m SimpleHTTPServer // for Python 2 +python -m http.server // for Python 3 ``` -Then navigate to `http://localhost:8000/webgl/example`, `http://localhost:8000/canvas/example`, `http://localhost:8000/threejs/example` or `http://localhost:8000/player/example` +Then navigate to `http://localhost:8000/webgl/example`, `http://localhost:8000/canvas/example`, `http://localhost:8000/threejs/example`, or `http://localhost:8000/player/example`. -### Spine-ts WebGL backend -By default, the spine-ts WebGL backend supports two-color tinting. This has a neglible effect on performance, as more per vertex data has to be submitted to the GPU, and the fragment shader has to do a few more arithmetic operations. +### WebGL backend -You can disable two-color tinting like this: +By default, the spine-ts WebGL backend supports two-color tinting. This requires more per vertex data to be submitted to the GPU and the fragment shader has to do a few more arithmetic operations. It has a neglible effect on performance, but you can disable two-color tinting like this: ```javascript -// If you use SceneRenderer, disable two-color tinting via the last constructor argument +// If you use SceneRenderer, disable two-color tinting via the last constructor argument. var sceneRenderer = new spine.SceneRenderer(canvas, gl, false); -// If you use SkeletonRenderer and PolygonBatcher directly, -// disable two-color tinting in the respective constructor -// and use the shader returned by Shader.newColoredTextured() -// instead of Shader.newTwoColoredTextured() +// If you use SkeletonRenderer and PolygonBatcher directly, disable two-color +// tinting in the respective constructor and use the shader returned by +// Shader.newColoredTextured() instead of Shader.newTwoColoredTextured(). var batcher = new spine.PolygonBatcher(gl, false); var skeletonRenderer = new spine.SkeletonRenderer(gl, false); var shader = Shader.newColoredTextured(); ``` ### Using the Player -Please see the documentation for the [Spine Web Player](https://esotericsoftware.com/spine-player) + +Please see the documentation for the [Spine Web Player](https://esotericsoftware.com/spine-player). diff --git a/spine-ts/webgl/demos/demos.js b/spine-ts/webgl/demos/demos.js index 4b654aa84..e121f2fe3 100644 --- a/spine-ts/webgl/demos/demos.js +++ b/spine-ts/webgl/demos/demos.js @@ -4,7 +4,6 @@ $(function () { alert("Error: " + message + "\n" + "URL:" + url + "\nLine: " + lineNo); } - spineDemos.init(); spineDemos.assetManager = new spine.SharedAssetManager("assets/"); diff --git a/spine-ts/webgl/example/barebones.html b/spine-ts/webgl/example/barebones.html new file mode 100644 index 000000000..47a450156 --- /dev/null +++ b/spine-ts/webgl/example/barebones.html @@ -0,0 +1,157 @@ + + + + + + + + \ No newline at end of file diff --git a/spine-ts/webgl/example/index.html b/spine-ts/webgl/example/index.html index ab69a6e91..a6d1abd33 100644 --- a/spine-ts/webgl/example/index.html +++ b/spine-ts/webgl/example/index.html @@ -2,9 +2,9 @@ @@ -15,21 +15,22 @@ Skin: Vertex Effect: Debug: -
+
- + \ No newline at end of file