From ff07a01aef83efa925304eba56594f45f3f87e01 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Wed, 12 Jun 2024 17:48:44 +0200 Subject: [PATCH] [unity] SkeletonGraphic allows custom mesh offset relative to the pivot to keep e.g. the face centered when layout scale downscales the mesh towards the pivot. Closes #2482. --- CHANGELOG.md | 1 + .../BoneFollowerGraphicInspector.cs | 10 ++- .../Components/SkeletonGraphicInspector.cs | 14 ++-- .../Editor/Utility/SpineHandles.cs | 84 ++++++++++++++----- .../Following/BoneFollowerGraphic.cs | 8 +- .../spine-unity/Components/SkeletonGraphic.cs | 82 +++++++++++++----- .../SkeletonUtility/SkeletonUtility.cs | 44 +++++++++- .../Mesh Generation/MeshGenerator.cs | 14 ++++ .../spine-unity/Utility/SkeletonExtensions.cs | 4 + spine-unity/Assets/Spine/package.json | 2 +- 10 files changed, 202 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf8ef5d2c..3df130229 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -159,6 +159,7 @@ - `SkeletonGraphicRenderTexture` example component now also received a `quadMaterial` property, defaulting to the newly added Material asset `RenderQuadGraphicMaterial` which applies proper premultiplied-alpha blending of the render texture. The `quadMaterial` member variable was moved from `SkeletonRenderTexture` to the common base class `SkeletonRenderTextureBase`. - All Spine Outline shaders, including the URP outline shader, now provide an additional parameter `Width in Screen Space`. Enable it to keep the outline width constant in screen space instead of texture space. Requires more expensive computations, so enable only where necessary. Defaults to `disabled` to maintain existing behaviour. - Added support for BlendModeMaterials at runtime instantiation from files via an additional method `SkeletonDataAsset.SetupRuntimeBlendModeMaterials`. See example scene `Spine Examples/Other Examples/Instantiate from Script` for a usage example. + - SkeletonGraphic: You can now offset the skeleton mesh relative to the pivot via a newly added green circle handle. This allows you to e.g. frame only the face of a skeleton inside a masked frame. Previously offsetting the pivot downwards fails when `Layout Scale Mode` scales the mesh smaller and towards the pivot (e.g. the feet) and thus out of the frame. Now you can keep the pivot in the center of the `RectTransform` while offsetting only the mesh downwards, keeping the desired skeleton area (e.g. the face) centered while resizing. Moving the new larger green circle handle moves the mesh offset, while moving the blue pivot circle handle moves the pivot as usual. - **Breaking changes** diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoneFollowerGraphicInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoneFollowerGraphicInspector.cs index a729ae900..1a53fa245 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoneFollowerGraphicInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoneFollowerGraphicInspector.cs @@ -100,17 +100,19 @@ namespace Spine.Unity.Editor { Transform transform = skeletonGraphicComponent.transform; Skeleton skeleton = skeletonGraphicComponent.Skeleton; float positionScale = skeletonGraphicComponent.MeshScale; + Vector2 positionOffset = skeletonGraphicComponent.GetScaledPivotOffset(); if (string.IsNullOrEmpty(boneName.stringValue)) { - SpineHandles.DrawBones(transform, skeleton, positionScale); - SpineHandles.DrawBoneNames(transform, skeleton, positionScale); + SpineHandles.DrawBones(transform, skeleton, positionScale, positionOffset); + SpineHandles.DrawBoneNames(transform, skeleton, positionScale, positionOffset); Handles.Label(tbf.transform.position, "No bone selected", EditorStyles.helpBox); } else { Bone targetBone = tbf.bone; if (targetBone == null) return; - SpineHandles.DrawBoneWireframe(transform, targetBone, SpineHandles.TransformContraintColor, positionScale); - Handles.Label(targetBone.GetWorldPosition(transform, positionScale), targetBone.Data.Name, SpineHandles.BoneNameStyle); + SpineHandles.DrawBoneWireframe(transform, targetBone, SpineHandles.TransformContraintColor, positionScale, positionOffset); + Handles.Label(targetBone.GetWorldPosition(transform, positionScale, positionOffset), + targetBone.Data.Name, SpineHandles.BoneNameStyle); } } 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 4e5a7f433..0fd424ea2 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 @@ -552,12 +552,16 @@ namespace Spine.Unity.Editor { protected void OnSceneGUI () { SkeletonGraphic skeletonGraphic = (SkeletonGraphic)target; - if (skeletonGraphic.EditReferenceRect) { - SpineHandles.DrawRectTransformRect(skeletonGraphic, Color.gray); - SpineHandles.DrawReferenceRect(skeletonGraphic, Color.green); - } else { - SpineHandles.DrawReferenceRect(skeletonGraphic, Color.blue); + + if (skeletonGraphic.layoutScaleMode != SkeletonGraphic.LayoutMode.None) { + if (skeletonGraphic.EditReferenceRect) { + SpineHandles.DrawRectTransformRect(skeletonGraphic, Color.gray); + SpineHandles.DrawReferenceRect(skeletonGraphic, Color.green); + } else { + SpineHandles.DrawReferenceRect(skeletonGraphic, Color.blue); + } } + SpineHandles.DrawPivotOffsetHandle(skeletonGraphic, Color.green); } public static void SetSeparatorSlotNames (SkeletonRenderer skeletonRenderer, string[] newSlotNames) { diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineHandles.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineHandles.cs index e8673f4f3..6f21b4b32 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineHandles.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineHandles.cs @@ -161,27 +161,32 @@ namespace Spine.Unity.Editor { } } - public static void DrawBoneNames (Transform transform, Skeleton skeleton, float positionScale = 1f) { + public static void DrawBoneNames (Transform transform, Skeleton skeleton, float positionScale = 1f, + Vector2? positionOffset = null) { if (UnityEngine.Event.current.type != EventType.Repaint) return; + Vector2 offset = positionOffset == null ? Vector2.zero : positionOffset.Value; GUIStyle style = BoneNameStyle; foreach (Bone b in skeleton.Bones) { if (!b.Active) continue; - Vector3 pos = new Vector3(b.WorldX * positionScale, b.WorldY * positionScale, 0) + (new Vector3(b.A, b.C) * (b.Data.Length * 0.5f)); + Vector3 pos = new Vector3(b.WorldX * positionScale + offset.x, b.WorldY * positionScale + offset.y, 0) + + (new Vector3(b.A, b.C) * (b.Data.Length * 0.5f)); pos = transform.TransformPoint(pos); Handles.Label(pos, b.Data.Name, style); } } - public static void DrawBones (Transform transform, Skeleton skeleton, float positionScale = 1f) { + public static void DrawBones (Transform transform, Skeleton skeleton, float positionScale = 1f, + Vector2? positionOffset = null) { if (UnityEngine.Event.current.type != EventType.Repaint) return; + Vector2 offset = positionOffset == null ? Vector2.zero : positionOffset.Value; float boneScale = 1.8f; // Draw the root bone largest; - DrawCrosshairs2D(skeleton.Bones.Items[0].GetWorldPosition(transform), 0.08f, positionScale); + DrawCrosshairs2D(skeleton.Bones.Items[0].GetWorldPosition(transform, positionScale, offset), 0.08f, positionScale); foreach (Bone b in skeleton.Bones) { if (!b.Active) continue; - DrawBone(transform, b, boneScale, positionScale); + DrawBone(transform, b, boneScale, positionScale, positionOffset); boneScale = 1f; } } @@ -194,11 +199,13 @@ namespace Spine.Unity.Editor { _boneWireBuffer[4] = _boneWireBuffer[0]; // closed polygon. return _boneWireBuffer; } - public static void DrawBoneWireframe (Transform transform, Bone b, Color color, float skeletonRenderScale = 1f) { + public static void DrawBoneWireframe (Transform transform, Bone b, Color color, float skeletonRenderScale = 1f, + Vector2? positionOffset = null) { if (UnityEngine.Event.current.type != EventType.Repaint) return; + Vector2 offset = positionOffset == null ? Vector2.zero : positionOffset.Value; Handles.color = color; - Vector3 pos = new Vector3(b.WorldX * skeletonRenderScale, b.WorldY * skeletonRenderScale, 0); + Vector3 pos = new Vector3(b.WorldX * skeletonRenderScale + offset.x, b.WorldY * skeletonRenderScale + offset.y, 0); float length = b.Data.Length; if (length > 0) { @@ -216,10 +223,12 @@ namespace Spine.Unity.Editor { } } - public static void DrawBone (Transform transform, Bone b, float boneScale, float skeletonRenderScale = 1f) { + public static void DrawBone (Transform transform, Bone b, float boneScale, float skeletonRenderScale = 1f, + Vector2? positionOffset = null) { if (UnityEngine.Event.current.type != EventType.Repaint) return; - Vector3 pos = new Vector3(b.WorldX * skeletonRenderScale, b.WorldY * skeletonRenderScale, 0); + Vector2 offset = positionOffset == null ? Vector2.zero : positionOffset.Value; + Vector3 pos = new Vector3(b.WorldX * skeletonRenderScale + offset.x, b.WorldY * skeletonRenderScale + offset.y, 0); float length = b.Data.Length; if (length > 0) { Quaternion rot = Quaternion.Euler(0, 0, b.WorldRotationX); @@ -235,10 +244,12 @@ namespace Spine.Unity.Editor { } } - public static void DrawBone (Transform transform, Bone b, float boneScale, Color color, float skeletonRenderScale = 1f) { + public static void DrawBone (Transform transform, Bone b, float boneScale, Color color, float skeletonRenderScale = 1f, + Vector2? positionOffset = null) { if (UnityEngine.Event.current.type != EventType.Repaint) return; - Vector3 pos = new Vector3(b.WorldX * skeletonRenderScale, b.WorldY * skeletonRenderScale, 0); + Vector2 offset = positionOffset == null ? Vector2.zero : positionOffset.Value; + Vector3 pos = new Vector3(b.WorldX * skeletonRenderScale + offset.x, b.WorldY * skeletonRenderScale + offset.y, 0); float length = b.Data.Length; if (length > 0) { Quaternion rot = Quaternion.Euler(0, 0, b.WorldRotationX); @@ -367,9 +378,11 @@ namespace Spine.Unity.Editor { DrawArrowhead(skeletonTransform.localToWorldMatrix * m); } - public static void DrawConstraints (Transform transform, Skeleton skeleton, float skeletonRenderScale = 1f) { + public static void DrawConstraints (Transform transform, Skeleton skeleton, float skeletonRenderScale = 1f, + Vector2? positionOffset = null) { if (UnityEngine.Event.current.type != EventType.Repaint) return; + Vector2 offset = positionOffset == null ? Vector2.zero : positionOffset.Value; Vector3 targetPos; Vector3 pos; bool active; @@ -381,14 +394,14 @@ namespace Spine.Unity.Editor { handleColor = SpineHandles.TransformContraintColor; foreach (TransformConstraint tc in skeleton.TransformConstraints) { Bone targetBone = tc.Target; - targetPos = targetBone.GetWorldPosition(transform, skeletonRenderScale); + targetPos = targetBone.GetWorldPosition(transform, skeletonRenderScale, offset); if (tc.MixX > 0 || tc.MixY > 0) { if ((tc.MixX > 0 && tc.MixX != 1f) || (tc.MixY > 0 && tc.MixY != 1f)) { Handles.color = handleColor; foreach (Bone b in tc.Bones) { - pos = b.GetWorldPosition(transform, skeletonRenderScale); + pos = b.GetWorldPosition(transform, skeletonRenderScale, offset); Handles.DrawDottedLine(targetPos, pos, Thickness); } } @@ -402,25 +415,25 @@ namespace Spine.Unity.Editor { handleColor = SpineHandles.IkColor; foreach (IkConstraint ikc in skeleton.IkConstraints) { Bone targetBone = ikc.Target; - targetPos = targetBone.GetWorldPosition(transform, skeletonRenderScale); + targetPos = targetBone.GetWorldPosition(transform, skeletonRenderScale, offset); ExposedList bones = ikc.Bones; active = ikc.Mix > 0; if (active) { - pos = bones.Items[0].GetWorldPosition(transform, skeletonRenderScale); + pos = bones.Items[0].GetWorldPosition(transform, skeletonRenderScale, offset); switch (bones.Count) { case 1: { Handles.color = handleColor; Handles.DrawLine(targetPos, pos); SpineHandles.DrawBoneCircle(targetPos, handleColor, normal); Matrix4x4 m = bones.Items[0].GetMatrix4x4(); - m.m03 = targetBone.WorldX * skeletonRenderScale; - m.m13 = targetBone.WorldY * skeletonRenderScale; + m.m03 = targetBone.WorldX * skeletonRenderScale + offset.x; + m.m13 = targetBone.WorldY * skeletonRenderScale + offset.y; SpineHandles.DrawArrowhead(transform.localToWorldMatrix * m); break; } case 2: { Bone childBone = bones.Items[1]; - Vector3 child = childBone.GetWorldPosition(transform, skeletonRenderScale); + Vector3 child = childBone.GetWorldPosition(transform, skeletonRenderScale, offset); Handles.color = handleColor; Handles.DrawLine(child, pos); Handles.DrawLine(targetPos, child); @@ -428,8 +441,8 @@ namespace Spine.Unity.Editor { SpineHandles.DrawBoneCircle(child, handleColor, normal, 0.5f); SpineHandles.DrawBoneCircle(targetPos, handleColor, normal); Matrix4x4 m = childBone.GetMatrix4x4(); - m.m03 = targetBone.WorldX * skeletonRenderScale; - m.m13 = targetBone.WorldY * skeletonRenderScale; + m.m03 = targetBone.WorldX * skeletonRenderScale + offset.x; + m.m13 = targetBone.WorldY * skeletonRenderScale + offset.y; SpineHandles.DrawArrowhead(transform.localToWorldMatrix * m); break; } @@ -444,7 +457,8 @@ namespace Spine.Unity.Editor { active = pc.MixX > 0 || pc.MixY > 0 || pc.MixRotate > 0; if (active) foreach (Bone b in pc.Bones) - SpineHandles.DrawBoneCircle(b.GetWorldPosition(transform, skeletonRenderScale), handleColor, normal, 1f * skeletonRenderScale); + SpineHandles.DrawBoneCircle(b.GetWorldPosition(transform, skeletonRenderScale, offset), + handleColor, normal, 1f * skeletonRenderScale); } } @@ -453,6 +467,7 @@ namespace Spine.Unity.Editor { RectTransform rectTransform = skeletonGraphic.rectTransform; Vector2 referenceRectSize = skeletonGraphic.GetReferenceRectSize(); + Vector3 position = rectTransform.position; Vector3 right = rectTransform.TransformVector(Vector3.right * referenceRectSize.x); Vector3 up = rectTransform.TransformVector(Vector3.up * referenceRectSize.y); @@ -466,6 +481,7 @@ namespace Spine.Unity.Editor { RectTransform rectTransform = skeletonGraphic.rectTransform; Vector2 rectTransformSize = skeletonGraphic.RectTransformSize; + Vector3 position = rectTransform.position; Vector3 right = rectTransform.TransformVector(Vector3.right * rectTransformSize.x); Vector3 up = rectTransform.TransformVector(Vector3.up * rectTransformSize.y); @@ -490,6 +506,30 @@ namespace Spine.Unity.Editor { UnityEditor.Handles.color = previousColor; } + public static void DrawPivotOffsetHandle (SkeletonGraphic skeletonGraphic, Color color) { + // Note: not limiting to current.type == EventType.Repaint because the FreeMoveHandle requires interaction. + + float handleSize = HandleUtility.GetHandleSize(skeletonGraphic.transform.position); + float controlSize = handleSize * 0.3f; + float discSize = handleSize * 0.03f; + Vector3 snap = Vector3.zero; + Color savedColor = Handles.color; + + Handles.color = color; + Vector2 scaledOffset = skeletonGraphic.GetScaledPivotOffset(); + Vector3 worldSpaceOffset = skeletonGraphic.transform.TransformPoint(scaledOffset); + EditorGUI.BeginChangeCheck(); + Vector3 newWorldSpacePosition = Handles.FreeMoveHandle(worldSpaceOffset, controlSize, snap, Handles.CircleHandleCap); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(skeletonGraphic, "Change Offset to Pivot"); + Vector3 localScaledOffset = skeletonGraphic.transform.InverseTransformPoint(newWorldSpacePosition); + skeletonGraphic.SetScaledPivotOffset(localScaledOffset); + skeletonGraphic.UpdateMeshToInstructions(); + } + Handles.DrawSolidDisc(newWorldSpacePosition, skeletonGraphic.transform.forward, discSize); + Handles.color = savedColor; + } + static void DrawCrosshairs2D (Vector3 position, float scale, float skeletonRenderScale = 1f) { if (UnityEngine.Event.current.type != EventType.Repaint) return; diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoneFollowerGraphic.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoneFollowerGraphic.cs index 77c9f702a..0b303495c 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoneFollowerGraphic.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/Following/BoneFollowerGraphic.cs @@ -140,17 +140,19 @@ namespace Spine.Unity { if (thisTransform == null) return; float scale = skeletonGraphic.MeshScale; + Vector2 offset = skeletonGraphic.MeshOffset; float additionalFlipScale = 1; if (skeletonTransformIsParent) { // Recommended setup: Use local transform properties if Spine GameObject is the immediate parent - thisTransform.localPosition = new Vector3(followXYPosition ? bone.WorldX * scale : thisTransform.localPosition.x, - followXYPosition ? bone.WorldY * scale : thisTransform.localPosition.y, + thisTransform.localPosition = new Vector3(followXYPosition ? bone.WorldX * scale + offset.x : thisTransform.localPosition.x, + followXYPosition ? bone.WorldY * scale + offset.y : thisTransform.localPosition.y, followZPosition ? 0f : thisTransform.localPosition.z); if (followBoneRotation) thisTransform.localRotation = bone.GetQuaternion(); } else { // For special cases: Use transform world properties if transform relationship is complicated - Vector3 targetWorldPosition = skeletonTransform.TransformPoint(new Vector3(bone.WorldX * scale, bone.WorldY * scale, 0f)); + Vector3 targetWorldPosition = skeletonTransform.TransformPoint( + new Vector3(bone.WorldX * scale + offset.x, bone.WorldY * scale + offset.y, 0f)); if (!followZPosition) targetWorldPosition.z = thisTransform.position.z; if (!followXYPosition) { targetWorldPosition.x = thisTransform.position.x; 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 dd2970de9..82b49e147 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs @@ -77,7 +77,9 @@ namespace Spine.Unity { public float timeScale = 1f; public bool freeze; protected float meshScale = 1f; + protected Vector2 meshOffset = Vector2.zero; public float MeshScale { get { return meshScale; } } + public Vector2 MeshOffset { get { return meshOffset; } } public enum LayoutMode { None = 0, @@ -88,6 +90,8 @@ namespace Spine.Unity { } public LayoutMode layoutScaleMode = LayoutMode.None; [SerializeField] protected Vector2 referenceSize = Vector2.one; + /// Offset relative to the pivot position, before potential layout scale is applied. + [SerializeField] protected Vector2 pivotOffset = Vector2.zero; [SerializeField] protected float referenceScale = 1f; #if UNITY_EDITOR protected LayoutMode previousLayoutScaleMode = LayoutMode.None; @@ -939,10 +943,19 @@ namespace Spine.Unity { meshScale = (canvas == null) ? 100 : canvas.referencePixelsPerUnit; if (layoutScaleMode != LayoutMode.None) { meshScale *= referenceScale; - if (!EditReferenceRect) - meshScale *= GetLayoutScale(layoutScaleMode); + float layoutScale = GetLayoutScale(layoutScaleMode); + if (!EditReferenceRect) { + meshScale *= layoutScale; + } + meshOffset = pivotOffset * layoutScale; + } else { + meshOffset = pivotOffset; } - meshGenerator.ScaleVertexData(meshScale); + if (meshOffset == Vector2.zero) + meshGenerator.ScaleVertexData(meshScale); + else + meshGenerator.ScaleAndOffsetVertexData(meshScale, meshOffset); + if (OnPostProcessVertices != null) OnPostProcessVertices.Invoke(this.meshGenerator.Buffers); Mesh mesh = smartMesh.mesh; @@ -1030,8 +1043,13 @@ namespace Spine.Unity { meshScale = (canvas == null) ? 100 : canvas.referencePixelsPerUnit; if (layoutScaleMode != LayoutMode.None) { meshScale *= referenceScale; - if (!EditReferenceRect) - meshScale *= GetLayoutScale(layoutScaleMode); + float layoutScale = GetLayoutScale(layoutScaleMode); + if (!EditReferenceRect) { + meshScale *= layoutScale; + } + meshOffset = pivotOffset * layoutScale; + } else { + meshOffset = pivotOffset; } // Generate meshes. int submeshCount = currentInstructions.submeshInstructions.Count; @@ -1052,7 +1070,10 @@ namespace Spine.Unity { meshGenerator.AddSubmesh(submeshInstructionItem); Mesh targetMesh = meshesItems[i]; - meshGenerator.ScaleVertexData(meshScale); + if (meshOffset == Vector2.zero) + meshGenerator.ScaleVertexData(meshScale); + else + meshGenerator.ScaleAndOffsetVertexData(meshScale, meshOffset); if (OnPostProcessVertices != null) OnPostProcessVertices.Invoke(this.meshGenerator.Buffers); meshGenerator.FillVertexData(targetMesh); meshGenerator.FillTriangles(targetMesh); @@ -1351,9 +1372,9 @@ namespace Spine.Unity { SetRectTransformSize(this, rectTransformSize); } } - if (editReferenceRect || layoutScaleMode == LayoutMode.None) { + if (editReferenceRect || layoutScaleMode == LayoutMode.None) referenceSize = GetCurrentRectSize(); - } + previousLayoutScaleMode = layoutScaleMode; } @@ -1374,13 +1395,7 @@ namespace Spine.Unity { float referenceAspect = referenceSize.x / referenceSize.y; Vector2 newSize = GetCurrentRectSize(); - LayoutMode mode = previousLayoutScaleMode; - float frameAspect = newSize.x / newSize.y; - if (mode == LayoutMode.FitInParent) - mode = frameAspect > referenceAspect ? LayoutMode.HeightControlsWidth : LayoutMode.WidthControlsHeight; - else if (mode == LayoutMode.EnvelopeParent) - mode = frameAspect > referenceAspect ? LayoutMode.WidthControlsHeight : LayoutMode.HeightControlsWidth; - + LayoutMode mode = GetEffectiveLayoutMode(previousLayoutScaleMode); if (mode == LayoutMode.WidthControlsHeight) newSize.y = newSize.x / referenceAspect; else if (mode == LayoutMode.HeightControlsWidth) @@ -1391,17 +1406,22 @@ namespace Spine.Unity { public Vector2 GetReferenceRectSize () { return referenceSize * GetLayoutScale(layoutScaleMode); } -#endif + public Vector2 GetPivotOffset () { + return pivotOffset; + } + + public Vector2 GetScaledPivotOffset () { + return pivotOffset * GetLayoutScale(layoutScaleMode); + } + + public void SetScaledPivotOffset (Vector2 pivotOffsetScaled) { + pivotOffset = pivotOffsetScaled / GetLayoutScale(layoutScaleMode); + } +#endif protected float GetLayoutScale (LayoutMode mode) { Vector2 currentSize = GetCurrentRectSize(); - float referenceAspect = referenceSize.x / referenceSize.y; - float frameAspect = currentSize.x / currentSize.y; - if (mode == LayoutMode.FitInParent) - mode = frameAspect > referenceAspect ? LayoutMode.HeightControlsWidth : LayoutMode.WidthControlsHeight; - else if (mode == LayoutMode.EnvelopeParent) - mode = frameAspect > referenceAspect ? LayoutMode.WidthControlsHeight : LayoutMode.HeightControlsWidth; - + mode = GetEffectiveLayoutMode(mode); if (mode == LayoutMode.WidthControlsHeight) { return currentSize.x / referenceSize.x; } else if (mode == LayoutMode.HeightControlsWidth) { @@ -1410,6 +1430,22 @@ namespace Spine.Unity { return 1f; } + /// + /// LayoutMode FitInParent and EnvelopeParent actually result in + /// HeightControlsWidth or WidthControlsHeight depending on the actual vs reference aspect ratio. + /// This method returns the respective LayoutMode of the two for any given input mode. + /// + protected LayoutMode GetEffectiveLayoutMode (LayoutMode mode) { + Vector2 currentSize = GetCurrentRectSize(); + float referenceAspect = referenceSize.x / referenceSize.y; + float frameAspect = currentSize.x / currentSize.y; + if (mode == LayoutMode.FitInParent) + mode = frameAspect > referenceAspect ? LayoutMode.HeightControlsWidth : LayoutMode.WidthControlsHeight; + else if (mode == LayoutMode.EnvelopeParent) + mode = frameAspect > referenceAspect ? LayoutMode.WidthControlsHeight : LayoutMode.HeightControlsWidth; + return mode; + } + private Vector2 GetCurrentRectSize () { return this.rectTransform.rect.size; } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonUtility/SkeletonUtility.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonUtility/SkeletonUtility.cs index 20471e0a0..749a81eae 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonUtility/SkeletonUtility.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonUtility/SkeletonUtility.cs @@ -159,6 +159,38 @@ namespace Spine.Unity { if (skeletonGraphic != null) { positionScale = skeletonGraphic.MeshScale; + lastPositionScale = positionScale; + if (boneRoot) { + positionOffset = skeletonGraphic.MeshOffset; + if (positionOffset != Vector2.zero) { + boneRoot.localPosition = positionOffset; + } + } + } + } + + void UpdateToMeshScaleAndOffset (MeshGeneratorBuffers ignoredParameter) { + if (skeletonGraphic == null) return; + + positionScale = skeletonGraphic.MeshScale; + if (boneRoot) { + positionOffset = skeletonGraphic.MeshOffset; + if (positionOffset != Vector2.zero) { + boneRoot.localPosition = positionOffset; + } + } + + // Note: skeletonGraphic.MeshScale and MeshOffset can be one frame behind in Update() above. + // Unfortunately update order is: + // 1. SkeletonGraphic.Update updating skeleton bones and calling UpdateWorld callback, + // calling SkeletonUtilityBone.DoUpdate() reading hierarchy.PositionScale. + // 2. Layout change triggers SkeletonGraphic.Rebuild, updating MeshScale and MeshOffset. + // Thus to prevent a one-frame-behind offset after a layout change affecting mesh scale, + // we have to re-evaluate the callbacks via the lines below. + if (lastPositionScale != positionScale) { + UpdateLocal(skeletonAnimation); + UpdateWorld(skeletonAnimation); + UpdateComplete(skeletonAnimation); } } @@ -170,7 +202,6 @@ namespace Spine.Unity { [System.NonSerialized] public List boneComponents = new List(); [System.NonSerialized] public List constraintComponents = new List(); - public ISkeletonComponent SkeletonComponent { get { if (skeletonComponent == null) { @@ -197,8 +228,11 @@ namespace Spine.Unity { } public float PositionScale { get { return positionScale; } } + public Vector2 PositionOffset { get { return positionOffset; } } float positionScale = 1.0f; + float lastPositionScale = 1.0f; + Vector2 positionOffset = Vector2.zero; bool hasOverrideBones; bool hasConstraints; bool needToReprocessBones; @@ -232,6 +266,8 @@ namespace Spine.Unity { } else if (skeletonGraphic != null) { skeletonGraphic.OnRebuild -= HandleRendererReset; skeletonGraphic.OnRebuild += HandleRendererReset; + skeletonGraphic.OnPostProcessVertices -= UpdateToMeshScaleAndOffset; + skeletonGraphic.OnPostProcessVertices += UpdateToMeshScaleAndOffset; } if (skeletonAnimation != null) { @@ -250,8 +286,10 @@ namespace Spine.Unity { void OnDisable () { if (skeletonRenderer != null) skeletonRenderer.OnRebuild -= HandleRendererReset; - if (skeletonGraphic != null) + if (skeletonGraphic != null) { skeletonGraphic.OnRebuild -= HandleRendererReset; + skeletonGraphic.OnPostProcessVertices -= UpdateToMeshScaleAndOffset; + } if (skeletonAnimation != null) { skeletonAnimation.UpdateLocal -= UpdateLocal; @@ -449,7 +487,7 @@ namespace Spine.Unity { if (mode == SkeletonUtilityBone.Mode.Override) { if (rot) goTransform.localRotation = Quaternion.Euler(0, 0, b.bone.AppliedRotation); - if (pos) goTransform.localPosition = new Vector3(b.bone.X * positionScale, b.bone.Y * positionScale, 0); + if (pos) goTransform.localPosition = new Vector3(b.bone.X * positionScale + positionOffset.x, b.bone.Y * positionScale + positionOffset.y, 0); goTransform.localScale = new Vector3(b.bone.ScaleX, b.bone.ScaleY, 0); } 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 cb77f0d94..5992bf38d 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 @@ -1065,6 +1065,20 @@ namespace Spine.Unity { meshBoundsThickness *= scale; } + public void ScaleAndOffsetVertexData (float scale, Vector2 offset2D) { + Vector3 offset = new Vector3(offset2D.x, offset2D.y); + Vector3[] vbi = vertexBuffer.Items; + for (int i = 0, n = vertexBuffer.Count; i < n; i++) { + vbi[i] = vbi[i] * scale + offset; + } + + meshBoundsMin *= scale; + meshBoundsMax *= scale; + meshBoundsMin += offset2D; + meshBoundsMax += offset2D; + meshBoundsThickness *= scale; + } + public Bounds GetMeshBounds () { if (float.IsInfinity(meshBoundsMin.x)) { // meshBoundsMin.x == BoundsMinDefault // == doesn't work on float Infinity constants. return new Bounds(); 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 f32ed5e40..f1a537a31 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/SkeletonExtensions.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Utility/SkeletonExtensions.cs @@ -157,6 +157,10 @@ namespace Spine.Unity { return spineGameObjectTransform.TransformPoint(new Vector3(bone.WorldX * positionScale, bone.WorldY * positionScale)); } + public static Vector3 GetWorldPosition (this Bone bone, UnityEngine.Transform spineGameObjectTransform, float positionScale, Vector2 positionOffset) { + return spineGameObjectTransform.TransformPoint(new Vector3(bone.WorldX * positionScale + positionOffset.x, bone.WorldY * positionScale + positionOffset.y)); + } + /// Gets a skeleton space UnityEngine.Quaternion representation of bone.WorldRotationX. public static Quaternion GetQuaternion (this Bone bone) { float halfRotation = Mathf.Atan2(bone.C, bone.A) * 0.5f; diff --git a/spine-unity/Assets/Spine/package.json b/spine-unity/Assets/Spine/package.json index 718a4cd55..ba2eb63e9 100644 --- a/spine-unity/Assets/Spine/package.json +++ b/spine-unity/Assets/Spine/package.json @@ -2,7 +2,7 @@ "name": "com.esotericsoftware.spine.spine-unity", "displayName": "spine-unity Runtime", "description": "This plugin provides the spine-unity runtime core.", - "version": "4.2.69", + "version": "4.2.70", "unity": "2018.3", "author": { "name": "Esoteric Software",