From 3be056c42258d170af171697856c307e07a72527 Mon Sep 17 00:00:00 2001 From: pharan Date: Fri, 9 Feb 2018 17:45:16 +0800 Subject: [PATCH] [unity] Add PointFollower editor conveniences. --- .../spine-unity/Editor/PointFollowerEditor.cs | 188 ++++++++++++++++++ .../Editor/PointFollowerEditor.cs.meta | 12 ++ .../Editor/SpineEditorUtilities.cs | 29 +++ .../Assets/spine-unity/ISkeletonAnimation.cs | 13 +- .../Assets/spine-unity/PointFollower.cs | 18 +- .../Assets/spine-unity/SkeletonExtensions.cs | 32 +-- 6 files changed, 263 insertions(+), 29 deletions(-) create mode 100644 spine-unity/Assets/spine-unity/Editor/PointFollowerEditor.cs create mode 100644 spine-unity/Assets/spine-unity/Editor/PointFollowerEditor.cs.meta diff --git a/spine-unity/Assets/spine-unity/Editor/PointFollowerEditor.cs b/spine-unity/Assets/spine-unity/Editor/PointFollowerEditor.cs new file mode 100644 index 000000000..4baf3055c --- /dev/null +++ b/spine-unity/Assets/spine-unity/Editor/PointFollowerEditor.cs @@ -0,0 +1,188 @@ +/****************************************************************************** + * Spine Runtimes Software License v2.5 + * + * Copyright (c) 2013-2016, Esoteric Software + * All rights reserved. + * + * You are granted a perpetual, non-exclusive, non-sublicensable, and + * non-transferable license to use, install, execute, and perform the Spine + * Runtimes software and derivative works solely for personal or internal + * use. Without the written permission of Esoteric Software (see Section 2 of + * the Spine Software License Agreement), you may not (a) modify, translate, + * adapt, or develop new applications using the Spine Runtimes or otherwise + * create derivative works or improvements of the Spine Runtimes or (b) remove, + * delete, alter, or obscure any trademarks or any copyright, trademark, patent, + * or other intellectual property or proprietary rights notices on or in the + * Software, including any copy thereof. Redistributions in binary or source + * form must include this license and terms. + * + * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF + * USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +using UnityEditor; +using UnityEngine; + +namespace Spine.Unity.Editor { + + using Editor = UnityEditor.Editor; + using Event = UnityEngine.Event; + + [CustomEditor(typeof(PointFollower)), CanEditMultipleObjects] + public class PointFollowerEditor : Editor { + SerializedProperty slotName, pointAttachmentName, skeletonRenderer, followZPosition, followBoneRotation, followSkeletonFlip; + PointFollower targetPointFollower; + bool needsReset; + + #region Context Menu Item + [MenuItem("CONTEXT/SkeletonRenderer/Add PointFollower GameObject")] + static void AddBoneFollowerGameObject (MenuCommand cmd) { + var skeletonRenderer = cmd.context as SkeletonRenderer; + var go = new GameObject("PointFollower"); + var t = go.transform; + t.SetParent(skeletonRenderer.transform); + t.localPosition = Vector3.zero; + + var f = go.AddComponent(); + f.skeletonRenderer = skeletonRenderer; + + EditorGUIUtility.PingObject(t); + + Undo.RegisterCreatedObjectUndo(go, "Add PointFollower"); + } + + // Validate + [MenuItem("CONTEXT/SkeletonRenderer/Add PointFollower GameObject", true)] + static bool ValidateAddBoneFollowerGameObject (MenuCommand cmd) { + var skeletonRenderer = cmd.context as SkeletonRenderer; + return skeletonRenderer.valid; + } + #endregion + + void OnEnable () { + skeletonRenderer = serializedObject.FindProperty("skeletonRenderer"); + slotName = serializedObject.FindProperty("slotName"); + pointAttachmentName = serializedObject.FindProperty("pointAttachmentName"); + + targetPointFollower = (PointFollower)target; + if (targetPointFollower.skeletonRenderer != null) + targetPointFollower.skeletonRenderer.Initialize(false); + + if (!targetPointFollower.IsValid || needsReset) { + targetPointFollower.Initialize(); + targetPointFollower.LateUpdate(); + needsReset = false; + SceneView.RepaintAll(); + } + } + + public void OnSceneGUI () { + var tbf = target as PointFollower; + var skeletonRendererComponent = tbf.skeletonRenderer; + if (skeletonRendererComponent == null) + return; + + var skeleton = skeletonRendererComponent.skeleton; + var skeletonTransform = skeletonRendererComponent.transform; + + if (string.IsNullOrEmpty(pointAttachmentName.stringValue)) { + // Draw all active PointAttachments in the current skin + var currentSkin = skeleton.Skin; + if (currentSkin != skeleton.Data.DefaultSkin) DrawPointsInSkin(skeleton.Data.DefaultSkin, skeleton, skeletonTransform); + if (currentSkin != null) DrawPointsInSkin(currentSkin, skeleton, skeletonTransform); + } else { + int slotIndex = skeleton.FindSlotIndex(slotName.stringValue); + if (slotIndex >= 0) { + var slot = skeleton.Slots.Items[slotIndex]; + var point = skeleton.GetAttachment(slotIndex, pointAttachmentName.stringValue) as PointAttachment; + if (point != null) { + DrawPointAttachmentWithLabel(point, slot.Bone, skeletonTransform); + } + } + } + } + + static void DrawPointsInSkin (Skin skin, Skeleton skeleton, Transform transform) { + foreach (var skinEntry in skin.Attachments) { + var attachment = skinEntry.Value as PointAttachment; + if (attachment != null) { + var skinKey = skinEntry.Key; + var slot = skeleton.Slots.Items[skinKey.slotIndex]; + DrawPointAttachmentWithLabel(attachment, slot.Bone, transform); + } + } + } + + static void DrawPointAttachmentWithLabel (PointAttachment point, Bone bone, Transform transform) { + Vector3 labelOffset = new Vector3(0f, -0.2f, 0f); + SpineHandles.DrawPointAttachment(bone, point, transform); + Handles.Label(labelOffset + point.GetWorldPosition(bone, transform), point.Name, SpineHandles.PointNameStyle); + } + + 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) { + targetPointFollower.Initialize(); + targetPointFollower.LateUpdate(); + needsReset = false; + SceneView.RepaintAll(); + } + serializedObject.Update(); + + DrawDefaultInspector(); + + // Find Renderer + if (skeletonRenderer.objectReferenceValue == null) { + SkeletonRenderer parentRenderer = targetPointFollower.GetComponentInParent(); + if (parentRenderer != null && parentRenderer.gameObject != targetPointFollower.gameObject) { + skeletonRenderer.objectReferenceValue = parentRenderer; + Debug.Log("Inspector automatically assigned PointFollower.SkeletonRenderer"); + } + } + + var skeletonRendererReference = skeletonRenderer.objectReferenceValue as SkeletonRenderer; + if (skeletonRendererReference != null) { + if (skeletonRendererReference.gameObject == targetPointFollower.gameObject) { + skeletonRenderer.objectReferenceValue = null; + EditorUtility.DisplayDialog("Invalid assignment.", "PointFollower can only follow a skeleton on a separate GameObject.\n\nCreate a new GameObject for your PointFollower, or choose a SkeletonRenderer from a different GameObject.", "Ok"); + } + } + + if (!targetPointFollower.IsValid) { + needsReset = true; + } + + var current = Event.current; + bool wasUndo = (current.type == EventType.ValidateCommand && current.commandName == "UndoRedoPerformed"); + if (wasUndo) + targetPointFollower.Initialize(); + + serializedObject.ApplyModifiedProperties(); + } + } + +} diff --git a/spine-unity/Assets/spine-unity/Editor/PointFollowerEditor.cs.meta b/spine-unity/Assets/spine-unity/Editor/PointFollowerEditor.cs.meta new file mode 100644 index 000000000..210615680 --- /dev/null +++ b/spine-unity/Assets/spine-unity/Editor/PointFollowerEditor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 7c7e838a8ec295a4e9c53602f690f42f +timeCreated: 1518163038 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs b/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs index b703b5f24..e6b0f0605 100644 --- a/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs +++ b/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs @@ -1577,6 +1577,7 @@ namespace Spine.Unity.Editor { public static Color PathColor { get { return new Color(254/255f, 127/255f, 0); } } public static Color TransformContraintColor { get { return new Color(170/255f, 226/255f, 35/255f); } } public static Color IkColor { get { return new Color(228/255f,90/255f,43/255f); } } + public static Color PointColor { get { return new Color(1f, 1f, 0f, 1f); } } static Vector3[] _boneMeshVerts = { new Vector3(0, 0, 0), @@ -1677,6 +1678,17 @@ namespace Spine.Unity.Editor { } } + static GUIStyle _pointNameStyle; + public static GUIStyle PointNameStyle { + get { + if (_pointNameStyle == null) { + _pointNameStyle = new GUIStyle(SpineHandles.BoneNameStyle); + _pointNameStyle.normal.textColor = SpineHandles.PointColor; + } + return _pointNameStyle; + } + } + public static void DrawBoneNames (Transform transform, Skeleton skeleton, float positionScale = 1f) { GUIStyle style = BoneNameStyle; foreach (Bone b in skeleton.Bones) { @@ -1850,6 +1862,19 @@ namespace Spine.Unity.Editor { Handles.DrawLine(lastVert, firstVert); } + public static void DrawPointAttachment (Bone bone, PointAttachment pointAttachment, Transform skeletonTransform) { + if (bone == null) return; + if (pointAttachment == null) return; + + Vector2 localPos; + pointAttachment.ComputeWorldPosition(bone, out localPos.x, out localPos.y); + float localRotation = pointAttachment.ComputeWorldRotation(bone); + Matrix4x4 m = Matrix4x4.TRS(localPos, Quaternion.Euler(0, 0, localRotation), Vector3.one) * Matrix4x4.TRS(Vector3.right * 0.25f, Quaternion.identity, Vector3.one); + + DrawBoneCircle(skeletonTransform.TransformPoint(localPos), SpineHandles.PointColor, Vector3.back, 1.3f); + DrawArrowhead(skeletonTransform.localToWorldMatrix * m); + } + public static void DrawConstraints (Transform transform, Skeleton skeleton, float skeletonRenderScale = 1f) { Vector3 targetPos; Vector3 pos; @@ -1953,6 +1978,10 @@ namespace Spine.Unity.Editor { Graphics.DrawMeshNow(SpineHandles.ArrowheadMesh, Matrix4x4.TRS(pos, Quaternion.Euler(0, 0, localRotation), new Vector3(scale, scale, scale))); } + static void DrawArrowhead (Vector3 pos, Quaternion worldQuaternion) { + Graphics.DrawMeshNow(SpineHandles.ArrowheadMesh, pos, worldQuaternion, 0); + } + static void DrawArrowhead (Matrix4x4 m) { var s = SpineHandles.handleScale; m.m00 *= s; diff --git a/spine-unity/Assets/spine-unity/ISkeletonAnimation.cs b/spine-unity/Assets/spine-unity/ISkeletonAnimation.cs index ebacb56fc..bf31e878f 100644 --- a/spine-unity/Assets/spine-unity/ISkeletonAnimation.cs +++ b/spine-unity/Assets/spine-unity/ISkeletonAnimation.cs @@ -46,10 +46,11 @@ namespace Spine.Unity { /// Gets the SkeletonDataAsset of the Spine Component. SkeletonDataAsset SkeletonDataAsset { get; } } - + /// A Spine-Unity Component that manages a Spine.Skeleton instance, instantiated from a SkeletonDataAsset. public interface ISkeletonComponent { /// Gets the SkeletonDataAsset of the Spine Component. + //[System.Obsolete] SkeletonDataAsset SkeletonDataAsset { get; } /// Gets the Spine.Skeleton instance of the Spine Component. This is equivalent to SkeletonRenderer's .skeleton. @@ -61,4 +62,14 @@ namespace Spine.Unity { /// Gets the Spine.AnimationState of the animated Spine Component. This is equivalent to SkeletonAnimation.state. AnimationState AnimationState { get; } } + + /// A Spine-Unity Component that holds a reference to a SkeletonRenderer. + public interface IHasSkeletonRenderer { + SkeletonRenderer SkeletonRenderer { get; } + } + + /// A Spine-Unity Component that holds a reference to an ISkeletonComponent. + public interface IHasSkeletonComponent { + ISkeletonComponent SkeletonComponent { get; } + } } diff --git a/spine-unity/Assets/spine-unity/PointFollower.cs b/spine-unity/Assets/spine-unity/PointFollower.cs index 7cdf52590..f3702beb5 100644 --- a/spine-unity/Assets/spine-unity/PointFollower.cs +++ b/spine-unity/Assets/spine-unity/PointFollower.cs @@ -36,9 +36,11 @@ namespace Spine.Unity { [ExecuteInEditMode] [AddComponentMenu("Spine/Point Follower")] - public class PointFollower : MonoBehaviour { + public class PointFollower : MonoBehaviour, IHasSkeletonRenderer, IHasSkeletonComponent { - public SkeletonRenderer skeletonRenderer; + [SerializeField] public SkeletonRenderer skeletonRenderer; + public SkeletonRenderer SkeletonRenderer { get { return this.skeletonRenderer; } } + public ISkeletonComponent SkeletonComponent { get { return skeletonRenderer as ISkeletonComponent; } } [SpineSlot(dataField:"skeletonRenderer", includeNone: true)] public string slotName; @@ -55,16 +57,7 @@ namespace Spine.Unity { PointAttachment point; Bone bone; bool valid; - - #if UNITY_EDITOR - void OnValidate () { - if (skeletonRenderer == null) { - skeletonRenderer = GetComponent(); - if (skeletonRenderer == null) - skeletonRenderer = GetComponentInParent(); - } - } - #endif + public bool IsValid { get { return valid; } } public void Initialize () { valid = skeletonRenderer != null && skeletonRenderer.valid; @@ -92,6 +85,7 @@ namespace Spine.Unity { point = null; if (!string.IsNullOrEmpty(pointAttachmentName)) { var skeleton = skeletonRenderer.skeleton; + int slotIndex = skeleton.FindSlotIndex(slotName); if (slotIndex >= 0) { var slot = skeleton.slots.Items[slotIndex]; diff --git a/spine-unity/Assets/spine-unity/SkeletonExtensions.cs b/spine-unity/Assets/spine-unity/SkeletonExtensions.cs index b2760574c..d1a33f646 100644 --- a/spine-unity/Assets/spine-unity/SkeletonExtensions.cs +++ b/spine-unity/Assets/spine-unity/SkeletonExtensions.cs @@ -152,22 +152,6 @@ namespace Spine.Unity { return new Quaternion(0, 0, Mathf.Sin(halfRotation), Mathf.Cos(halfRotation)); } - /// Gets the PointAttachment's Unity World position using its Spine GameObject Transform. - public static Vector3 GetWorldPosition (this PointAttachment attachment, Slot slot, Transform spineGameObjectTransform) { - Vector3 skeletonSpacePosition; - skeletonSpacePosition.z = 0; - attachment.ComputeWorldPosition(slot.bone, out skeletonSpacePosition.x, out skeletonSpacePosition.y); - return spineGameObjectTransform.TransformPoint(skeletonSpacePosition); - } - - /// Gets the PointAttachment's Unity World position using its Spine GameObject Transform. - public static Vector3 GetWorldPosition (this PointAttachment attachment, Bone bone, Transform spineGameObjectTransform) { - Vector3 skeletonSpacePosition; - skeletonSpacePosition.z = 0; - attachment.ComputeWorldPosition(bone, out skeletonSpacePosition.x, out skeletonSpacePosition.y); - return spineGameObjectTransform.TransformPoint(skeletonSpacePosition); - } - /// Gets the internal bone matrix as a Unity bonespace-to-skeletonspace transformation matrix. public static Matrix4x4 GetMatrix4x4 (this Bone bone) { return new Matrix4x4 { @@ -281,6 +265,22 @@ namespace Spine.Unity { return buffer; } + + /// Gets the PointAttachment's Unity World position using its Spine GameObject Transform. + public static Vector3 GetWorldPosition (this PointAttachment attachment, Slot slot, Transform spineGameObjectTransform) { + Vector3 skeletonSpacePosition; + skeletonSpacePosition.z = 0; + attachment.ComputeWorldPosition(slot.bone, out skeletonSpacePosition.x, out skeletonSpacePosition.y); + return spineGameObjectTransform.TransformPoint(skeletonSpacePosition); + } + + /// Gets the PointAttachment's Unity World position using its Spine GameObject Transform. + public static Vector3 GetWorldPosition (this PointAttachment attachment, Bone bone, Transform spineGameObjectTransform) { + Vector3 skeletonSpacePosition; + skeletonSpacePosition.z = 0; + attachment.ComputeWorldPosition(bone, out skeletonSpacePosition.x, out skeletonSpacePosition.y); + return spineGameObjectTransform.TransformPoint(skeletonSpacePosition); + } #endregion } }