diff --git a/spine-unity/Assets/spine-unity/Asset Types/Editor/SkeletonDataAssetInspector.cs b/spine-unity/Assets/spine-unity/Asset Types/Editor/SkeletonDataAssetInspector.cs index 90f603c2b..b991889d6 100644 --- a/spine-unity/Assets/spine-unity/Asset Types/Editor/SkeletonDataAssetInspector.cs +++ b/spine-unity/Assets/spine-unity/Asset Types/Editor/SkeletonDataAssetInspector.cs @@ -24,20 +24,21 @@ namespace Spine.Unity.Editor { static bool showAttachments = false; #if SPINE_BAKING - static bool showBaking = false; + static bool isBakingExpanded = false; static bool bakeAnimations = true; static bool bakeIK = true; static SendMessageOptions bakeEventOptions = SendMessageOptions.DontRequireReceiver; + const string ShowBakingPrefsKey = "SkeletonDataAssetInspector_showUnity"; #endif SerializedProperty atlasAssets, skeletonJSON, scale, fromAnimation, toAnimation, duration, defaultMix; - #if SPINE_SKELETON_ANIMATOR - static bool showMecanim = false; - SerializedProperty controller; + #if SPINE_TK2D + SerializedProperty spriteCollection; #endif - #if SPINE_TK2D - private SerializedProperty spriteCollection; + #if SPINE_SKELETON_ANIMATOR + static bool isMecanimExpanded = false; + SerializedProperty controller; #endif bool m_initialized = false; @@ -48,42 +49,48 @@ namespace Spine.Unity.Editor { List warnings = new List(); + GUIStyle activePlayButtonStyle, idlePlayButtonStyle; + void OnEnable () { SpineEditorUtilities.ConfirmInitialization(); atlasAssets = serializedObject.FindProperty("atlasAssets"); - atlasAssets.isExpanded = true; skeletonJSON = serializedObject.FindProperty("skeletonJSON"); scale = serializedObject.FindProperty("scale"); fromAnimation = serializedObject.FindProperty("fromAnimation"); toAnimation = serializedObject.FindProperty("toAnimation"); duration = serializedObject.FindProperty("duration"); defaultMix = serializedObject.FindProperty("defaultMix"); + + idlePlayButtonStyle = new GUIStyle(EditorStyles.toolbarButton); + activePlayButtonStyle = new GUIStyle(EditorStyles.toolbarButton); + activePlayButtonStyle.normal.textColor = Color.red; + #if SPINE_SKELETON_ANIMATOR controller = serializedObject.FindProperty("controller"); #endif + #if SPINE_TK2D + atlasAssets.isExpanded = false; spriteCollection = serializedObject.FindProperty("spriteCollection"); + #else + atlasAssets.isExpanded = true; + #endif + + #if SPINE_BAKING + isBakingExpanded = EditorPrefs.GetBool(ShowBakingPrefsKey, false); #endif m_skeletonDataAsset = (SkeletonDataAsset)target; m_skeletonDataAssetGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(m_skeletonDataAsset)); - - EditorApplication.update += Update; - + EditorApplication.update += EditorUpdate; m_skeletonData = m_skeletonDataAsset.GetSkeletonData(false); - - #if SPINE_BAKING - showBaking = EditorPrefs.GetBool("SkeletonDataAssetInspector_showUnity", false); - #endif - - InitPreview(); RepopulateWarnings(); } void OnDestroy () { m_initialized = false; - EditorApplication.update -= Update; + EditorApplication.update -= EditorUpdate; this.DestroyPreviewInstances(); if (this.m_previewUtility != null) { this.m_previewUtility.Cleanup(); @@ -101,26 +108,26 @@ namespace Spine.Unity.Editor { using (new EditorGUI.DisabledGroupScope(spriteCollection.objectReferenceValue != null)) { EditorGUILayout.PropertyField(atlasAssets, true); } + EditorGUILayout.LabelField("spine-tk2d", EditorStyles.boldLabel); EditorGUILayout.PropertyField(spriteCollection, true); #endif + EditorGUILayout.Space(); EditorGUILayout.PropertyField(skeletonJSON); EditorGUILayout.PropertyField(scale); + EditorGUILayout.Space(); + if (EditorGUI.EndChangeCheck()) { if (serializedObject.ApplyModifiedProperties()) { - if (m_previewUtility != null) { m_previewUtility.Cleanup(); m_previewUtility = null; } - RepopulateWarnings(); OnEnable(); return; } - } - - + if (m_skeletonData != null) { DrawAnimationStateInfo(); DrawAnimationList(); @@ -129,10 +136,22 @@ namespace Spine.Unity.Editor { } else { - DrawReimportButton(); - //Show Warnings - foreach (var str in warnings) - EditorGUILayout.LabelField(new GUIContent(str, SpineEditorUtilities.Icons.warning)); + #if !SPINE_TK2D + // Reimport Button + using (new EditorGUI.DisabledGroupScope(skeletonJSON.objectReferenceValue == null)) { + if (GUILayout.Button(new GUIContent("Attempt Reimport", SpineEditorUtilities.Icons.warning))) { + DoReimport(); + return; + } + } + #else + EditorGUILayout.HelpBox("Couldn't load SkeletonData.", MessageType.Error); + #endif + + // List warnings. + foreach (var line in warnings) + EditorGUILayout.LabelField(new GUIContent(line, SpineEditorUtilities.Icons.warning)); + } if(!Application.isPlaying) @@ -141,37 +160,58 @@ namespace Spine.Unity.Editor { void DrawUnityTools () { #if SPINE_SKELETON_ANIMATOR - showMecanim = EditorGUILayout.Foldout(showMecanim, new GUIContent("SkeletonAnimator", SpineEditorUtilities.Icons.unityIcon)); - if (showMecanim) { + isMecanimExpanded = EditorGUILayout.Foldout(isMecanimExpanded, new GUIContent("SkeletonAnimator", SpineEditorUtilities.Icons.unityIcon)); + if (isMecanimExpanded) { EditorGUI.indentLevel++; EditorGUILayout.PropertyField(controller, new GUIContent("Controller", SpineEditorUtilities.Icons.controllerIcon)); if (controller.objectReferenceValue == null) { + + // Generate Mecanim Controller Button using (new GUILayout.HorizontalScope()) { - GUILayout.Space(32); - if (GUILayout.Button(new GUIContent("Generate Mecanim Controller"), GUILayout.Width(195), GUILayout.Height(20))) + GUILayout.Space(EditorGUIUtility.labelWidth); + if (GUILayout.Button(new GUIContent("Generate Mecanim Controller"), GUILayout.Height(20))) SkeletonBaker.GenerateMecanimAnimationClips(m_skeletonDataAsset); } - EditorGUILayout.LabelField("SkeletonAnimator is the Mecanim alternative to SkeletonAnimation. It is not required.", EditorStyles.miniLabel); + EditorGUILayout.HelpBox("SkeletonAnimator is the Mecanim alternative to SkeletonAnimation.\nIt is not required.", MessageType.Info); + } else { + + // Update AnimationClips button. using (new GUILayout.HorizontalScope()) { GUILayout.Space(EditorGUIUtility.labelWidth); if (GUILayout.Button(new GUIContent("Force Update AnimationClips"), GUILayout.Height(20))) SkeletonBaker.GenerateMecanimAnimationClips(m_skeletonDataAsset); } + } EditorGUI.indentLevel--; } #endif #if SPINE_BAKING - bool pre = showBaking; - showBaking = EditorGUILayout.Foldout(showBaking, new GUIContent("Baking", SpineEditorUtilities.Icons.unityIcon)); - if (pre != showBaking) - EditorPrefs.SetBool("SkeletonDataAssetInspector_showUnity", showBaking); + bool pre = isBakingExpanded; + isBakingExpanded = EditorGUILayout.Foldout(isBakingExpanded, new GUIContent("Baking", SpineEditorUtilities.Icons.unityIcon)); + if (pre != isBakingExpanded) + EditorPrefs.SetBool(ShowBakingPrefsKey, isBakingExpanded); - if (showBaking) { + if (isBakingExpanded) { EditorGUI.indentLevel++; - EditorGUILayout.HelpBox("WARNING!\n\nBaking is NOT the same as SkeletonAnimator!\nDoes not support the following:\n\tFlipX or Y\n\tInheritScale\n\tColor Keys\n\tDraw Order Keys\n\tIK and Curves are sampled at 60fps and are not realtime.\n\tPlease read SkeletonBaker.cs comments for full details.\n\nThe main use of Baking is to export Spine projects to be used without the Spine Runtime (ie: for sale on the Asset Store, or background objects that are animated only with a wind noise generator)", MessageType.Warning, true); + const string BakingWarningMessage = + "WARNING!" + + "\nBaking is NOT the same as SkeletonAnimator!" + + + "\n\nThe main use of Baking is to export Spine projects to be used without the Spine Runtime (ie: for sale on the Asset Store, or background objects that are animated only with a wind noise generator)" + + + "\n\nBaking also does not support the following:" + + "\n\tDisabled transform inheritance" + + "\n\tShear" + + "\n\tColor Keys" + + "\n\tDraw Order Keys" + + "\n\tAll Constraint types" + + + "\n\nCurves are sampled at 60fps and are not realtime." + + "\nPlease read SkeletonBaker.cs comments for full details."; + EditorGUILayout.HelpBox(BakingWarningMessage, MessageType.Warning, true); EditorGUI.indentLevel++; bakeAnimations = EditorGUILayout.Toggle("Bake Animations", bakeAnimations); @@ -181,16 +221,20 @@ namespace Spine.Unity.Editor { bakeEventOptions = (SendMessageOptions)EditorGUILayout.EnumPopup("Event Options", bakeEventOptions); EditorGUI.indentLevel--; } - + + // Bake Skin buttons. using (new GUILayout.HorizontalScope()) { if (GUILayout.Button(new GUIContent("Bake All Skins", SpineEditorUtilities.Icons.unityIcon), GUILayout.Height(32), GUILayout.Width(150))) SkeletonBaker.BakeToPrefab(m_skeletonDataAsset, m_skeletonData.Skins, "", bakeAnimations, bakeIK, bakeEventOptions); - string skinName = ""; - - if (m_skeletonAnimation != null && m_skeletonAnimation.skeleton != null) { // If m_skeletonAnimation is lazy-instantiated, this can cause contents to change between Layout and Repaint events, which can cause scope errors. + // If m_skeletonAnimation is lazy-instantiated elsewhere, this can cause contents to change between Layout and Repaint events, causing scope errors. + if (m_skeletonData != null && m_skeletonAnimation == null) + InitPreview(); + + if (m_skeletonAnimation != null && m_skeletonAnimation.skeleton != null) { Skin bakeSkin = m_skeletonAnimation.skeleton.Skin; + string skinName = ""; if (bakeSkin == null) { skinName = "Default"; bakeSkin = m_skeletonData.Skins.Items[0]; @@ -203,7 +247,7 @@ namespace Spine.Unity.Editor { using (new GUILayout.HorizontalScope()) { GUILayout.Label(new GUIContent("Skins", SpineEditorUtilities.Icons.skinsRoot), GUILayout.Width(50)); if (GUILayout.Button(skinName, EditorStyles.popup, GUILayout.Width(196))) { - SelectSkinContext(); + DrawSkinDropdown(); } } } @@ -216,16 +260,6 @@ namespace Spine.Unity.Editor { #endif } - void DrawReimportButton () { - bool isJsonFieldEmpty = skeletonJSON.objectReferenceValue == null; - using (new EditorGUI.DisabledGroupScope(isJsonFieldEmpty)) { - if (GUILayout.Button(new GUIContent("Attempt Reimport", SpineEditorUtilities.Icons.warning))) { - DoReimport(); - return; - } - } - } - void DoReimport () { SpineEditorUtilities.ImportSpineContent(new string[] { AssetDatabase.GetAssetPath(skeletonJSON.objectReferenceValue) }, true); @@ -248,7 +282,6 @@ namespace Spine.Unity.Editor { EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(defaultMix); - // Animation names var animations = new string[m_skeletonData.Animations.Count]; for (int i = 0; i < animations.Length; i++) animations[i] = m_skeletonData.Animations.Items[i].Name; @@ -268,6 +301,7 @@ namespace Spine.Unity.Editor { } } } + using (new EditorGUILayout.HorizontalScope()) { EditorGUILayout.Space(); if (GUILayout.Button("Add Mix")) { @@ -284,7 +318,6 @@ namespace Spine.Unity.Editor { serializedObject.ApplyModifiedProperties(); needToSerialize = true; } - } void DrawAnimationList () { @@ -303,39 +336,35 @@ namespace Spine.Unity.Editor { } EditorGUILayout.LabelField("Name", "Duration"); - foreach (Spine.Animation a in m_skeletonData.Animations) { + foreach (Spine.Animation animation in m_skeletonData.Animations) { using (new GUILayout.HorizontalScope()) { if (m_skeletonAnimation != null && m_skeletonAnimation.state != null) { - if (m_skeletonAnimation.state.GetCurrent(0) != null && m_skeletonAnimation.state.GetCurrent(0).Animation == a) { - GUI.contentColor = Color.red; - if (GUILayout.Button("\u25BA", EditorStyles.toolbarButton, GUILayout.Width(24))) { + var activeTrack = m_skeletonAnimation.state.GetCurrent(0); + if (activeTrack != null && activeTrack.Animation == animation) { + if (GUILayout.Button("\u25BA", activePlayButtonStyle, GUILayout.Width(24))) { StopAnimation(); } - GUI.contentColor = Color.white; } else { - if (GUILayout.Button("\u25BA", EditorStyles.toolbarButton, GUILayout.Width(24))) { - PlayAnimation(a.Name, true); + if (GUILayout.Button("\u25BA", idlePlayButtonStyle, GUILayout.Width(24))) { + PlayAnimation(animation.Name, true); } } } else { - GUILayout.Label("?", GUILayout.Width(24)); + GUILayout.Label("-", GUILayout.Width(24)); } - EditorGUILayout.LabelField(new GUIContent(a.Name, SpineEditorUtilities.Icons.animation), new GUIContent(a.Duration.ToString("f3") + "s" + ("(" + (Mathf.RoundToInt(a.Duration * 30)) + ")").PadLeft(12, ' '))); + EditorGUILayout.LabelField(new GUIContent(animation.Name, SpineEditorUtilities.Icons.animation), new GUIContent(animation.Duration.ToString("f3") + "s" + ("(" + (Mathf.RoundToInt(animation.Duration * 30)) + ")").PadLeft(12, ' '))); } } } - void DrawSlotList () { showSlotList = EditorGUILayout.Foldout(showSlotList, new GUIContent("Slots", SpineEditorUtilities.Icons.slotRoot)); - if (!showSlotList) - return; - - if (m_skeletonAnimation == null || m_skeletonAnimation.skeleton == null) - return; + if (!showSlotList) return; + if (m_skeletonAnimation == null || m_skeletonAnimation.skeleton == null) return; EditorGUI.indentLevel++; + try { showAttachments = EditorGUILayout.ToggleLeft("Show Attachments", showAttachments); } catch { @@ -346,10 +375,7 @@ namespace Spine.Unity.Editor { List slotAttachmentNames = new List(); List defaultSkinAttachmentNames = new List(); var defaultSkin = m_skeletonData.Skins.Items[0]; - Skin skin = m_skeletonAnimation.skeleton.Skin; - if (skin == null) { - skin = defaultSkin; - } + Skin skin = m_skeletonAnimation.skeleton.Skin ?? defaultSkin; for (int i = m_skeletonAnimation.skeleton.Slots.Count - 1; i >= 0; i--) { Slot slot = m_skeletonAnimation.skeleton.Slots.Items[i]; @@ -364,7 +390,6 @@ namespace Spine.Unity.Editor { skin.FindNamesForSlot(i, slotAttachmentNames); skin.FindAttachmentsForSlot(i, slotAttachments); - if (skin != defaultSkin) { defaultSkin.FindNamesForSlot(i, defaultSkinAttachmentNames); defaultSkin.FindNamesForSlot(i, slotAttachmentNames); @@ -375,7 +400,7 @@ namespace Spine.Unity.Editor { for (int a = 0; a < slotAttachments.Count; a++) { Attachment attachment = slotAttachments[a]; - string name = slotAttachmentNames[a]; + string attachmentName = slotAttachmentNames[a]; Texture2D icon = null; var type = attachment.GetType(); @@ -391,15 +416,15 @@ namespace Spine.Unity.Editor { else icon = SpineEditorUtilities.Icons.warning; - //TODO: Waterboard Nate + // MITCH: left todo: Waterboard Nate //if (name != attachment.Name) //icon = SpineEditorUtilities.Icons.skinPlaceholder; bool initialState = slot.Attachment == attachment; - bool toggled = EditorGUILayout.ToggleLeft(new GUIContent(name, icon), slot.Attachment == attachment); + bool toggled = EditorGUILayout.ToggleLeft(new GUIContent(attachmentName, icon), slot.Attachment == attachment); - if (!defaultSkinAttachmentNames.Contains(name)) { + if (!defaultSkinAttachmentNames.Contains(attachmentName)) { Rect skinPlaceHolderIconRect = GUILayoutUtility.GetLastRect(); skinPlaceHolderIconRect.width = SpineEditorUtilities.Icons.skinPlaceholder.width; skinPlaceHolderIconRect.height = SpineEditorUtilities.Icons.skinPlaceholder.height; @@ -407,11 +432,7 @@ namespace Spine.Unity.Editor { } if (toggled != initialState) { - if (toggled) { - slot.Attachment = attachment; - } else { - slot.Attachment = null; - } + slot.Attachment = toggled ? attachment : null; m_requireRefresh = true; } } @@ -420,18 +441,17 @@ namespace Spine.Unity.Editor { } EditorGUI.indentLevel--; } - - + void RepopulateWarnings () { warnings.Clear(); - if (skeletonJSON.objectReferenceValue == null) + if (skeletonJSON.objectReferenceValue == null) { warnings.Add("Missing Skeleton JSON"); - else { - + } else { if (SpineEditorUtilities.IsValidSpineData((TextAsset)skeletonJSON.objectReferenceValue) == false) { warnings.Add("Skeleton data file is not a valid JSON or binary file."); } else { + #if !SPINE_TK2D bool detectedNullAtlasEntry = false; var atlasList = new List(); for (int i = 0; i < atlasAssets.arraySize; i++) { @@ -446,7 +466,7 @@ namespace Spine.Unity.Editor { if (detectedNullAtlasEntry) warnings.Add("AtlasAsset elements cannot be Null"); else { - //get requirements + // Get requirements. var missingPaths = SpineEditorUtilities.GetRequiredAtlasRegions(AssetDatabase.GetAssetPath((TextAsset)skeletonJSON.objectReferenceValue)); foreach (var atlas in atlasList) { @@ -462,11 +482,18 @@ namespace Spine.Unity.Editor { warnings.Add("Missing Region: '" + str + "'"); } + #else + if (spriteCollection.objectReferenceValue == null) { + warnings.Add("SkeletonDataAsset requires tk2DSpriteCollectionData."); + } else { + warnings.Add("Your sprite collection may have missing images."); + } + #endif } } } - // Preview window stuff + #region Preview Window PreviewRenderUtility m_previewUtility; GameObject m_previewInstance; Vector2 previewDir; @@ -526,10 +553,15 @@ namespace Spine.Unity.Editor { void CreatePreviewInstances () { this.DestroyPreviewInstances(); + + var skeletonDataAsset = (SkeletonDataAsset)target; + if (skeletonDataAsset.GetSkeletonData(false) == null) + return; + if (this.m_previewInstance == null) { string skinName = EditorPrefs.GetString(m_skeletonDataAssetGUID + "_lastSkin", ""); - m_previewInstance = SpineEditorUtilities.InstantiateSkeletonAnimation((SkeletonDataAsset)target, skinName).gameObject; + m_previewInstance = SpineEditorUtilities.InstantiateSkeletonAnimation(skeletonDataAsset, skinName).gameObject; m_previewInstance.hideFlags = HideFlags.HideAndDontSave; m_previewInstance.layer = 0x1f; @@ -555,7 +587,7 @@ namespace Spine.Unity.Editor { } public override bool HasPreviewGUI () { - //TODO: validate json data + // MITCH: left todo: validate json data for (int i = 0; i < atlasAssets.arraySize; i++) { var prop = atlasAssets.GetArrayElementAtIndex(i); @@ -584,8 +616,8 @@ namespace Spine.Unity.Editor { DrawSkinToolbar(r); NormalizedTimeBar(r); - //TODO: implement panning - // this.previewDir = Drag2D(this.previewDir, r); + // MITCH: left a todo: Implement panning + // this.previewDir = Drag2D(this.previewDir, r); MouseScroll(r); } @@ -593,14 +625,13 @@ namespace Spine.Unity.Editor { Vector3 m_posGoal = new Vector3(0, 0, -10); double m_adjustFrameEndTime = 0; - private void AdjustCameraGoals (bool calculateMixTime) { + void AdjustCameraGoals (bool calculateMixTime) { if (this.m_previewInstance == null) return; if (calculateMixTime) { - if (m_skeletonAnimation.state.GetCurrent(0) != null) { + if (m_skeletonAnimation.state.GetCurrent(0) != null) m_adjustFrameEndTime = EditorApplication.timeSinceStartup + m_skeletonAnimation.state.GetCurrent(0).Mix; - } } GameObject go = this.m_previewInstance; @@ -609,17 +640,16 @@ namespace Spine.Unity.Editor { m_posGoal = bounds.center + new Vector3(0, 0, -10); } - private void AdjustCameraGoals () { + void AdjustCameraGoals () { AdjustCameraGoals(false); } - private void AdjustCamera () { + void AdjustCamera () { if (m_previewUtility == null) return; - if (EditorApplication.timeSinceStartup < m_adjustFrameEndTime) { + if (EditorApplication.timeSinceStartup < m_adjustFrameEndTime) AdjustCameraGoals(); - } float orthoSet = Mathf.Lerp(this.m_previewUtility.m_Camera.orthographicSize, m_orthoGoal, 0.1f); @@ -635,17 +665,14 @@ namespace Spine.Unity.Editor { } } - private void DoRenderPreview (bool drawHandles) { + void DoRenderPreview (bool drawHandles) { GameObject go = this.m_previewInstance; if (m_requireRefresh && go != null) { go.GetComponent().enabled = true; - if (EditorApplication.isPlaying) { - //do nothing - } else { + if (!EditorApplication.isPlaying) m_skeletonAnimation.Update((Time.realtimeSinceStartup - m_lastTime)); - } m_lastTime = Time.realtimeSinceStartup; @@ -666,10 +693,8 @@ namespace Spine.Unity.Editor { Handles.SetCamera(m_previewUtility.m_Camera); foreach (var slot in m_skeletonAnimation.skeleton.Slots) { var boundingBoxAttachment = slot.Attachment as BoundingBoxAttachment; - - if (boundingBoxAttachment != null) { + if (boundingBoxAttachment != null) DrawBoundingBox (slot.Bone, boundingBoxAttachment); - } } } @@ -692,9 +717,8 @@ namespace Spine.Unity.Editor { vert.x = worldVerts[i]; vert.y = worldVerts[i + 1]; - if (i > 0) { + if (i > 0) Handles.DrawLine(lastVert, vert); - } lastVert = vert; } @@ -703,7 +727,7 @@ namespace Spine.Unity.Editor { } - void Update () { + void EditorUpdate () { AdjustCamera(); if (m_playing) { @@ -740,29 +764,11 @@ namespace Spine.Unity.Editor { popRect.x += 44; if (GUI.Button(popRect, label, EditorStyles.popup)) { - SelectSkinContext(); + DrawSkinDropdown(); } } } - - void SelectSkinContext () { - var menu = new GenericMenu(); - foreach (Skin s in m_skeletonData.Skins) { - menu.AddItem(new GUIContent(s.Name), this.m_skeletonAnimation.skeleton.Skin == s, SetSkin, (object)s); - } - menu.ShowAsContext(); - } - - void SetSkin (object o) { - Skin skin = (Skin)o; - - m_skeletonAnimation.initialSkinName = skin.Name; - m_skeletonAnimation.Initialize(true); - m_requireRefresh = true; - - EditorPrefs.SetString(m_skeletonDataAssetGUID + "_lastSkin", skin.Name); - } - + void NormalizedTimeBar (Rect r) { if (m_skeletonAnimation == null) return; @@ -792,11 +798,10 @@ namespace Spine.Unity.Editor { GUI.color = Color.white; for (int i = 0; i < m_animEvents.Count; i++) { - //TODO: Tooltip + // MITCH: left todo: Tooltip //Spine.Event spev = animEvents[i]; float fr = m_animEventFrames[i]; - var evRect = new Rect(barRect); evRect.x = Mathf.Clamp(((fr / t.Animation.Duration) * width) - (SpineEditorUtilities.Icons._event.width / 2), barRect.x, float.MaxValue); evRect.width = SpineEditorUtilities.Icons._event.width; @@ -804,20 +809,17 @@ namespace Spine.Unity.Editor { evRect.y += SpineEditorUtilities.Icons._event.height; GUI.DrawTexture(evRect, SpineEditorUtilities.Icons._event); - - //TODO: Tooltip - /* - UnityEngine.Event ev = UnityEngine.Event.current; - if(ev.isMouse){ - if(evRect.Contains(ev.mousePosition)){ - Rect tooltipRect = new Rect(evRect); - tooltipRect.width = 500; - tooltipRect.y -= 4; - tooltipRect.x += 4; - GUI.Label(tooltipRect, spev.Data.Name); - } - } - */ + // MITCH: left todo: Tooltip +// UnityEngine.Event ev = UnityEngine.Event.current; +// if (ev.isMouse) { +// if (evRect.Contains(ev.mousePosition)) { +// Rect tooltipRect = new Rect(evRect); +// tooltipRect.width = 500; +// tooltipRect.y -= 4; +// tooltipRect.x += 4; +// GUI.Label(tooltipRect, spev.Data.Name); +// } +// } } } } @@ -825,11 +827,9 @@ namespace Spine.Unity.Editor { void MouseScroll (Rect position) { UnityEngine.Event current = UnityEngine.Event.current; int controlID = GUIUtility.GetControlID(SliderHash, FocusType.Passive); - switch (current.GetTypeForControl(controlID)) { case EventType.ScrollWheel: if (position.Contains(current.mousePosition)) { - m_orthoGoal += current.delta.y; m_orthoGoal = Mathf.Max(0.01f, m_orthoGoal); GUIUtility.hotControl = controlID; @@ -837,50 +837,49 @@ namespace Spine.Unity.Editor { } break; } - } - //TODO: Implement preview panning + // MITCH: left todo: Implement preview panning /* - static Vector2 Drag2D(Vector2 scrollPosition, Rect position) - { - int controlID = GUIUtility.GetControlID(sliderHash, FocusType.Passive); - UnityEngine.Event current = UnityEngine.Event.current; - switch (current.GetTypeForControl(controlID)) + static Vector2 Drag2D(Vector2 scrollPosition, Rect position) { - case EventType.MouseDown: - if (position.Contains(current.mousePosition) && (position.width > 50f)) + int controlID = GUIUtility.GetControlID(sliderHash, FocusType.Passive); + UnityEngine.Event current = UnityEngine.Event.current; + switch (current.GetTypeForControl(controlID)) { - GUIUtility.hotControl = controlID; - current.Use(); - EditorGUIUtility.SetWantsMouseJumping(1); - } - return scrollPosition; - - case EventType.MouseUp: - if (GUIUtility.hotControl == controlID) - { - GUIUtility.hotControl = 0; - } - EditorGUIUtility.SetWantsMouseJumping(0); - return scrollPosition; - - case EventType.MouseMove: - return scrollPosition; - - case EventType.MouseDrag: - if (GUIUtility.hotControl == controlID) - { - scrollPosition -= (Vector2) (((current.delta * (!current.shift ? ((float) 1) : ((float) 3))) / Mathf.Min(position.width, position.height)) * 140f); - scrollPosition.y = Mathf.Clamp(scrollPosition.y, -90f, 90f); - current.Use(); - GUI.changed = true; + case EventType.MouseDown: + if (position.Contains(current.mousePosition) && (position.width > 50f)) + { + GUIUtility.hotControl = controlID; + current.Use(); + EditorGUIUtility.SetWantsMouseJumping(1); + } + return scrollPosition; + + case EventType.MouseUp: + if (GUIUtility.hotControl == controlID) + { + GUIUtility.hotControl = 0; + } + EditorGUIUtility.SetWantsMouseJumping(0); + return scrollPosition; + + case EventType.MouseMove: + return scrollPosition; + + case EventType.MouseDrag: + if (GUIUtility.hotControl == controlID) + { + scrollPosition -= (Vector2) (((current.delta * (!current.shift ? ((float) 1) : ((float) 3))) / Mathf.Min(position.width, position.height)) * 140f); + scrollPosition.y = Mathf.Clamp(scrollPosition.y, -90f, 90f); + current.Use(); + GUI.changed = true; + } + return scrollPosition; } return scrollPosition; } - return scrollPosition; - } - */ + */ public override GUIContent GetPreviewTitle () { return new GUIContent("Preview"); @@ -901,8 +900,8 @@ namespace Spine.Unity.Editor { } } - //TODO: Fix first-import error - //TODO: Update preview without thumbnail + // MITCH: left todo: Fix first-import error + // MITCH: left todo: Update preview without thumbnail public override Texture2D RenderStaticPreview (string assetPath, UnityEngine.Object[] subAssets, int width, int height) { var tex = new Texture2D(width, height, TextureFormat.ARGB32, false); @@ -920,7 +919,7 @@ namespace Spine.Unity.Editor { this.m_previewUtility.BeginStaticPreview(new Rect(0, 0, width, height)); this.DoRenderPreview(false); - //TODO: Figure out why this is throwing errors on first attempt + //MITCH: left todo: Figure out why this is throwing errors on first attempt // if(m_previewUtility != null){ // Handles.SetCamera(this.m_previewUtility.m_Camera); // Handles.BeginGUI(); @@ -930,6 +929,27 @@ namespace Spine.Unity.Editor { tex = this.m_previewUtility.EndStaticPreview(); return tex; } + #endregion + + #region Skin Dropdown Context Menu + void DrawSkinDropdown () { + var menu = new GenericMenu(); + foreach (Skin s in m_skeletonData.Skins) + menu.AddItem(new GUIContent(s.Name), this.m_skeletonAnimation.skeleton.Skin == s, SetSkin, s); + + menu.ShowAsContext(); + } + + void SetSkin (object o) { + Skin skin = (Skin)o; + + m_skeletonAnimation.initialSkinName = skin.Name; + m_skeletonAnimation.Initialize(true); + m_requireRefresh = true; + + EditorPrefs.SetString(m_skeletonDataAssetGUID + "_lastSkin", skin.Name); + } + #endregion } } diff --git a/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs b/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs index af9583d10..d0f08cfae 100644 --- a/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs +++ b/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs @@ -48,6 +48,7 @@ using Spine; namespace Spine.Unity.Editor { + // Analysis disable once ConvertToStaticType [InitializeOnLoad] public class SpineEditorUtilities : AssetPostprocessor { @@ -78,47 +79,42 @@ namespace Spine.Unity.Editor { public static Texture2D unityIcon; public static Texture2D controllerIcon; - public static Mesh boneMesh { + internal static Mesh _boneMesh; + public static Mesh BoneMesh { get { if (_boneMesh == null) { _boneMesh = new Mesh(); - _boneMesh.vertices = new Vector3[4] { - Vector3.zero, + _boneMesh.vertices = new [] { + new Vector3(0, 0, 0), new Vector3(-0.1f, 0.1f, 0), - Vector3.up, + new Vector3(0, 1, 0), new Vector3(0.1f, 0.1f, 0) }; _boneMesh.uv = new Vector2[4]; - _boneMesh.triangles = new int[6] { 0, 1, 2, 2, 3, 0 }; + _boneMesh.triangles = new [] { 0, 1, 2, 2, 3, 0 }; _boneMesh.RecalculateBounds(); _boneMesh.RecalculateNormals(); } - return _boneMesh; } } - internal static Mesh _boneMesh; - - public static Material boneMaterial { + internal static Material _boneMaterial; + public static Material BoneMaterial { get { if (_boneMaterial == null) { #if UNITY_4_3 _boneMaterial = new Material(Shader.Find("Particles/Alpha Blended")); _boneMaterial.SetColor("_TintColor", new Color(0.4f, 0.4f, 0.4f, 0.25f)); #else - _boneMaterial = new Material(Shader.Find("Spine/Bones")); + _boneMaterial = new Material(Shader.Find("Hidden/Spine/Bones")); _boneMaterial.SetColor("_Color", new Color(0.4f, 0.4f, 0.4f, 0.25f)); #endif - } - return _boneMaterial; } } - internal static Material _boneMaterial; - public static void Initialize () { skeleton = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-skeleton.png"); nullBone = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-null.png"); @@ -152,17 +148,19 @@ namespace Spine.Unity.Editor { public static string editorPath = ""; public static string editorGUIPath = ""; + public static bool initialized; + static HashSet assetsImportedInWrongState; static Dictionary skeletonRendererTable; static Dictionary skeletonUtilityBoneTable; static Dictionary boundingBoxFollowerTable; + + const string DEFAULT_MIX_KEY = "SPINE_DEFAULT_MIX"; public static float defaultScale = 0.01f; public static float defaultMix = 0.2f; public static string defaultShader = "Spine/Skeleton"; - public static bool initialized; - - const string DEFAULT_MIX_KEY = "SPINE_DEFAULT_MIX"; + #region Initialization static SpineEditorUtilities () { Initialize(); } @@ -193,8 +191,39 @@ namespace Spine.Unity.Editor { if (!initialized || Icons.skeleton == null) Initialize(); } + #endregion - #region Hierarchy Icon + #region Spine Preferences and Defaults + static bool preferencesLoaded = false; + + [PreferenceItem("Spine")] + static void PreferencesGUI () { + if (!preferencesLoaded) { + preferencesLoaded = true; + defaultMix = EditorPrefs.GetFloat(DEFAULT_MIX_KEY, 0.2f); + } + + + EditorGUILayout.LabelField("Auto-Import Settings", EditorStyles.boldLabel); + EditorGUI.BeginChangeCheck(); + defaultMix = EditorGUILayout.FloatField("Default Mix", defaultMix); + if (EditorGUI.EndChangeCheck()) + EditorPrefs.SetFloat(DEFAULT_MIX_KEY, defaultMix); + + GUILayout.Space(20); + EditorGUILayout.LabelField("3rd Party Settings", EditorStyles.boldLabel); + GUILayout.BeginHorizontal(); + EditorGUILayout.PrefixLabel("TK2D"); + + if (GUILayout.Button("Enable", GUILayout.Width(64))) + EnableTK2D(); + if (GUILayout.Button("Disable", GUILayout.Width(64))) + DisableTK2D(); + GUILayout.EndHorizontal(); + } + #endregion + + #region Hierarchy Icons static void HierarchyWindowChanged () { skeletonRendererTable.Clear(); skeletonUtilityBoneTable.Clear(); @@ -218,48 +247,37 @@ namespace Spine.Unity.Editor { Rect r = new Rect(selectionRect); r.x = r.width - 15; r.width = 15; - GUI.Label(r, Icons.spine); } else if (skeletonUtilityBoneTable.ContainsKey(instanceId)) { Rect r = new Rect(selectionRect); r.x -= 26; - if (skeletonUtilityBoneTable[instanceId] != null) { if (skeletonUtilityBoneTable[instanceId].transform.childCount == 0) r.x += 13; - r.y += 2; - r.width = 13; r.height = 13; - - if (skeletonUtilityBoneTable[instanceId].mode == SkeletonUtilityBone.Mode.Follow) { + if (skeletonUtilityBoneTable[instanceId].mode == SkeletonUtilityBone.Mode.Follow) GUI.DrawTexture(r, Icons.bone); - } else { + else GUI.DrawTexture(r, Icons.poseBones); - } } - } else if (boundingBoxFollowerTable.ContainsKey(instanceId)) { Rect r = new Rect(selectionRect); r.x -= 26; - if (boundingBoxFollowerTable[instanceId] != null) { if (boundingBoxFollowerTable[instanceId].transform.childCount == 0) r.x += 13; - r.y += 2; - r.width = 13; r.height = 13; - GUI.DrawTexture(r, Icons.boundingBox); } } - } #endregion + #region Auto-Import Entry Point static void OnPostprocessAllAssets (string[] imported, string[] deleted, string[] moved, string[] movedFromAssetPaths) { if (imported.Length == 0) return; @@ -298,9 +316,8 @@ namespace Spine.Unity.Editor { string extension = Path.GetExtension(str).ToLower(); switch (extension) { case ".txt": - if (str.EndsWith(".atlas.txt")) { + if (str.EndsWith(".atlas.txt", System.StringComparison.Ordinal)) atlasPaths.Add(str); - } break; case ".png": case ".jpg": @@ -311,28 +328,24 @@ namespace Spine.Unity.Editor { skeletonPaths.Add(str); break; case ".bytes": - if (str.ToLower().EndsWith(".skel.bytes")) { + if (str.ToLower().EndsWith(".skel.bytes", System.StringComparison.Ordinal)) { if (IsValidSpineData((TextAsset)AssetDatabase.LoadAssetAtPath(str, typeof(TextAsset)))) skeletonPaths.Add(str); } break; } } - - - List atlases = new List(); - - //import atlases first + + // Import atlases first. + var atlases = new List(); foreach (string ap in atlasPaths) { - if (!reimport && CheckForValidAtlas(ap)) - continue; - + // MITCH: left note: Always import atlas data now. TextAsset atlasText = (TextAsset)AssetDatabase.LoadAssetAtPath(ap, typeof(TextAsset)); AtlasAsset atlas = IngestSpineAtlas(atlasText); atlases.Add(atlas); } - //import skeletons and match them with atlases + // Import skeletons and match them with atlases. bool abortSkeletonImport = false; foreach (string sp in skeletonPaths) { if (!reimport && CheckForValidSkeletonData(sp)) { @@ -340,19 +353,27 @@ namespace Spine.Unity.Editor { continue; } - string dir = Path.GetDirectoryName(sp); + #if SPINE_TK2D + IngestSpineProject(AssetDatabase.LoadAssetAtPath(sp, typeof(TextAsset)) as TextAsset, null); + #else var localAtlases = FindAtlasesAtPath(dir); var requiredPaths = GetRequiredAtlasRegions(sp); var atlasMatch = GetMatchingAtlas(requiredPaths, localAtlases); - if (atlasMatch != null) { IngestSpineProject(AssetDatabase.LoadAssetAtPath(sp, typeof(TextAsset)) as TextAsset, atlasMatch); } else { bool resolved = false; while (!resolved) { - int result = EditorUtility.DisplayDialogComplex("Skeleton JSON Import Error!", "Could not find matching AtlasAsset for " + Path.GetFileNameWithoutExtension(sp), "Select", "Skip", "Abort"); + + var filename = Path.GetFileNameWithoutExtension(sp); + int result = EditorUtility.DisplayDialogComplex( + string.Format("Missing AtlasAsset for \"{0}\"", filename), + string.Format("Could not find matching AtlasAsset for \"{0}\"", filename), + "Choose AtlaseAssets...", "Skip this", "Stop importing all" + ); + switch (result) { case -1: Debug.Log("Select Atlas"); @@ -366,25 +387,19 @@ namespace Spine.Unity.Editor { IngestSpineProject(AssetDatabase.LoadAssetAtPath(sp, typeof(TextAsset)) as TextAsset, atlasMatch); } } - break; - case 0: + case 0: // Choose AtlaseAssets... var atlasList = MultiAtlasDialog(requiredPaths, Path.GetDirectoryName(sp), Path.GetFileNameWithoutExtension(sp)); - if (atlasList != null) IngestSpineProject(AssetDatabase.LoadAssetAtPath(sp, typeof(TextAsset)) as TextAsset, atlasList.ToArray()); resolved = true; break; - - case 1: + case 1: // Skip Debug.Log("Skipped importing: " + Path.GetFileName(sp)); resolved = true; break; - - - case 2: - //abort + case 2: // Stop importing all abortSkeletonImport = true; resolved = true; break; @@ -394,46 +409,22 @@ namespace Spine.Unity.Editor { if (abortSkeletonImport) break; + #endif } - - //TODO: any post processing of images - } - - static bool CheckForValidSkeletonData (string skeletonJSONPath) { - - string dir = Path.GetDirectoryName(skeletonJSONPath); - TextAsset textAsset = (TextAsset)AssetDatabase.LoadAssetAtPath(skeletonJSONPath, typeof(TextAsset)); - DirectoryInfo dirInfo = new DirectoryInfo(dir); - - FileInfo[] files = dirInfo.GetFiles("*.asset"); - - foreach (var f in files) { - string localPath = dir + "/" + f.Name; - var obj = AssetDatabase.LoadAssetAtPath(localPath, typeof(Object)); - if (obj is SkeletonDataAsset) { - var skeletonDataAsset = (SkeletonDataAsset)obj; - if (skeletonDataAsset.skeletonJSON == textAsset) - return true; - } - } - - return false; + // MITCH: left a todo: any post processing of images } static void ResetExistingSkeletonData (string skeletonJSONPath) { - string dir = Path.GetDirectoryName(skeletonJSONPath); TextAsset textAsset = (TextAsset)AssetDatabase.LoadAssetAtPath(skeletonJSONPath, typeof(TextAsset)); DirectoryInfo dirInfo = new DirectoryInfo(dir); - FileInfo[] files = dirInfo.GetFiles("*.asset"); foreach (var f in files) { string localPath = dir + "/" + f.Name; var obj = AssetDatabase.LoadAssetAtPath(localPath, typeof(Object)); - if (obj is SkeletonDataAsset) { - var skeletonDataAsset = (SkeletonDataAsset)obj; - + var skeletonDataAsset = obj as SkeletonDataAsset; + if (skeletonDataAsset != null) { if (skeletonDataAsset.skeletonJSON == textAsset) { if (Selection.activeObject == skeletonDataAsset) Selection.activeObject = null; @@ -461,10 +452,14 @@ namespace Spine.Unity.Editor { SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(true); string currentHash = skeletonData != null ? skeletonData.Hash : null; - if (currentHash == null || lastHash != currentHash) { - //do any upkeep on synchronized assets + + #if SPINE_SKELETONANIMATOR + if (currentHash == null || lastHash != currentHash) UpdateMecanimClips(skeletonDataAsset); - } + #endif + + // if (currentHash == null || lastHash != currentHash) + // Do any upkeep on synchronized assets if (currentHash != null) { EditorPrefs.SetString(guid + "_hash", currentHash); @@ -473,99 +468,62 @@ namespace Spine.Unity.Editor { } } } + #endregion - static bool CheckForValidAtlas (string atlasPath) { - return false; - //////////////DEPRECATED - always check for new atlas data now - /* - string dir = Path.GetDirectoryName(atlasPath); - TextAsset textAsset = (TextAsset)AssetDatabase.LoadAssetAtPath(atlasPath, typeof(TextAsset)); - DirectoryInfo dirInfo = new DirectoryInfo(dir); - - FileInfo[] files = dirInfo.GetFiles("*.asset"); - - foreach (var f in files) { - string localPath = dir + "/" + f.Name; - var obj = AssetDatabase.LoadAssetAtPath(localPath, typeof(Object)); - if (obj is AtlasAsset) { - var atlasAsset = (AtlasAsset)obj; - if (atlasAsset.atlasFile == textAsset) { - - - Atlas atlas = atlasAsset.GetAtlas(); - FieldInfo field = typeof(Atlas).GetField("regions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic); - List regions = (List)field.GetValue(atlas); - string atlasAssetPath = AssetDatabase.GetAssetPath(atlasAsset); - string atlasAssetDirPath = Path.GetDirectoryName(atlasAssetPath); - string bakedDirPath = Path.Combine(atlasAssetDirPath, atlasAsset.name); - - for (int i = 0; i < regions.Count; i++) { - AtlasRegion region = regions[i]; - string bakedPrefabPath = Path.Combine(bakedDirPath, SpineEditorUtilities.GetPathSafeRegionName(region) + ".prefab").Replace("\\", "/"); - GameObject prefab = (GameObject)AssetDatabase.LoadAssetAtPath(bakedPrefabPath, typeof(GameObject)); - - if (prefab != null) { - Debug.Log("Updating: " + region.name); - BakeRegion(atlasAsset, region); - } - } - - - return true; - } - - } - } - - return false; - - */ - } - - static List MultiAtlasDialog (List requiredPaths, string initialDirectory, string header = "") { - + #region Match SkeletonData with Atlases + static List MultiAtlasDialog (List requiredPaths, string initialDirectory, string filename = "") { List atlasAssets = new List(); - bool resolved = false; string lastAtlasPath = initialDirectory; while (!resolved) { - StringBuilder sb = new StringBuilder(); - sb.AppendLine(header); - sb.AppendLine("Atlases:"); - if (atlasAssets.Count == 0) { - sb.AppendLine("\t--none--"); - } - for (int i = 0; i < atlasAssets.Count; i++) { - sb.AppendLine("\t" + atlasAssets[i].name); - } - sb.AppendLine(); - sb.AppendLine("Missing Regions:"); + // Build dialog box message. + var missingRegions = new List(requiredPaths); + var dialogText = new StringBuilder(); + { + dialogText.AppendLine(string.Format("SkeletonDataAsset for \"{0}\"", filename)); + dialogText.AppendLine("has missing regions."); + dialogText.AppendLine(); + dialogText.AppendLine("Current Atlases:"); - List missingRegions = new List(requiredPaths); + if (atlasAssets.Count == 0) + dialogText.AppendLine("\t--none--"); - foreach (var atlasAsset in atlasAssets) { - var atlas = atlasAsset.GetAtlas(); - for (int i = 0; i < missingRegions.Count; i++) { - if (atlas.FindRegion(missingRegions[i]) != null) { - missingRegions.RemoveAt(i); - i--; + for (int i = 0; i < atlasAssets.Count; i++) + dialogText.AppendLine("\t" + atlasAssets[i].name); + + dialogText.AppendLine(); + dialogText.AppendLine("Missing Regions:"); + + foreach (var atlasAsset in atlasAssets) { + var atlas = atlasAsset.GetAtlas(); + for (int i = 0; i < missingRegions.Count; i++) { + if (atlas.FindRegion(missingRegions[i]) != null) { + missingRegions.RemoveAt(i); + i--; + } } } + + int n = missingRegions.Count; + if (n == 0) break; + + const int MaxListLength = 15; + for (int i = 0; (i < n && i < MaxListLength); i++) + dialogText.AppendLine("\t" + missingRegions[i]); + + if (n > MaxListLength) dialogText.AppendLine(string.Format("\t... {0} more...", n - MaxListLength)); } - if (missingRegions.Count == 0) { - break; - } - - for (int i = 0; i < missingRegions.Count; i++) { - sb.AppendLine("\t" + missingRegions[i]); - } - - int result = EditorUtility.DisplayDialogComplex("Atlas Selection", sb.ToString(), "Select", "Finish", "Abort"); + // Show dialog box. + int result = EditorUtility.DisplayDialogComplex( + "SkeletonDataAsset has missing Atlas.", + dialogText.ToString(), + "Browse...", "Import anyway", "Cancel" + ); switch (result) { - case 0: + case 0: // Browse... AtlasAsset selectedAtlasAsset = GetAtlasDialog(lastAtlasPath); if (selectedAtlasAsset != null) { var atlas = selectedAtlasAsset.GetAtlas(); @@ -576,32 +534,26 @@ namespace Spine.Unity.Editor { break; } } - atlasAssets.Add(selectedAtlasAsset); } break; - - case 1: + case 1: // Import anyway resolved = true; break; - - case 2: + case 2: // Cancel atlasAssets = null; resolved = true; break; } - - } - return atlasAssets; } static AtlasAsset GetAtlasDialog (string dirPath) { string path = EditorUtility.OpenFilePanel("Select AtlasAsset...", dirPath, "asset"); - if (path == "") - return null; + + if (path == "") return null; // Canceled or closed by user. int subLen = Application.dataPath.Length - 6; string assetRelativePath = path.Substring(subLen, path.Length - subLen).Replace("\\", "/"); @@ -641,10 +593,8 @@ namespace Spine.Unity.Editor { foreach (KeyValuePair attachmentEntry in ((Dictionary)slotEntry.Value)) { var data = ((Dictionary)attachmentEntry.Value); if (data.ContainsKey("type")) { - if ((string)data["type"] == "boundingbox") { + if ((string)data["type"] == "boundingbox") continue; - } - } if (data.ContainsKey("path")) requiredPaths.Add((string)data["path"]); @@ -652,13 +602,13 @@ namespace Spine.Unity.Editor { requiredPaths.Add((string)data["name"]); else requiredPaths.Add(attachmentEntry.Key); - //requiredPaths.Add((string)sdf["path"]); } } } return requiredPaths; } + static AtlasAsset GetMatchingAtlas (List requiredPaths, List atlasAssets) { AtlasAsset atlasAssetMatch = null; @@ -676,65 +626,55 @@ namespace Spine.Unity.Editor { atlasAssetMatch = a; break; } - } return atlasAssetMatch; } + public class AtlasRequirementLoader : AttachmentLoader { + List requirementList; + public AtlasRequirementLoader (List requirementList) { + this.requirementList = requirementList; + } + + public RegionAttachment NewRegionAttachment (Skin skin, string name, string path) { + requirementList.Add(path); + return new RegionAttachment(name); + } + + public MeshAttachment NewMeshAttachment (Skin skin, string name, string path) { + requirementList.Add(path); + return new MeshAttachment(name); + } + + public WeightedMeshAttachment NewWeightedMeshAttachment(Skin skin, string name, string path) { + requirementList.Add(path); + return new WeightedMeshAttachment(name); + } + + public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name) { + return new BoundingBoxAttachment(name); + } + } + #endregion + + #region Import Atlases static List FindAtlasesAtPath (string path) { List arr = new List(); - DirectoryInfo dir = new DirectoryInfo(path); FileInfo[] assetInfoArr = dir.GetFiles("*.asset"); int subLen = Application.dataPath.Length - 6; - foreach (var f in assetInfoArr) { string assetRelativePath = f.FullName.Substring(subLen, f.FullName.Length - subLen).Replace("\\", "/"); - Object obj = AssetDatabase.LoadAssetAtPath(assetRelativePath, typeof(AtlasAsset)); - if (obj != null) { + if (obj != null) arr.Add(obj as AtlasAsset); - } - } - return arr; } - public static bool IsValidSpineData (TextAsset asset) { - if (asset.name.Contains(".skel")) return true; - - object obj = null; - try { - obj = Json.Deserialize(new StringReader(asset.text)); - } catch (System.Exception) { - - } - - if (obj == null) { - Debug.LogError("Is not valid JSON"); - return false; - } - - var root = obj as Dictionary; - if (root == null) { - Debug.LogError("Parser returned an incorrect type."); - return false; - } - - if (!root.ContainsKey("skeleton")) - return false; - -// var skeletonInfo = (Dictionary)root["skeleton"]; -// string spineVersion = (string)skeletonInfo["spine"]; - // TODO: Warn users of old version incompatibility. - - return true; - } - static AtlasAsset IngestSpineAtlas (TextAsset atlasText) { if (atlasText == null) { Debug.LogWarning("Atlas source cannot be null!"); @@ -805,7 +745,6 @@ namespace Spine.Unity.Editor { mat.mainTexture = texture; EditorUtility.SetDirty(mat); - AssetDatabase.SaveAssets(); atlasAsset.materials[i] = mat; @@ -820,11 +759,9 @@ namespace Spine.Unity.Editor { atlasAsset.Reset(); EditorUtility.SetDirty(atlasAsset); - AssetDatabase.SaveAssets(); - - //iterate regions and bake marked + // Iterate regions and bake marked. Atlas atlas = atlasAsset.GetAtlas(); FieldInfo field = typeof(Atlas).GetField("regions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic); List regions = (List)field.GetValue(atlas); @@ -837,7 +774,6 @@ namespace Spine.Unity.Editor { AtlasRegion region = regions[i]; string bakedPrefabPath = Path.Combine(bakedDirPath, SpineEditorUtilities.GetPathSafeRegionName(region) + ".prefab").Replace("\\", "/"); GameObject prefab = (GameObject)AssetDatabase.LoadAssetAtPath(bakedPrefabPath, typeof(GameObject)); - if (prefab != null) { BakeRegion(atlasAsset, region, false); hasBakedRegions = true; @@ -851,7 +787,9 @@ namespace Spine.Unity.Editor { return (AtlasAsset)AssetDatabase.LoadAssetAtPath(atlasPath, typeof(AtlasAsset)); } + #endregion + #region Bake Atlas Region public static GameObject BakeRegion (AtlasAsset atlasAsset, AtlasRegion region, bool autoSave = true) { Atlas atlas = atlasAsset.GetAtlas(); string atlasAssetPath = AssetDatabase.GetAssetPath(atlasAsset); @@ -869,7 +807,7 @@ namespace Spine.Unity.Editor { if (prefab == null) { root = new GameObject("temp", typeof(MeshFilter), typeof(MeshRenderer)); - prefab = (GameObject)PrefabUtility.CreatePrefab(bakedPrefabPath, root); + prefab = PrefabUtility.CreatePrefab(bakedPrefabPath, root); isNewPrefab = true; Object.DestroyImmediate(root); } @@ -891,23 +829,45 @@ namespace Spine.Unity.Editor { AssetDatabase.Refresh(); } - prefab.GetComponent().sharedMaterial = mat; return prefab; } + #endregion - public static string GetPathSafeRegionName (AtlasRegion region) { - return region.name.Replace("/", "_"); - } - + #region Import SkeletonData (json or binary) static SkeletonDataAsset IngestSpineProject (TextAsset spineJson, params AtlasAsset[] atlasAssets) { string primaryName = Path.GetFileNameWithoutExtension(spineJson.name); string assetPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(spineJson)); string filePath = assetPath + "/" + primaryName + "_SkeletonData.asset"; - if (spineJson != null && atlasAssets != null) { + #if SPINE_TK2D + if (spineJson != null) { + SkeletonDataAsset skeletonDataAsset = (SkeletonDataAsset)AssetDatabase.LoadAssetAtPath(filePath, typeof(SkeletonDataAsset)); + if (skeletonDataAsset == null) { + skeletonDataAsset = SkeletonDataAsset.CreateInstance(); + skeletonDataAsset.skeletonJSON = spineJson; + skeletonDataAsset.fromAnimation = new string[0]; + skeletonDataAsset.toAnimation = new string[0]; + skeletonDataAsset.duration = new float[0]; + skeletonDataAsset.defaultMix = defaultMix; + skeletonDataAsset.scale = defaultScale; + AssetDatabase.CreateAsset(skeletonDataAsset, filePath); + AssetDatabase.SaveAssets(); + } else { + skeletonDataAsset.Reset(); + skeletonDataAsset.GetSkeletonData(true); + } + + return skeletonDataAsset; + } else { + EditorUtility.DisplayDialog("Error!", "Tried to ingest null Spine data.", "OK"); + return null; + } + + #else + if (spineJson != null && atlasAssets != null) { SkeletonDataAsset skelDataAsset = (SkeletonDataAsset)AssetDatabase.LoadAssetAtPath(filePath, typeof(SkeletonDataAsset)); if (skelDataAsset == null) { skelDataAsset = SkeletonDataAsset.CreateInstance(); @@ -932,7 +892,52 @@ namespace Spine.Unity.Editor { EditorUtility.DisplayDialog("Error!", "Must specify both Spine JSON and AtlasAsset array", "OK"); return null; } + #endif } + #endregion + + #region Checking Methods + static bool CheckForValidSkeletonData (string skeletonJSONPath) { + string dir = Path.GetDirectoryName(skeletonJSONPath); + TextAsset textAsset = (TextAsset)AssetDatabase.LoadAssetAtPath(skeletonJSONPath, typeof(TextAsset)); + DirectoryInfo dirInfo = new DirectoryInfo(dir); + FileInfo[] files = dirInfo.GetFiles("*.asset"); + + foreach (var path in files) { + string localPath = dir + "/" + path.Name; + var obj = AssetDatabase.LoadAssetAtPath(localPath, typeof(Object)); + var skeletonDataAsset = obj as SkeletonDataAsset; + if (skeletonDataAsset != null && skeletonDataAsset.skeletonJSON == textAsset) + return true; + } + + return false; + } + + public static bool IsValidSpineData (TextAsset asset) { + if (asset.name.Contains(".skel")) return true; + + object obj = null; + obj = Json.Deserialize(new StringReader(asset.text)); + + if (obj == null) { + Debug.LogError("Is not valid JSON."); + return false; + } + + var root = obj as Dictionary; + if (root == null) { + Debug.LogError("Parser returned an incorrect type."); + return false; + } + + return root.ContainsKey("skeleton"); + + // TODO: Warn users of old version incompatibility. + // var skeletonInfo = (Dictionary)root["skeleton"]; + // string spineVersion = (string)skeletonInfo["spine"]; + } + #endregion #region SkeletonAnimation Menu [MenuItem("Assets/Spine/Instantiate (SkeletonAnimation)", false, 10)] @@ -963,18 +968,20 @@ namespace Spine.Unity.Editor { } public static SkeletonAnimation InstantiateSkeletonAnimation (SkeletonDataAsset skeletonDataAsset, string skinName) { - return InstantiateSkeletonAnimation(skeletonDataAsset, skeletonDataAsset.GetSkeletonData(true).FindSkin(skinName)); + var skeletonData = skeletonDataAsset.GetSkeletonData(true); + var skin = skeletonData != null ? skeletonData.FindSkin(skinName) : null; + return InstantiateSkeletonAnimation(skeletonDataAsset, skin); } public static SkeletonAnimation InstantiateSkeletonAnimation (SkeletonDataAsset skeletonDataAsset, Skin skin = null) { string spineGameObjectName = string.Format("Spine GameObject ({0})", skeletonDataAsset.name.Replace("_SkeletonData", "")); GameObject go = new GameObject(spineGameObjectName, typeof(MeshFilter), typeof(MeshRenderer), typeof(SkeletonAnimation)); - SkeletonAnimation anim = go.GetComponent(); - anim.skeletonDataAsset = skeletonDataAsset; + SkeletonAnimation newSkeletonAnimation = go.GetComponent(); + newSkeletonAnimation.skeletonDataAsset = skeletonDataAsset; bool requiresNormals = false; - foreach (AtlasAsset atlasAsset in anim.skeletonDataAsset.atlasAssets) { + foreach (AtlasAsset atlasAsset in newSkeletonAnimation.skeletonDataAsset.atlasAssets) { foreach (Material m in atlasAsset.materials) { if (m.shader.name.Contains("Lit")) { requiresNormals = true; @@ -982,10 +989,8 @@ namespace Spine.Unity.Editor { } } } - - - - anim.calculateNormals = requiresNormals; + + newSkeletonAnimation.calculateNormals = requiresNormals; SkeletonData data = skeletonDataAsset.GetSkeletonData(true); @@ -994,8 +999,12 @@ namespace Spine.Unity.Editor { string reloadAtlasPath = AssetDatabase.GetAssetPath(skeletonDataAsset.atlasAssets[i]); skeletonDataAsset.atlasAssets[i] = (AtlasAsset)AssetDatabase.LoadAssetAtPath(reloadAtlasPath, typeof(AtlasAsset)); } + data = skeletonDataAsset.GetSkeletonData(false); + } - data = skeletonDataAsset.GetSkeletonData(true); + if (data == null) { + Debug.LogWarning("Tried to instantiate a skeleton from an invalid SkeletonDataAsset."); + return null; } if (skin == null) @@ -1004,17 +1013,17 @@ namespace Spine.Unity.Editor { if (skin == null) skin = data.Skins.Items[0]; - anim.Initialize(false); + newSkeletonAnimation.Initialize(false); - anim.skeleton.SetSkin(skin); - anim.initialSkinName = skin.Name; + newSkeletonAnimation.skeleton.SetSkin(skin); + newSkeletonAnimation.initialSkinName = skin.Name; - anim.skeleton.Update(1); - anim.state.Update(1); - anim.state.Apply(anim.skeleton); - anim.skeleton.UpdateWorldTransform(); + newSkeletonAnimation.skeleton.Update(1); + newSkeletonAnimation.state.Update(1); + newSkeletonAnimation.state.Apply(newSkeletonAnimation.skeleton); + newSkeletonAnimation.skeleton.UpdateWorldTransform(); - return anim; + return newSkeletonAnimation; } #endregion @@ -1115,37 +1124,7 @@ namespace Spine.Unity.Editor { #endif #endregion - #region Spine Preferences - static bool preferencesLoaded = false; - - [PreferenceItem("Spine")] - static void PreferencesGUI () { - if (!preferencesLoaded) { - preferencesLoaded = true; - defaultMix = EditorPrefs.GetFloat(DEFAULT_MIX_KEY, 0.2f); - } - - - EditorGUILayout.LabelField("Auto-Import Settings", EditorStyles.boldLabel); - EditorGUI.BeginChangeCheck(); - defaultMix = EditorGUILayout.FloatField("Default Mix", defaultMix); - if (EditorGUI.EndChangeCheck()) - EditorPrefs.SetFloat(DEFAULT_MIX_KEY, defaultMix); - - GUILayout.Space(20); - EditorGUILayout.LabelField("3rd Party Settings", EditorStyles.boldLabel); - GUILayout.BeginHorizontal(); - EditorGUILayout.PrefixLabel("TK2D"); - - if (GUILayout.Button("Enable", GUILayout.Width(64))) - EnableTK2D(); - if (GUILayout.Button("Disable", GUILayout.Width(64))) - DisableTK2D(); - GUILayout.EndHorizontal(); - } - #endregion - - //TK2D Support + #region TK2D Support const string SPINE_TK2D_DEFINE = "SPINE_TK2D"; static void EnableTK2D () { @@ -1195,32 +1174,10 @@ namespace Spine.Unity.Editor { Debug.LogWarning("Already Removed Scripting Define Symbol " + SPINE_TK2D_DEFINE); } } + #endregion - public class AtlasRequirementLoader : AttachmentLoader { - - List requirementList; - public AtlasRequirementLoader (List requirementList) { - this.requirementList = requirementList; - } - - public RegionAttachment NewRegionAttachment (Skin skin, string name, string path) { - requirementList.Add(path); - return new RegionAttachment(name); - } - - public MeshAttachment NewMeshAttachment (Skin skin, string name, string path) { - requirementList.Add(path); - return new MeshAttachment(name); - } - - public WeightedMeshAttachment NewWeightedMeshAttachment(Skin skin, string name, string path) { - requirementList.Add(path); - return new WeightedMeshAttachment(name); - } - - public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name) { - return new BoundingBoxAttachment(name); - } + public static string GetPathSafeRegionName (AtlasRegion region) { + return region.name.Replace("/", "_"); } } diff --git a/spine-unity/Assets/spine-unity/Modules/Ghost/Shaders/SkeletonGhost.shader b/spine-unity/Assets/spine-unity/Modules/Ghost/Shaders/SkeletonGhost.shader index 1494a89d6..bda3f4684 100644 --- a/spine-unity/Assets/spine-unity/Modules/Ghost/Shaders/SkeletonGhost.shader +++ b/spine-unity/Assets/spine-unity/Modules/Ghost/Shaders/SkeletonGhost.shader @@ -1,5 +1,5 @@ //Shader written by Alex Dixon -Shader "Spine/SkeletonGhost" +Shader "Spine/Special/SkeletonGhost" { Properties { @@ -32,14 +32,12 @@ Shader "Spine/SkeletonGhost" float4 vertex : POSITION; float2 texcoord : TEXCOORD0; float4 color : COLOR; - }; struct vertex_output { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; - float4 color : COLOR; }; diff --git a/spine-unity/Assets/spine-unity/Modules/Ghost/SkeletonGhost.cs b/spine-unity/Assets/spine-unity/Modules/Ghost/SkeletonGhost.cs index 3de748487..0244fcfa4 100644 --- a/spine-unity/Assets/spine-unity/Modules/Ghost/SkeletonGhost.cs +++ b/spine-unity/Assets/spine-unity/Modules/Ghost/SkeletonGhost.cs @@ -38,7 +38,7 @@ namespace Spine.Unity.Modules { void Start () { if (ghostShader == null) - ghostShader = Shader.Find("Spine/SkeletonGhost"); + ghostShader = Shader.Find("Spine/Special/SkeletonGhost"); skeletonRenderer = GetComponent(); meshFilter = GetComponent(); diff --git a/spine-unity/Assets/spine-unity/Shaders/Bones.shader b/spine-unity/Assets/spine-unity/Shaders/Bones.shader index 61fc977e5..055d9d329 100644 --- a/spine-unity/Assets/spine-unity/Shaders/Bones.shader +++ b/spine-unity/Assets/spine-unity/Shaders/Bones.shader @@ -1,4 +1,4 @@ -Shader "Spine/Bones" { +Shader "Hidden/Spine/Bones" { Properties { _Color ("Color", Color) = (0.5,0.5,0.5,0.5) _MainTex ("Particle Texture", 2D) = "white" {} @@ -56,7 +56,6 @@ Category { } sampler2D_float _CameraDepthTexture; - fixed4 frag (v2f i) : SV_Target { diff --git a/spine-unity/Assets/spine-unity/Shaders/HiddenPass.shader b/spine-unity/Assets/spine-unity/Shaders/HiddenPass.shader index 3a0de6756..c06650af2 100644 --- a/spine-unity/Assets/spine-unity/Shaders/HiddenPass.shader +++ b/spine-unity/Assets/spine-unity/Shaders/HiddenPass.shader @@ -1,21 +1,12 @@ -Shader "Spine/HiddenPass" { +Shader "Spine/Special/HiddenPass" { SubShader - { - Tags {"Queue" = "Geometry-1" } - Lighting Off - Pass - { - ZWrite Off - ColorMask 0 - } - } } diff --git a/spine-unity/Assets/spine-unity/SkeletonRenderer.cs b/spine-unity/Assets/spine-unity/SkeletonRenderer.cs index 9cf65b806..341713ac5 100644 --- a/spine-unity/Assets/spine-unity/SkeletonRenderer.cs +++ b/spine-unity/Assets/spine-unity/SkeletonRenderer.cs @@ -257,7 +257,9 @@ namespace Spine.Unity { var workingSubmeshInstructions = workingInstruction.submeshInstructions; // Items array should not be cached. There is dynamic writing to this list. workingSubmeshInstructions.Clear(false); + #if !SPINE_TK2D bool isCustomSlotMaterialsPopulated = customSlotMaterials.Count > 0; + #endif int vertexCount = 0; int submeshVertexCount = 0; diff --git a/spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs b/spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs index f61f22b25..fff53889b 100644 --- a/spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs +++ b/spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs @@ -114,8 +114,8 @@ namespace Spine.Unity.Editor { Vector3 forward = transform.TransformDirection(rot * Vector3.right); forward *= flipRotation; - SpineEditorUtilities.Icons.boneMaterial.SetPass(0); - Graphics.DrawMeshNow(SpineEditorUtilities.Icons.boneMesh, Matrix4x4.TRS(vec, Quaternion.LookRotation(transform.forward, forward), Vector3.one * b.Data.Length * b.WorldScaleX)); + SpineEditorUtilities.Icons.BoneMaterial.SetPass(0); + Graphics.DrawMeshNow(SpineEditorUtilities.Icons.BoneMesh, Matrix4x4.TRS(vec, Quaternion.LookRotation(transform.forward, forward), Vector3.one * b.Data.Length * b.WorldScaleX)); } }