/****************************************************************************** * 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. *****************************************************************************/ #define NO_PREFAB_MESH using UnityEditor; using System.Collections.Generic; using UnityEngine; namespace Spine.Unity.Editor { using Event = UnityEngine.Event; [CustomEditor(typeof(SkeletonRenderer))] [CanEditMultipleObjects] public class SkeletonRendererInspector : UnityEditor.Editor { protected static bool advancedFoldout; protected static bool showBoneNames, showPaths, showShapes, showConstraints = true; protected SerializedProperty skeletonDataAsset, initialSkinName, normals, tangents, meshes, immutableTriangles, separatorSlotNames, frontFacing, zSpacing, pmaVertexColors, clearStateOnDisable; protected SpineInspectorUtility.SerializedSortingProperties sortingProperties; protected bool isInspectingPrefab; protected GUIContent SkeletonDataAssetLabel, SkeletonUtilityButtonContent; protected GUIContent PMAVertexColorsLabel, ClearStateOnDisableLabel, ZSpacingLabel, MeshesLabel, ImmubleTrianglesLabel; protected GUIContent NormalsLabel, TangentsLabel; const string ReloadButtonLabel = "Reload"; protected bool TargetIsValid { get { if (serializedObject.isEditingMultipleObjects) { foreach (var o in targets) { var component = (SkeletonRenderer)o; if (!component.valid) return false; } return true; } else { var component = (SkeletonRenderer)target; return component.valid; } } } protected virtual void OnEnable () { isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab); SpineEditorUtilities.ConfirmInitialization(); // Labels SkeletonDataAssetLabel = new GUIContent("SkeletonData Asset", SpineEditorUtilities.Icons.spine); SkeletonUtilityButtonContent = new GUIContent("Add Skeleton Utility", SpineEditorUtilities.Icons.skeletonUtility); MeshesLabel = new GUIContent("Render MeshAttachments", "Disable to optimize rendering for skeletons that don't use Mesh Attachments"); ImmubleTrianglesLabel = new GUIContent("Immutable Triangles", "Enable to optimize rendering for skeletons that never change attachment visbility"); PMAVertexColorsLabel = new GUIContent("PMA Vertex Colors", "Use this if you are using the default Spine/Skeleton shader or any premultiply-alpha shader."); ClearStateOnDisableLabel = new GUIContent("Clear State On Disable", "Use this if you are pooling or enabling/disabling your Spine GameObject."); ZSpacingLabel = new GUIContent("Z Spacing", "A value other than 0 adds a space between each rendered attachment to prevent Z Fighting when using shaders that read or write to the depth buffer. Large values may cause unwanted parallax and spaces depending on camera setup."); NormalsLabel = new GUIContent("Add Normals", "Use this if your shader requires vertex normals. A more efficient solution for 2D setups is to modify the shader to assume a single normal value for the whole mesh."); TangentsLabel = new GUIContent("Solve Tangents", "Calculates the tangents per frame. Use this if you are using lit shaders (usually with normal maps) that require vertex tangents."); var so = this.serializedObject; skeletonDataAsset = so.FindProperty("skeletonDataAsset"); initialSkinName = so.FindProperty("initialSkinName"); normals = so.FindProperty("calculateNormals"); tangents = so.FindProperty("calculateTangents"); meshes = so.FindProperty("renderMeshes"); immutableTriangles = so.FindProperty("immutableTriangles"); pmaVertexColors = so.FindProperty("pmaVertexColors"); clearStateOnDisable = so.FindProperty("clearStateOnDisable"); separatorSlotNames = so.FindProperty("separatorSlotNames"); separatorSlotNames.isExpanded = true; frontFacing = so.FindProperty("frontFacing"); zSpacing = so.FindProperty("zSpacing"); SerializedObject rso = SpineInspectorUtility.GetRenderersSerializedObject(serializedObject); sortingProperties = new SpineInspectorUtility.SerializedSortingProperties(rso); } public static void ReapplySeparatorSlotNames (SkeletonRenderer skeletonRenderer) { if (!skeletonRenderer.valid) return; var separatorSlots = skeletonRenderer.separatorSlots; var separatorSlotNames = skeletonRenderer.separatorSlotNames; var skeleton = skeletonRenderer.skeleton; separatorSlots.Clear(); for (int i = 0, n = separatorSlotNames.Length; i < n; i++) { var slot = skeleton.FindSlot(separatorSlotNames[i]); if (slot != null) { separatorSlots.Add(slot); } else { Debug.LogWarning(separatorSlotNames[i] + " is not a slot in " + skeletonRenderer.skeletonDataAsset.skeletonJSON.name); } } } protected virtual void DrawInspectorGUI (bool multi) { bool valid = TargetIsValid; var reloadWidth = GUILayout.Width(GUI.skin.label.CalcSize(new GUIContent(ReloadButtonLabel)).x + 20); var reloadButtonStyle = EditorStyles.miniButtonRight; if (multi) { using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox)) { SpineInspectorUtility.PropertyFieldFitLabel(skeletonDataAsset, SkeletonDataAssetLabel); if (GUILayout.Button(ReloadButtonLabel, reloadButtonStyle, reloadWidth)) { foreach (var c in targets) { var component = c as SkeletonRenderer; if (component.skeletonDataAsset != null) { foreach (AtlasAsset aa in component.skeletonDataAsset.atlasAssets) { if (aa != null) aa.Clear(); } component.skeletonDataAsset.Clear(); } component.Initialize(true); } } } foreach (var c in targets) { var component = c as SkeletonRenderer; if (!component.valid) { if (Event.current.type == EventType.Layout) { component.Initialize(true); component.LateUpdate(); } if (!component.valid) continue; } #if NO_PREFAB_MESH if (isInspectingPrefab) { MeshFilter meshFilter = component.GetComponent(); if (meshFilter != null) meshFilter.sharedMesh = null; } #endif } if (valid) EditorGUILayout.PropertyField(initialSkinName); } else { var component = (SkeletonRenderer)target; if (!component.valid && Event.current.type == EventType.Layout) { component.Initialize(true); component.LateUpdate(); } using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox)) { SpineInspectorUtility.PropertyFieldFitLabel(skeletonDataAsset, SkeletonDataAssetLabel); if (component.valid) { if (GUILayout.Button(ReloadButtonLabel, reloadButtonStyle, reloadWidth)) { if (component.skeletonDataAsset != null) { foreach (AtlasAsset aa in component.skeletonDataAsset.atlasAssets) { if (aa != null) aa.Clear(); } component.skeletonDataAsset.Clear(); } component.Initialize(true); } } } if (component.skeletonDataAsset == null) { EditorGUILayout.HelpBox("Skeleton Data Asset required", MessageType.Warning); return; } #if NO_PREFAB_MESH if (isInspectingPrefab) { MeshFilter meshFilter = component.GetComponent(); if (meshFilter != null) meshFilter.sharedMesh = null; } #endif // Initial skin name. if (component.valid) { string[] skins = new string[component.skeleton.Data.Skins.Count]; int skinIndex = 0; for (int i = 0; i < skins.Length; i++) { string skinNameString = component.skeleton.Data.Skins.Items[i].Name; skins[i] = skinNameString; if (skinNameString == initialSkinName.stringValue) skinIndex = i; } skinIndex = EditorGUILayout.Popup("Initial Skin", skinIndex, skins); initialSkinName.stringValue = skins[skinIndex]; } } EditorGUILayout.Space(); // Sorting Layers SpineInspectorUtility.SortingPropertyFields(sortingProperties, applyModifiedProperties: true); if (!TargetIsValid) return; // More Render Options... using (new SpineInspectorUtility.BoxScope()) { EditorGUI.BeginChangeCheck(); if (advancedFoldout = EditorGUILayout.Foldout(advancedFoldout, "Advanced")) { using (new SpineInspectorUtility.IndentScope()) { using (new SpineInspectorUtility.LabelWidthScope()) { // Optimization options EditorGUILayout.PropertyField(meshes, MeshesLabel); EditorGUILayout.PropertyField(immutableTriangles, ImmubleTrianglesLabel); EditorGUILayout.PropertyField(clearStateOnDisable, ClearStateOnDisableLabel); EditorGUILayout.Space(); } SeparatorsField(separatorSlotNames); EditorGUILayout.Space(); // Render options const float MinZSpacing = -0.1f; const float MaxZSpacing = 0f; EditorGUILayout.Slider(zSpacing, MinZSpacing, MaxZSpacing, ZSpacingLabel); EditorGUILayout.Space(); using (new SpineInspectorUtility.LabelWidthScope()) { EditorGUILayout.LabelField("Vertex Data", EditorStyles.boldLabel); EditorGUILayout.PropertyField(pmaVertexColors, PMAVertexColorsLabel); // Optional fields. May be disabled in SkeletonRenderer. if (normals != null) EditorGUILayout.PropertyField(normals, NormalsLabel); if (tangents != null) EditorGUILayout.PropertyField(tangents, TangentsLabel); if (frontFacing != null) EditorGUILayout.PropertyField(frontFacing); EditorGUILayout.Space(); EditorGUILayout.LabelField("Editor Preview", EditorStyles.boldLabel); showBoneNames = EditorGUILayout.Toggle("Show Bone Names", showBoneNames); showPaths = EditorGUILayout.Toggle("Show Paths", showPaths); showShapes = EditorGUILayout.Toggle("Show Shapes", showShapes); showConstraints = EditorGUILayout.Toggle("Show Constraints", showConstraints); } EditorGUILayout.Space(); } } if (EditorGUI.EndChangeCheck()) SceneView.RepaintAll(); } } public static void SeparatorsField (SerializedProperty separatorSlotNames) { bool multi = separatorSlotNames.serializedObject.isEditingMultipleObjects; bool hasTerminalSlot = false; if (!multi) { var sr = separatorSlotNames.serializedObject.targetObject as ISkeletonComponent; var skeleton = sr.Skeleton; int lastSlot = skeleton.Slots.Count - 1; if (skeleton != null) { for (int i = 0, n = separatorSlotNames.arraySize; i < n; i++) { int index = skeleton.FindSlotIndex(separatorSlotNames.GetArrayElementAtIndex(i).stringValue); if (index == 0 || index == lastSlot) { hasTerminalSlot = true; break; } } } } string terminalSlotWarning = hasTerminalSlot ? " (!)" : ""; using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) { const string SeparatorsDescription = "Stored names of slots where the Skeleton's render will be split into different batches. This is used by separate components that split the render into different MeshRenderers or GameObjects."; if (separatorSlotNames.isExpanded) { EditorGUILayout.PropertyField(separatorSlotNames, new GUIContent(separatorSlotNames.displayName + terminalSlotWarning, SeparatorsDescription), true); EditorGUILayout.Space(); } else EditorGUILayout.PropertyField(separatorSlotNames, new GUIContent(separatorSlotNames.displayName + string.Format("{0} [{1}]", terminalSlotWarning, separatorSlotNames.arraySize), SeparatorsDescription), true); } } public void OnSceneGUI () { var skeletonRenderer = (SkeletonRenderer)target; var skeleton = skeletonRenderer.skeleton; var transform = skeletonRenderer.transform; if (skeleton == null) return; if (showPaths) SpineHandles.DrawPaths(transform, skeleton); SpineHandles.DrawBones(transform, skeleton); if (showConstraints) SpineHandles.DrawConstraints(transform, skeleton); if (showBoneNames) SpineHandles.DrawBoneNames(transform, skeleton); if (showShapes) SpineHandles.DrawBoundingBoxes(transform, skeleton); } public void DrawSkeletonUtilityButton (bool multi) { if (multi) { // Support multi-edit SkeletonUtility button. // EditorGUILayout.Space(); // bool addSkeletonUtility = GUILayout.Button(buttonContent, GUILayout.Height(30)); // foreach (var t in targets) { // var component = t as Component; // if (addSkeletonUtility && component.GetComponent() == null) // component.gameObject.AddComponent(); // } } else { EditorGUILayout.Space(); var component = (Component)target; if (component.GetComponent() == null) { if (SpineInspectorUtility.LargeCenteredButton(SkeletonUtilityButtonContent)) component.gameObject.AddComponent(); } } } override public void OnInspectorGUI () { //serializedObject.Update(); bool multi = serializedObject.isEditingMultipleObjects; DrawInspectorGUI(multi); if (serializedObject.ApplyModifiedProperties() || SpineInspectorUtility.UndoRedoPerformed(Event.current)) { if (!Application.isPlaying) { if (multi) foreach (var o in targets) ((SkeletonRenderer)o).Initialize(true); else ((SkeletonRenderer)target).Initialize(true); } } } } }