diff --git a/CHANGELOG.md b/CHANGELOG.md index 5abce6605..0c60ef462 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,6 +95,9 @@ * All Spine shaders (also including URP and LWRP shaders) now support `PMA Vertex Colors` in combination with `Linear` color space. Thus when using Spine shaders, you should always enable `PMA Vertex Colors` at the `SkeletonRenderer` component. This allows using single pass `Additive` Slots rendering. Note that textures shall still be exported as `Straight alpha` when using `Linear` color space, so combine `PMA Vertex Colors` with `Straight Texture`. All `Sprite` shaders now provide an additional blend mode for this, named `PMA Vertex, Straight Texture` which shall be the preferred Sprite shader blend mode in `Linear` color space. * Additive Slots have always been lit before they were written to the target buffer. Now all lit shaders provide an additional parameter `Light Affects Additive` which defaults to `false`, as it is the more intuitive default value. You can enable the old behaviour by setting this parameter to `true`. * `SkeletonRootMotion` and `SkeletonMecanimRootMotion` components now support arbitrary bones in the hierarchy as `Root Motion Bone`. Previously there were problems when selecting a non-root bone as `Root Motion Bone`. `Skeleton.ScaleX` and `.ScaleY` and parent bone scale is now respected as well. + * URP and LWRP `Sprite` and `SkeletonLit` shaders no longer require `Advanced - Add Normals` enabled to properly cast and receive shadows. It is recommended to disable `Add Normals` if normals are otherwise not needed. + * Added an example component `RootMotionDeltaCompensation` located in `Spine Examples/Scripts/Sample Components` which can be used for applying simple delta compensation. You can enable and disable the component to toggle delta compensation of the currently playing animation on and off. + * `SkeletonRagdoll` and `SkeletonRagdoll2D` now support bone scale at any bone in the skeleton hierarchy. This includes negative scale and root bone scale. * **Changes of default values** diff --git a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonRagdoll.cs b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonRagdoll.cs index 8deaf5205..a88194cd2 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonRagdoll.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonRagdoll.cs @@ -68,12 +68,22 @@ namespace Spine.Unity.Examples { public int colliderLayer = 0; [Range(0, 1)] public float mix = 1; - public bool oldRagdollBehaviour = true; + public bool oldRagdollBehaviour = false; #endregion ISkeletonAnimation targetSkeletonComponent; Skeleton skeleton; + struct BoneFlipEntry { + public BoneFlipEntry (bool flipX, bool flipY) { + this.flipX = flipX; + this.flipY = flipY; + } + + public bool flipX; + public bool flipY; + } Dictionary boneTable = new Dictionary(); + Dictionary boneFlipTable = new Dictionary(); Transform ragdollRoot; public Rigidbody RootRigidbody { get; private set; } public Bone StartingBone { get; private set; } @@ -139,12 +149,12 @@ namespace Spine.Unity.Examples { if (b == StartingBone) { ragdollRoot = new GameObject("RagdollRoot").transform; ragdollRoot.SetParent(transform, false); - if (b == skeleton.RootBone) { // RagdollRoot is skeleton root. - ragdollRoot.localPosition = new Vector3(b.WorldX, b.WorldY, 0); - ragdollRoot.localRotation = Quaternion.Euler(0, 0, GetPropagatedRotation(b)); + if (b == skeleton.RootBone) { // RagdollRoot is skeleton root's parent, thus the skeleton's scale and position. + ragdollRoot.localPosition = new Vector3(skeleton.X, skeleton.Y, 0); + ragdollRoot.localRotation = (skeleton.ScaleX < 0) ? Quaternion.Euler(0, 0, 180.0f) : Quaternion.identity; } else { ragdollRoot.localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0); - ragdollRoot.localRotation = Quaternion.Euler(0, 0, GetPropagatedRotation(b.Parent)); + ragdollRoot.localRotation = Quaternion.Euler(0, 0, b.Parent.WorldRotationX - b.Parent.ShearX); } parentTransform = ragdollRoot; rootOffset = t.position - transform.position; @@ -295,7 +305,6 @@ namespace Spine.Unity.Examples { t.localRotation = Quaternion.Euler(0, 0, b.WorldRotationX - b.ShearX); t.localScale = new Vector3(b.WorldScaleX, b.WorldScaleY, 1); - // MITCH: You left "todo: proper ragdoll branching" var colliders = AttachBoundingBoxRagdollColliders(b); if (colliders.Count == 0) { float length = b.Data.Length; @@ -316,20 +325,38 @@ namespace Spine.Unity.Examples { } void UpdateSpineSkeleton (ISkeletonAnimation skeletonRenderer) { - bool flipX = skeleton.ScaleX < 0; - bool flipY = skeleton.ScaleY < 0; - bool flipXOR = flipX ^ flipY; - bool flipOR = flipX || flipY; + bool parentFlipX; + bool parentFlipY; + GetStartBoneParentFlipState(out parentFlipX, out parentFlipY); foreach (var pair in boneTable) { var b = pair.Key; var t = pair.Value; bool isStartingBone = b == StartingBone; - Transform parentTransform = isStartingBone ? ragdollRoot : boneTable[b.Parent]; + var parentBone = b.Parent; + Transform parentTransform = isStartingBone ? ragdollRoot : boneTable[parentBone]; + if (!isStartingBone) { + var parentBoneFlip = boneFlipTable[parentBone]; + parentFlipX = parentBoneFlip.flipX; + parentFlipY = parentBoneFlip.flipY; + } + bool flipX = parentFlipX ^ (b.ScaleX < 0); + bool flipY = parentFlipY ^ (b.ScaleY < 0); + + BoneFlipEntry boneFlip; + boneFlipTable.TryGetValue(b, out boneFlip); + boneFlip.flipX = flipX; + boneFlip.flipY = flipY; + boneFlipTable[b] = boneFlip; + + bool flipXOR = flipX ^ flipY; + bool parentFlipXOR = parentFlipX ^ parentFlipY; + if (!oldRagdollBehaviour && isStartingBone) { if (b != skeleton.RootBone) { // RagdollRoot is not skeleton root. - ragdollRoot.localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0); - ragdollRoot.localRotation = Quaternion.Euler(0, 0, GetPropagatedRotation(b.Parent)); + ragdollRoot.localPosition = new Vector3(parentBone.WorldX, parentBone.WorldY, 0); + ragdollRoot.localRotation = Quaternion.Euler(0, 0, parentBone.WorldRotationX - parentBone.ShearX); + ragdollRoot.localScale = new Vector3(parentBone.WorldScaleX, parentBone.WorldScaleY, 1); } } Vector3 parentTransformWorldPosition = parentTransform.position; @@ -339,26 +366,26 @@ namespace Spine.Unity.Examples { parentSpaceHelper.rotation = parentTransformWorldRotation; parentSpaceHelper.localScale = parentTransform.lossyScale; + if (oldRagdollBehaviour) { + if (isStartingBone && b != skeleton.RootBone) { + Vector3 localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0); + parentSpaceHelper.position = ragdollRoot.TransformPoint(localPosition); + parentSpaceHelper.localRotation = Quaternion.Euler(0, 0, parentBone.WorldRotationX - parentBone.ShearX); + parentSpaceHelper.localScale = new Vector3(parentBone.WorldScaleX, parentBone.WorldScaleY, 1); + } + } + Vector3 boneWorldPosition = t.position; Vector3 right = parentSpaceHelper.InverseTransformDirection(t.right); Vector3 boneLocalPosition = parentSpaceHelper.InverseTransformPoint(boneWorldPosition); float boneLocalRotation = Mathf.Atan2(right.y, right.x) * Mathf.Rad2Deg; - if (flipOR) { - if (isStartingBone) { - if (flipX) boneLocalPosition.x *= -1f; - if (flipY) boneLocalPosition.y *= -1f; + if (flipXOR) boneLocalPosition.y *= -1f; + if (parentFlipXOR != flipXOR) boneLocalPosition.y *= -1f; - boneLocalRotation = boneLocalRotation * (flipXOR ? -1f : 1f); - if (flipX) boneLocalRotation += 180; - } else { - if (flipXOR) { - boneLocalRotation *= -1f; - boneLocalPosition.y *= -1f; // wtf?? - } - } - } + if (parentFlipXOR) boneLocalRotation *= -1f; + if (parentFlipX != flipX) boneLocalRotation += 180; b.X = Mathf.Lerp(b.X, boneLocalPosition.x, mix); b.Y = Mathf.Lerp(b.Y, boneLocalPosition.y, mix); @@ -367,6 +394,17 @@ namespace Spine.Unity.Examples { } } + void GetStartBoneParentFlipState (out bool parentFlipX, out bool parentFlipY) { + parentFlipX = skeleton.ScaleX < 0; + parentFlipY = skeleton.ScaleY < 0; + var parent = this.StartingBone == null ? null : this.StartingBone.Parent; + while (parent != null) { + parentFlipX ^= parent.ScaleX < 0; + parentFlipY ^= parent.ScaleY < 0; + parent = parent.Parent; + } + } + List AttachBoundingBoxRagdollColliders (Bone b) { const string AttachmentNameMarker = "ragdoll"; var colliders = new List(); @@ -399,16 +437,6 @@ namespace Spine.Unity.Examples { return colliders; } - static float GetPropagatedRotation (Bone b) { - Bone parent = b.Parent; - float a = b.AppliedRotation; - while (parent != null) { - a += parent.AppliedRotation; - parent = parent.Parent; - } - return a; - } - public class LayerFieldAttribute : PropertyAttribute {} } diff --git a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonRagdoll2D.cs b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonRagdoll2D.cs index 61d692224..b11294ee3 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonRagdoll2D.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/SkeletonUtility Modules/SkeletonRagdoll2D.cs @@ -71,12 +71,22 @@ namespace Spine.Unity.Examples { public int colliderLayer = 0; [Range(0, 1)] public float mix = 1; - public bool oldRagdollBehaviour = true; + public bool oldRagdollBehaviour = false; #endregion ISkeletonAnimation targetSkeletonComponent; Skeleton skeleton; + struct BoneFlipEntry { + public BoneFlipEntry (bool flipX, bool flipY) { + this.flipX = flipX; + this.flipY = flipY; + } + + public bool flipX; + public bool flipY; + } Dictionary boneTable = new Dictionary(); + Dictionary boneFlipTable = new Dictionary(); Transform ragdollRoot; public Rigidbody2D RootRigidbody { get; private set; } public Bone StartingBone { get; private set; } @@ -141,12 +151,12 @@ namespace Spine.Unity.Examples { if (b == startingBone) { ragdollRoot = new GameObject("RagdollRoot").transform; ragdollRoot.SetParent(transform, false); - if (b == skeleton.RootBone) { // RagdollRoot is skeleton root. - ragdollRoot.localPosition = new Vector3(b.WorldX, b.WorldY, 0); - ragdollRoot.localRotation = Quaternion.Euler(0, 0, GetPropagatedRotation(b)); + if (b == skeleton.RootBone) { // RagdollRoot is skeleton root's parent, thus the skeleton's scale and position. + ragdollRoot.localPosition = new Vector3(skeleton.X, skeleton.Y, 0); + ragdollRoot.localRotation = (skeleton.ScaleX < 0) ? Quaternion.Euler(0, 0, 180.0f) : Quaternion.identity; } else { ragdollRoot.localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0); - ragdollRoot.localRotation = Quaternion.Euler(0, 0, GetPropagatedRotation(b.Parent)); + ragdollRoot.localRotation = Quaternion.Euler(0, 0, b.Parent.WorldRotationX - b.Parent.ShearX); } parentTransform = ragdollRoot; rootOffset = t.position - transform.position; @@ -307,7 +317,6 @@ namespace Spine.Unity.Examples { t.localRotation = Quaternion.Euler(0, 0, b.WorldRotationX - b.ShearX); t.localScale = new Vector3(b.WorldScaleX, b.WorldScaleY, 1); - // MITCH: You left "todo: proper ragdoll branching" var colliders = AttachBoundingBoxRagdollColliders(b, boneGameObject, skeleton, this.gravityScale); if (colliders.Count == 0) { float length = b.Data.Length; @@ -331,21 +340,39 @@ namespace Spine.Unity.Examples { /// Performed every skeleton animation update to translate Unity Transforms positions into Spine bone transforms. void UpdateSpineSkeleton (ISkeletonAnimation animatedSkeleton) { - bool flipX = skeleton.ScaleX < 0; - bool flipY = skeleton.ScaleY < 0; - bool flipXOR = flipX ^ flipY; - bool flipOR = flipX || flipY; + bool parentFlipX; + bool parentFlipY; var startingBone = this.StartingBone; + GetStartBoneParentFlipState(out parentFlipX, out parentFlipY); foreach (var pair in boneTable) { var b = pair.Key; var t = pair.Value; bool isStartingBone = (b == startingBone); - Transform parentTransform = isStartingBone ? ragdollRoot : boneTable[b.Parent]; + var parentBone = b.Parent; + Transform parentTransform = isStartingBone ? ragdollRoot : boneTable[parentBone]; + if (!isStartingBone) { + var parentBoneFlip = boneFlipTable[parentBone]; + parentFlipX = parentBoneFlip.flipX; + parentFlipY = parentBoneFlip.flipY; + } + bool flipX = parentFlipX ^ (b.ScaleX < 0); + bool flipY = parentFlipY ^ (b.ScaleY < 0); + + BoneFlipEntry boneFlip; + boneFlipTable.TryGetValue(b, out boneFlip); + boneFlip.flipX = flipX; + boneFlip.flipY = flipY; + boneFlipTable[b] = boneFlip; + + bool flipXOR = flipX ^ flipY; + bool parentFlipXOR = parentFlipX ^ parentFlipY; + if (!oldRagdollBehaviour && isStartingBone) { if (b != skeleton.RootBone) { // RagdollRoot is not skeleton root. - ragdollRoot.localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0); - ragdollRoot.localRotation = Quaternion.Euler(0, 0, GetPropagatedRotation(b.Parent)); + ragdollRoot.localPosition = new Vector3(parentBone.WorldX, parentBone.WorldY, 0); + ragdollRoot.localRotation = Quaternion.Euler(0, 0, parentBone.WorldRotationX - parentBone.ShearX); + ragdollRoot.localScale = new Vector3(parentBone.WorldScaleX, parentBone.WorldScaleY, 1); } } @@ -356,25 +383,26 @@ namespace Spine.Unity.Examples { parentSpaceHelper.rotation = parentTransformWorldRotation; parentSpaceHelper.localScale = parentTransform.lossyScale; + if (oldRagdollBehaviour) { + if (isStartingBone && b != skeleton.RootBone) { + Vector3 localPosition = new Vector3(b.Parent.WorldX, b.Parent.WorldY, 0); + parentSpaceHelper.position = ragdollRoot.TransformPoint(localPosition); + parentSpaceHelper.localRotation = Quaternion.Euler(0, 0, parentBone.WorldRotationX - parentBone.ShearX); + parentSpaceHelper.localScale = new Vector3(parentBone.WorldScaleX, parentBone.WorldScaleY, 1); + } + } + Vector3 boneWorldPosition = t.position; Vector3 right = parentSpaceHelper.InverseTransformDirection(t.right); Vector3 boneLocalPosition = parentSpaceHelper.InverseTransformPoint(boneWorldPosition); float boneLocalRotation = Mathf.Atan2(right.y, right.x) * Mathf.Rad2Deg; - if (flipOR) { - if (isStartingBone) { - if (flipX) boneLocalPosition.x *= -1f; - if (flipY) boneLocalPosition.y *= -1f; - boneLocalRotation = boneLocalRotation * (flipXOR ? -1f : 1f); - if (flipX) boneLocalRotation += 180; - } else { - if (flipXOR) { - boneLocalRotation *= -1f; - boneLocalPosition.y *= -1f; // wtf?? - } - } - } + if (flipXOR) boneLocalPosition.y *= -1f; + if (parentFlipXOR != flipXOR) boneLocalPosition.y *= -1f; + + if (parentFlipXOR) boneLocalRotation *= -1f; + if (parentFlipX != flipX) boneLocalRotation += 180; b.X = Mathf.Lerp(b.X, boneLocalPosition.x, mix); b.Y = Mathf.Lerp(b.Y, boneLocalPosition.y, mix); @@ -383,6 +411,17 @@ namespace Spine.Unity.Examples { } } + void GetStartBoneParentFlipState (out bool parentFlipX, out bool parentFlipY) { + parentFlipX = skeleton.ScaleX < 0; + parentFlipY = skeleton.ScaleY < 0; + var parent = this.StartingBone == null ? null : this.StartingBone.Parent; + while (parent != null) { + parentFlipX ^= parent.ScaleX < 0; + parentFlipY ^= parent.ScaleY < 0; + parent = parent.Parent; + } + } + static List AttachBoundingBoxRagdollColliders (Bone b, GameObject go, Skeleton skeleton, float gravityScale) { const string AttachmentNameMarker = "ragdoll"; var colliders = new List(); @@ -414,16 +453,6 @@ namespace Spine.Unity.Examples { return colliders; } - static float GetPropagatedRotation (Bone b) { - Bone parent = b.Parent; - float a = b.AppliedRotation; - while (parent != null) { - a += parent.AppliedRotation; - parent = parent.Parent; - } - return a; - } - static Vector3 FlipScale (bool flipX, bool flipY) { return new Vector3(flipX ? -1f : 1f, flipY ? -1f : 1f, 1f); }