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;