diff --git a/spine-unity/Assets/Examples/Getting Started/6 SkeletonGraphic.unity b/spine-unity/Assets/Examples/Getting Started/6 SkeletonGraphic.unity index 527adc097..0e6c167c3 100644 --- a/spine-unity/Assets/Examples/Getting Started/6 SkeletonGraphic.unity +++ b/spine-unity/Assets/Examples/Getting Started/6 SkeletonGraphic.unity @@ -135,13 +135,14 @@ RectTransform: m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_Children: + - {fileID: 759111375} - {fileID: 189134935} m_Father: {fileID: 289700665} - m_RootOrder: 0 + m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: -13.605, y: 239} + m_AnchoredPosition: {x: -13.604996, y: 239} m_SizeDelta: {x: 491, y: 708} m_Pivot: {x: 0.5, y: 0.14047737} --- !u!114 &57002145 @@ -165,6 +166,8 @@ MonoBehaviour: Version=1.0.0.0, Culture=neutral, PublicKeyToken=null skeletonDataAsset: {fileID: 11400000, guid: 3c48535ae5679204c950a22a7caaa5a4, type: 2} initialSkinName: default + initialFlipX: 0 + initialFlipY: 0 startingAnimation: main startingLoop: 1 timeScale: 1 @@ -185,6 +188,99 @@ CanvasRenderer: m_PrefabParentObject: {fileID: 0} m_PrefabInternal: {fileID: 0} m_GameObject: {fileID: 57002143} +--- !u!1 &140863499 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 140863500} + - component: {fileID: 140863503} + - component: {fileID: 140863502} + - component: {fileID: 140863501} + m_Layer: 5 + m_Name: Detached BoneFollowerGraphic + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &140863500 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 140863499} + m_LocalRotation: {x: -0, y: -0, z: 0.6421143, w: 0.766609} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 289700665} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: -11.670258, y: 578.2387} + m_SizeDelta: {x: 100, y: 54.64} + m_Pivot: {x: -0.15, y: -2.04} +--- !u!114 &140863501 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 140863499} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 0 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 50 + m_FontStyle: 1 + m_BestFit: 0 + m_MinSize: 5 + m_MaxSize: 50 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 1 + m_VerticalOverflow: 1 + m_LineSpacing: 1 + m_Text: Hello +--- !u!222 &140863502 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 140863499} +--- !u!114 &140863503 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 140863499} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b42a195b47491d34b9bcbc40898bcb29, type: 3} + m_Name: + m_EditorClassIdentifier: + skeletonGraphic: {fileID: 57002145} + initializeOnAwake: 1 + boneName: head + followBoneRotation: 1 + followSkeletonFlip: 1 + followLocalScale: 0 + followZPosition: 1 --- !u!1 &189134934 GameObject: m_ObjectHideFlags: 0 @@ -213,7 +309,7 @@ RectTransform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 57002144} - m_RootOrder: 0 + m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 1} m_AnchorMax: {x: 0.5, y: 1} @@ -379,6 +475,7 @@ RectTransform: m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_Children: + - {fileID: 140863500} - {fileID: 57002144} - {fileID: 1384013133} m_Father: {fileID: 2133858527} @@ -478,6 +575,99 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &759111374 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 5 + m_Component: + - component: {fileID: 759111375} + - component: {fileID: 759111378} + - component: {fileID: 759111377} + - component: {fileID: 759111376} + m_Layer: 5 + m_Name: Child BoneFollowerGraphic + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &759111375 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 759111374} + m_LocalRotation: {x: 0, y: 0, z: 0.012748675, w: 0.99991876} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 57002144} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 214.28879, y: 127.327515} + m_SizeDelta: {x: 35.7, y: 54.1} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &759111376 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 759111374} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f70555f144d8491a825f0804e09c671c, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 0 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 24 + m_FontStyle: 1 + m_BestFit: 0 + m_MinSize: 2 + m_MaxSize: 50 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 1 + m_VerticalOverflow: 1 + m_LineSpacing: 1 + m_Text: World! +--- !u!222 &759111377 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 759111374} +--- !u!114 &759111378 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 759111374} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b42a195b47491d34b9bcbc40898bcb29, type: 3} + m_Name: + m_EditorClassIdentifier: + skeletonGraphic: {fileID: 57002145} + initializeOnAwake: 1 + boneName: handL + followBoneRotation: 1 + followSkeletonFlip: 1 + followLocalScale: 0 + followZPosition: 1 --- !u!1 &774800193 GameObject: m_ObjectHideFlags: 0 @@ -912,7 +1102,7 @@ RectTransform: m_Children: - {fileID: 1066372096} m_Father: {fileID: 289700665} - m_RootOrder: 1 + m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -940,6 +1130,8 @@ MonoBehaviour: Version=1.0.0.0, Culture=neutral, PublicKeyToken=null skeletonDataAsset: {fileID: 11400000, guid: a467507a4ffb1d542a558739b2fede77, type: 2} initialSkinName: base + initialFlipX: 0 + initialFlipY: 0 startingAnimation: run startingLoop: 1 timeScale: 1 diff --git a/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs b/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs index ab0d8283d..52a414894 100644 --- a/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs +++ b/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs @@ -1641,21 +1641,21 @@ namespace Spine.Unity.Editor { } } - public static void DrawBoneNames (Transform transform, Skeleton skeleton) { + public static void DrawBoneNames (Transform transform, Skeleton skeleton, float positionScale = 1f) { GUIStyle style = BoneNameStyle; foreach (Bone b in skeleton.Bones) { - var pos = new Vector3(b.WorldX, b.WorldY, 0) + (new Vector3(b.A, b.C) * (b.Data.Length * 0.5f)); + var pos = new Vector3(b.WorldX * positionScale, b.WorldY * positionScale, 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) { + public static void DrawBones (Transform transform, Skeleton skeleton, float positionScale = 1f) { float boneScale = 1.8f; // Draw the root bone largest; - DrawCrosshairs2D(skeleton.Bones.Items[0].GetWorldPosition(transform), 0.08f); + DrawCrosshairs2D(skeleton.Bones.Items[0].GetWorldPosition(transform), 0.08f, positionScale); foreach (Bone b in skeleton.Bones) { - DrawBone(transform, b, boneScale); + DrawBone(transform, b, boneScale, positionScale); boneScale = 1f; } } @@ -1668,45 +1668,45 @@ namespace Spine.Unity.Editor { _boneWireBuffer[4] = _boneWireBuffer[0]; // closed polygon. return _boneWireBuffer; } - public static void DrawBoneWireframe (Transform transform, Bone b, Color color) { + public static void DrawBoneWireframe (Transform transform, Bone b, Color color, float skeletonRenderScale = 1f) { Handles.color = color; - var pos = new Vector3(b.WorldX, b.WorldY, 0); + var pos = new Vector3(b.WorldX * skeletonRenderScale, b.WorldY * skeletonRenderScale, 0); float length = b.Data.Length; if (length > 0) { Quaternion rot = Quaternion.Euler(0, 0, b.WorldRotationX); - Vector3 scale = Vector3.one * length * b.WorldScaleX; + Vector3 scale = Vector3.one * length * b.WorldScaleX * skeletonRenderScale; const float my = 1.5f; - scale.y *= (SpineHandles.handleScale + 1f) * 0.5f; - scale.y = Mathf.Clamp(scale.x, -my, my); + scale.y *= (SpineHandles.handleScale + 1) * 0.5f; + scale.y = Mathf.Clamp(scale.x, -my * skeletonRenderScale, my * skeletonRenderScale); Handles.DrawPolyLine(GetBoneWireBuffer(transform.localToWorldMatrix * Matrix4x4.TRS(pos, rot, scale))); var wp = transform.TransformPoint(pos); - DrawBoneCircle(wp, color, transform.forward); + DrawBoneCircle(wp, color, transform.forward, skeletonRenderScale); } else { var wp = transform.TransformPoint(pos); - DrawBoneCircle(wp, color, transform.forward); + DrawBoneCircle(wp, color, transform.forward, skeletonRenderScale); } } - public static void DrawBone (Transform transform, Bone b, float boneScale) { - var pos = new Vector3(b.WorldX, b.WorldY, 0); + public static void DrawBone (Transform transform, Bone b, float boneScale, float skeletonRenderScale = 1f) { + var pos = new Vector3(b.WorldX * skeletonRenderScale, b.WorldY * skeletonRenderScale, 0); float length = b.Data.Length; if (length > 0) { Quaternion rot = Quaternion.Euler(0, 0, b.WorldRotationX); - Vector3 scale = Vector3.one * length * b.WorldScaleX; + Vector3 scale = Vector3.one * length * b.WorldScaleX * skeletonRenderScale; const float my = 1.5f; scale.y *= (SpineHandles.handleScale + 1f) * 0.5f; - scale.y = Mathf.Clamp(scale.x, -my, my); + scale.y = Mathf.Clamp(scale.x, -my * skeletonRenderScale, my * skeletonRenderScale); SpineHandles.GetBoneMaterial().SetPass(0); Graphics.DrawMeshNow(SpineHandles.BoneMesh, transform.localToWorldMatrix * Matrix4x4.TRS(pos, rot, scale)); } else { var wp = transform.TransformPoint(pos); - DrawBoneCircle(wp, SpineHandles.BoneColor, transform.forward, boneScale); + DrawBoneCircle(wp, SpineHandles.BoneColor, transform.forward, boneScale * skeletonRenderScale); } } - public static void DrawBone (Transform transform, Bone b, float boneScale, Color color) { - var pos = new Vector3(b.WorldX, b.WorldY, 0); + public static void DrawBone (Transform transform, Bone b, float boneScale, Color color, float skeletonRenderScale = 1f) { + var pos = new Vector3(b.WorldX * skeletonRenderScale, b.WorldY * skeletonRenderScale, 0); float length = b.Data.Length; if (length > 0) { Quaternion rot = Quaternion.Euler(0, 0, b.WorldRotationX); @@ -1718,7 +1718,7 @@ namespace Spine.Unity.Editor { Graphics.DrawMeshNow(SpineHandles.BoneMesh, transform.localToWorldMatrix * Matrix4x4.TRS(pos, rot, scale)); } else { var wp = transform.TransformPoint(pos); - DrawBoneCircle(wp, color, transform.forward, boneScale); + DrawBoneCircle(wp, color, transform.forward, boneScale * skeletonRenderScale); } } @@ -1814,7 +1814,7 @@ namespace Spine.Unity.Editor { Handles.DrawLine(lastVert, firstVert); } - public static void DrawConstraints (Transform transform, Skeleton skeleton) { + public static void DrawConstraints (Transform transform, Skeleton skeleton, float skeletonRenderScale = 1f) { Vector3 targetPos; Vector3 pos; bool active; @@ -1826,19 +1826,19 @@ namespace Spine.Unity.Editor { handleColor = SpineHandles.TransformContraintColor; foreach (var tc in skeleton.TransformConstraints) { var targetBone = tc.Target; - targetPos = targetBone.GetWorldPosition(transform); + targetPos = targetBone.GetWorldPosition(transform, skeletonRenderScale); if (tc.TranslateMix > 0) { if (tc.TranslateMix != 1f) { Handles.color = handleColor; foreach (var b in tc.Bones) { - pos = b.GetWorldPosition(transform); + pos = b.GetWorldPosition(transform, skeletonRenderScale); Handles.DrawDottedLine(targetPos, pos, Thickness); } } - SpineHandles.DrawBoneCircle(targetPos, handleColor, normal, 1.3f); + SpineHandles.DrawBoneCircle(targetPos, handleColor, normal, 1.3f * skeletonRenderScale); Handles.color = handleColor; - SpineHandles.DrawCrosshairs(targetPos, 0.2f, targetBone.A, targetBone.B, targetBone.C, targetBone.D, transform); + SpineHandles.DrawCrosshairs(targetPos, 0.2f, targetBone.A, targetBone.B, targetBone.C, targetBone.D, transform, skeletonRenderScale); } } @@ -1846,25 +1846,25 @@ namespace Spine.Unity.Editor { handleColor = SpineHandles.IkColor; foreach (var ikc in skeleton.IkConstraints) { Bone targetBone = ikc.Target; - targetPos = targetBone.GetWorldPosition(transform); + targetPos = targetBone.GetWorldPosition(transform, skeletonRenderScale); var bones = ikc.Bones; active = ikc.Mix > 0; if (active) { - pos = bones.Items[0].GetWorldPosition(transform); + pos = bones.Items[0].GetWorldPosition(transform, skeletonRenderScale); switch (bones.Count) { case 1: { Handles.color = handleColor; Handles.DrawLine(targetPos, pos); SpineHandles.DrawBoneCircle(targetPos, handleColor, normal); var m = bones.Items[0].GetMatrix4x4(); - m.m03 = targetBone.WorldX; - m.m13 = targetBone.WorldY; + m.m03 = targetBone.WorldX * skeletonRenderScale; + m.m13 = targetBone.WorldY * skeletonRenderScale; SpineHandles.DrawArrowhead(transform.localToWorldMatrix * m); break; } case 2: { Bone childBone = bones.Items[1]; - Vector3 child = childBone.GetWorldPosition(transform); + Vector3 child = childBone.GetWorldPosition(transform, skeletonRenderScale); Handles.color = handleColor; Handles.DrawLine(child, pos); Handles.DrawLine(targetPos, child); @@ -1872,8 +1872,8 @@ namespace Spine.Unity.Editor { SpineHandles.DrawBoneCircle(child, handleColor, normal, 0.5f); SpineHandles.DrawBoneCircle(targetPos, handleColor, normal); var m = childBone.GetMatrix4x4(); - m.m03 = targetBone.WorldX; - m.m13 = targetBone.WorldY; + m.m03 = targetBone.WorldX * skeletonRenderScale; + m.m13 = targetBone.WorldY * skeletonRenderScale; SpineHandles.DrawArrowhead(transform.localToWorldMatrix * m); break; } @@ -1888,18 +1888,18 @@ namespace Spine.Unity.Editor { active = pc.TranslateMix > 0; if (active) foreach (var b in pc.Bones) - SpineHandles.DrawBoneCircle(b.GetWorldPosition(transform), handleColor, normal, 1f); + SpineHandles.DrawBoneCircle(b.GetWorldPosition(transform, skeletonRenderScale), handleColor, normal, 1f * skeletonRenderScale); } } - static void DrawCrosshairs2D (Vector3 position, float scale) { - scale *= SpineHandles.handleScale; + static void DrawCrosshairs2D (Vector3 position, float scale, float skeletonRenderScale = 1f) { + scale *= SpineHandles.handleScale * skeletonRenderScale; Handles.DrawLine(position + new Vector3(-scale, 0), position + new Vector3(scale, 0)); Handles.DrawLine(position + new Vector3(0, -scale), position + new Vector3(0, scale)); } - static void DrawCrosshairs (Vector3 position, float scale, float a, float b, float c, float d, Transform transform) { - scale *= SpineHandles.handleScale; + static void DrawCrosshairs (Vector3 position, float scale, float a, float b, float c, float d, Transform transform, float skeletonRenderScale = 1f) { + scale *= SpineHandles.handleScale * skeletonRenderScale; var xOffset = (Vector3)(new Vector2(a, c).normalized * scale); var yOffset = (Vector3)(new Vector2(b, d).normalized * scale); @@ -1941,7 +1941,7 @@ namespace Spine.Unity.Editor { float firstScale = 0.08f * scale; Handles.DrawSolidDisc(pos, normal, firstScale); const float Thickness = 0.03f; - float secondScale = firstScale - (Thickness * SpineHandles.handleScale); + float secondScale = firstScale - (Thickness * SpineHandles.handleScale * scale); if (secondScale > 0f) { Handles.color = new Color(0.3f, 0.3f, 0.3f, 0.5f); diff --git a/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/BoneFollowerGraphic.cs b/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/BoneFollowerGraphic.cs new file mode 100644 index 000000000..dd4e45bce --- /dev/null +++ b/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/BoneFollowerGraphic.cs @@ -0,0 +1,133 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace Spine.Unity { + [ExecuteInEditMode] + [DisallowMultipleComponent] + [AddComponentMenu("Spine/UI/BoneFollowerGraphic")] + public class BoneFollowerGraphic : MonoBehaviour { + public SkeletonGraphic skeletonGraphic; + public SkeletonGraphic SkeletonGraphic { + get { return skeletonGraphic; } + set { + skeletonGraphic = value; + Initialize(); + } + } + + public bool initializeOnAwake = true; + + /// If a bone isn't set in code, boneName is used to find the bone at the beginning. For runtime switching by name, use SetBoneByName. You can also set the BoneFollower.bone field directly. + [SpineBone(dataField: "skeletonGraphic")] + [SerializeField] public string boneName; + + public bool followBoneRotation = true; + [Tooltip("Follows the skeleton's flip state by controlling this Transform's local scale.")] + public bool followSkeletonFlip = true; + [Tooltip("Follows the target bone's local scale. BoneFollower cannot inherit world/skewed scale because of UnityEngine.Transform property limitations.")] + public bool followLocalScale = false; + public bool followZPosition = true; + + [System.NonSerialized] public Bone bone; + + Transform skeletonTransform; + bool skeletonTransformIsParent; + + [System.NonSerialized] public bool valid; + + /// + /// Sets the target bone by its bone name. Returns false if no bone was found. + public bool SetBone (string name) { + bone = skeletonGraphic.Skeleton.FindBone(name); + if (bone == null) { + Debug.LogError("Bone not found: " + name, this); + return false; + } + boneName = name; + return true; + } + + public void Awake () { + if (initializeOnAwake) Initialize(); + } + + public void Initialize () { + bone = null; + valid = skeletonGraphic != null && skeletonGraphic.IsValid; + if (!valid) return; + + skeletonTransform = skeletonGraphic.transform; +// skeletonGraphic.OnRebuild -= HandleRebuildRenderer; +// skeletonGraphic.OnRebuild += HandleRebuildRenderer; + skeletonTransformIsParent = Transform.ReferenceEquals(skeletonTransform, transform.parent); + + if (!string.IsNullOrEmpty(boneName)) + bone = skeletonGraphic.Skeleton.FindBone(boneName); + + #if UNITY_EDITOR + if (Application.isEditor) + LateUpdate(); + #endif + } + + public void LateUpdate () { + if (!valid) { + Initialize(); + return; + } + + #if UNITY_EDITOR + if (!Application.isPlaying) + skeletonTransformIsParent = Transform.ReferenceEquals(skeletonTransform, transform.parent); + #endif + + if (bone == null) { + if (string.IsNullOrEmpty(boneName)) return; + bone = skeletonGraphic.Skeleton.FindBone(boneName); + if (!SetBone(boneName)) return; + } + + var thisTransform = this.transform as RectTransform; + if (thisTransform == null) return; + + float scale = skeletonGraphic.canvas.referencePixelsPerUnit; + + if (skeletonTransformIsParent) { + // Recommended setup: Use local transform properties if Spine GameObject is the immediate parent + thisTransform.localPosition = new Vector3(bone.worldX * scale, bone.worldY * scale, 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)); + if (!followZPosition) targetWorldPosition.z = thisTransform.position.z; + + float boneWorldRotation = bone.WorldRotationX; + + Transform transformParent = thisTransform.parent; + if (transformParent != null) { + Matrix4x4 m = transformParent.localToWorldMatrix; + if (m.m00 * m.m11 - m.m01 * m.m10 < 0) // Determinant2D is negative + boneWorldRotation = -boneWorldRotation; + } + + if (followBoneRotation) { + Vector3 worldRotation = skeletonTransform.rotation.eulerAngles; + #if UNITY_5_6_OR_NEWER + thisTransform.SetPositionAndRotation(targetWorldPosition, Quaternion.Euler(worldRotation.x, worldRotation.y, skeletonTransform.rotation.eulerAngles.z + boneWorldRotation)); + #else + thisTransform.position = targetWorldPosition; + thisTransform.rotation = Quaternion.Euler(worldRotation.x, worldRotation.y, skeletonTransform.rotation.eulerAngles.z + bone.WorldRotationX); + #endif + } else { + thisTransform.position = targetWorldPosition; + } + } + + Vector3 localScale = followLocalScale ? new Vector3(bone.scaleX, bone.scaleY, 1f) : new Vector3(1f, 1f, 1f); + if (followSkeletonFlip) localScale.y *= bone.skeleton.flipX ^ bone.skeleton.flipY ? -1f : 1f; + thisTransform.localScale = localScale; + } + + } +} diff --git a/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/BoneFollowerGraphic.cs.meta b/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/BoneFollowerGraphic.cs.meta new file mode 100644 index 000000000..b2279663c --- /dev/null +++ b/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/BoneFollowerGraphic.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b42a195b47491d34b9bcbc40898bcb29 +timeCreated: 1499211965 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/BoneFollowerGraphicInspector.cs b/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/BoneFollowerGraphicInspector.cs new file mode 100644 index 000000000..1d74f0b0c --- /dev/null +++ b/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/BoneFollowerGraphicInspector.cs @@ -0,0 +1,172 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; + +using Spine.Unity; + +namespace Spine.Unity.Editor { + + using Editor = UnityEditor.Editor; + using Event = UnityEngine.Event; + + [CustomEditor(typeof(BoneFollowerGraphic)), CanEditMultipleObjects] + public class BoneFollowerGraphicInspector : Editor { + + SerializedProperty boneName, skeletonGraphic, followZPosition, followBoneRotation, followLocalScale, followSkeletonFlip; + BoneFollowerGraphic targetBoneFollower; + bool needsReset; + + #region Context Menu Item + [MenuItem ("CONTEXT/SkeletonGraphic/Add BoneFollower GameObject")] + static void AddBoneFollowerGameObject (MenuCommand cmd) { + var skeletonGraphic = cmd.context as SkeletonGraphic; + var go = new GameObject("BoneFollower", typeof(RectTransform)); + var t = go.transform; + t.SetParent(skeletonGraphic.transform); + t.localPosition = Vector3.zero; + + var f = go.AddComponent(); + f.skeletonGraphic = skeletonGraphic; + f.SetBone(skeletonGraphic.Skeleton.RootBone.Data.Name); + + EditorGUIUtility.PingObject(t); + + Undo.RegisterCreatedObjectUndo(go, "Add BoneFollowerGraphic"); + } + + // Validate + [MenuItem ("CONTEXT/SkeletonGraphic/Add BoneFollower GameObject", true)] + static bool ValidateAddBoneFollowerGameObject (MenuCommand cmd) { + var skeletonGraphic = cmd.context as SkeletonGraphic; + return skeletonGraphic.IsValid; + } + #endregion + + void OnEnable () { + skeletonGraphic = serializedObject.FindProperty("skeletonGraphic"); + boneName = serializedObject.FindProperty("boneName"); + followBoneRotation = serializedObject.FindProperty("followBoneRotation"); + followZPosition = serializedObject.FindProperty("followZPosition"); + followLocalScale = serializedObject.FindProperty("followLocalScale"); + followSkeletonFlip = serializedObject.FindProperty("followSkeletonFlip"); + + targetBoneFollower = (BoneFollowerGraphic)target; + if (targetBoneFollower.SkeletonGraphic != null) + targetBoneFollower.SkeletonGraphic.Initialize(false); + + if (!targetBoneFollower.valid || needsReset) { + targetBoneFollower.Initialize(); + targetBoneFollower.LateUpdate(); + needsReset = false; + SceneView.RepaintAll(); + } + } + + public void OnSceneGUI () { + var tbf = target as BoneFollowerGraphic; + var skeletonGraphicComponent = tbf.SkeletonGraphic; + if (skeletonGraphicComponent == null) return; + + var transform = skeletonGraphicComponent.transform; + var skeleton = skeletonGraphicComponent.Skeleton; + var canvas = skeletonGraphicComponent.canvas; + float positionScale = canvas == null ? 1f : skeletonGraphicComponent.canvas.referencePixelsPerUnit; + + if (string.IsNullOrEmpty(boneName.stringValue)) { + SpineHandles.DrawBones(transform, skeleton, positionScale); + SpineHandles.DrawBoneNames(transform, skeleton, positionScale); + Handles.Label(tbf.transform.position, "No bone selected", EditorStyles.helpBox); + } else { + var 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); + } + } + + override public void OnInspectorGUI () { + if (serializedObject.isEditingMultipleObjects) { + if (needsReset) { + needsReset = false; + foreach (var o in targets) { + var bf = (BoneFollower)o; + bf.Initialize(); + bf.LateUpdate(); + } + SceneView.RepaintAll(); + } + + EditorGUI.BeginChangeCheck(); + DrawDefaultInspector(); + needsReset |= EditorGUI.EndChangeCheck(); + return; + } + + if (needsReset && Event.current.type == EventType.Layout) { + targetBoneFollower.Initialize(); + targetBoneFollower.LateUpdate(); + needsReset = false; + SceneView.RepaintAll(); + } + serializedObject.Update(); + + // Find Renderer + if (skeletonGraphic.objectReferenceValue == null) { + SkeletonGraphic parentRenderer = targetBoneFollower.GetComponentInParent(); + if (parentRenderer != null && parentRenderer.gameObject != targetBoneFollower.gameObject) { + skeletonGraphic.objectReferenceValue = parentRenderer; + Debug.Log("Inspector automatically assigned BoneFollowerGraphic.SkeletonGraphic"); + } + } + + EditorGUILayout.PropertyField(skeletonGraphic); + var skeletonGraphicComponent = skeletonGraphic.objectReferenceValue as SkeletonGraphic; + if (skeletonGraphicComponent != null) { + if (skeletonGraphicComponent.gameObject == targetBoneFollower.gameObject) { + skeletonGraphic.objectReferenceValue = null; + EditorUtility.DisplayDialog("Invalid assignment.", "BoneFollowerGraphic can only follow a skeleton on a separate GameObject.\n\nCreate a new GameObject for your BoneFollower, or choose a SkeletonGraphic from a different GameObject.", "Ok"); + } + } + + if (!targetBoneFollower.valid) { + needsReset = true; + } + + if (targetBoneFollower.valid) { + EditorGUI.BeginChangeCheck(); + EditorGUILayout.PropertyField(boneName); + needsReset |= EditorGUI.EndChangeCheck(); + + EditorGUILayout.PropertyField(followBoneRotation); + EditorGUILayout.PropertyField(followZPosition); + EditorGUILayout.PropertyField(followLocalScale); + EditorGUILayout.PropertyField(followSkeletonFlip); + + //BoneFollowerInspector.RecommendRigidbodyButton(targetBoneFollower); + } else { + var boneFollowerSkeletonGraphic = targetBoneFollower.skeletonGraphic; + if (boneFollowerSkeletonGraphic == null) { + EditorGUILayout.HelpBox("SkeletonGraphic is unassigned. Please assign a SkeletonRenderer (SkeletonAnimation or SkeletonAnimator).", MessageType.Warning); + } else { + boneFollowerSkeletonGraphic.Initialize(false); + + if (boneFollowerSkeletonGraphic.skeletonDataAsset == null) + EditorGUILayout.HelpBox("Assigned SkeletonGraphic does not have SkeletonData assigned to it.", MessageType.Warning); + + if (!boneFollowerSkeletonGraphic.IsValid) + EditorGUILayout.HelpBox("Assigned SkeletonGraphic is invalid. Check target SkeletonGraphic, its SkeletonDataAsset or the console for other errors.", MessageType.Warning); + } + } + + var current = Event.current; + bool wasUndo = (current.type == EventType.ValidateCommand && current.commandName == "UndoRedoPerformed"); + if (wasUndo) + targetBoneFollower.Initialize(); + + serializedObject.ApplyModifiedProperties(); + } + + } +} \ No newline at end of file diff --git a/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/BoneFollowerGraphicInspector.cs.meta b/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/BoneFollowerGraphicInspector.cs.meta new file mode 100644 index 000000000..b3d60a49c --- /dev/null +++ b/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/BoneFollowerGraphicInspector.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: da44a8561fd243c43a1f77bda36de0eb +timeCreated: 1499279157 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/spine-unity/SkeletonExtensions.cs b/spine-unity/Assets/spine-unity/SkeletonExtensions.cs index 018297e75..0e2dd1f60 100644 --- a/spine-unity/Assets/spine-unity/SkeletonExtensions.cs +++ b/spine-unity/Assets/spine-unity/SkeletonExtensions.cs @@ -134,6 +134,10 @@ namespace Spine.Unity { return spineGameObjectTransform.TransformPoint(new Vector3(bone.worldX, bone.worldY)); } + public static Vector3 GetWorldPosition (this Bone bone, UnityEngine.Transform spineGameObjectTransform, float positionScale) { + return spineGameObjectTransform.TransformPoint(new Vector3(bone.worldX * positionScale, bone.worldY * positionScale)); + } + /// Gets a skeleton space UnityEngine.Quaternion representation of bone.WorldRotationX. public static Quaternion GetQuaternion (this Bone bone) { var halfRotation = Mathf.Atan2(bone.c, bone.a) * 0.5f;