A game about forced loneliness, made by TACStudios
at master 383 lines 18 kB view raw
1using System.Collections.Generic; 2using System.Text; 3using System.Linq; 4using UnityEngine; 5using UnityEngine.UI; 6using UnityEditor.AnimatedValues; 7 8namespace UnityEditor.UI 9{ 10 [CustomEditor(typeof(Selectable), true)] 11 /// <summary> 12 /// Custom Editor for the Selectable Component. 13 /// Extend this class to write a custom editor for a component derived from Selectable. 14 /// </summary> 15 public class SelectableEditor : Editor 16 { 17 SerializedProperty m_Script; 18 SerializedProperty m_InteractableProperty; 19 SerializedProperty m_TargetGraphicProperty; 20 SerializedProperty m_TransitionProperty; 21 SerializedProperty m_ColorBlockProperty; 22 SerializedProperty m_SpriteStateProperty; 23 SerializedProperty m_AnimTriggerProperty; 24 SerializedProperty m_NavigationProperty; 25 26 GUIContent m_VisualizeNavigation = EditorGUIUtility.TrTextContent("Visualize", "Show navigation flows between selectable UI elements."); 27 28 AnimBool m_ShowColorTint = new AnimBool(); 29 AnimBool m_ShowSpriteTrasition = new AnimBool(); 30 AnimBool m_ShowAnimTransition = new AnimBool(); 31 32 private static List<SelectableEditor> s_Editors = new List<SelectableEditor>(); 33 private static bool s_ShowNavigation = false; 34 private static string s_ShowNavigationKey = "SelectableEditor.ShowNavigation"; 35 36 // Whenever adding new SerializedProperties to the Selectable and SelectableEditor 37 // Also update this guy in OnEnable. This makes the inherited classes from Selectable not require a CustomEditor. 38 private string[] m_PropertyPathToExcludeForChildClasses; 39 40 protected virtual void OnEnable() 41 { 42 m_Script = serializedObject.FindProperty("m_Script"); 43 m_InteractableProperty = serializedObject.FindProperty("m_Interactable"); 44 m_TargetGraphicProperty = serializedObject.FindProperty("m_TargetGraphic"); 45 m_TransitionProperty = serializedObject.FindProperty("m_Transition"); 46 m_ColorBlockProperty = serializedObject.FindProperty("m_Colors"); 47 m_SpriteStateProperty = serializedObject.FindProperty("m_SpriteState"); 48 m_AnimTriggerProperty = serializedObject.FindProperty("m_AnimationTriggers"); 49 m_NavigationProperty = serializedObject.FindProperty("m_Navigation"); 50 51 m_PropertyPathToExcludeForChildClasses = new[] 52 { 53 m_Script.propertyPath, 54 m_NavigationProperty.propertyPath, 55 m_TransitionProperty.propertyPath, 56 m_ColorBlockProperty.propertyPath, 57 m_SpriteStateProperty.propertyPath, 58 m_AnimTriggerProperty.propertyPath, 59 m_InteractableProperty.propertyPath, 60 m_TargetGraphicProperty.propertyPath, 61 }; 62 63 var trans = GetTransition(m_TransitionProperty); 64 m_ShowColorTint.value = (trans == Selectable.Transition.ColorTint); 65 m_ShowSpriteTrasition.value = (trans == Selectable.Transition.SpriteSwap); 66 m_ShowAnimTransition.value = (trans == Selectable.Transition.Animation); 67 68 m_ShowColorTint.valueChanged.AddListener(Repaint); 69 m_ShowSpriteTrasition.valueChanged.AddListener(Repaint); 70 71 s_Editors.Add(this); 72 RegisterStaticOnSceneGUI(); 73 74 s_ShowNavigation = EditorPrefs.GetBool(s_ShowNavigationKey); 75 } 76 77 protected virtual void OnDisable() 78 { 79 m_ShowColorTint.valueChanged.RemoveListener(Repaint); 80 m_ShowSpriteTrasition.valueChanged.RemoveListener(Repaint); 81 82 s_Editors.Remove(this); 83 RegisterStaticOnSceneGUI(); 84 } 85 86 private void RegisterStaticOnSceneGUI() 87 { 88 SceneView.duringSceneGui -= StaticOnSceneGUI; 89 if (s_Editors.Count > 0) 90 SceneView.duringSceneGui += StaticOnSceneGUI; 91 } 92 93 static Selectable.Transition GetTransition(SerializedProperty transition) 94 { 95 return (Selectable.Transition)transition.enumValueIndex; 96 } 97 98 public override void OnInspectorGUI() 99 { 100 serializedObject.Update(); 101 102 EditorGUILayout.PropertyField(m_InteractableProperty); 103 104 var trans = GetTransition(m_TransitionProperty); 105 106 var graphic = m_TargetGraphicProperty.objectReferenceValue as Graphic; 107 if (graphic == null) 108 graphic = (target as Selectable).GetComponent<Graphic>(); 109 110 var animator = (target as Selectable).GetComponent<Animator>(); 111 m_ShowColorTint.target = (!m_TransitionProperty.hasMultipleDifferentValues && trans == Button.Transition.ColorTint); 112 m_ShowSpriteTrasition.target = (!m_TransitionProperty.hasMultipleDifferentValues && trans == Button.Transition.SpriteSwap); 113 m_ShowAnimTransition.target = (!m_TransitionProperty.hasMultipleDifferentValues && trans == Button.Transition.Animation); 114 115 EditorGUILayout.PropertyField(m_TransitionProperty); 116 117 ++EditorGUI.indentLevel; 118 { 119 if (trans == Selectable.Transition.ColorTint || trans == Selectable.Transition.SpriteSwap) 120 { 121 EditorGUILayout.PropertyField(m_TargetGraphicProperty); 122 } 123 124 switch (trans) 125 { 126 case Selectable.Transition.ColorTint: 127 if (graphic == null) 128 EditorGUILayout.HelpBox("You must have a Graphic target in order to use a color transition.", MessageType.Warning); 129 break; 130 131 case Selectable.Transition.SpriteSwap: 132 if (graphic as Image == null) 133 EditorGUILayout.HelpBox("You must have a Image target in order to use a sprite swap transition.", MessageType.Warning); 134 break; 135 } 136 137 if (EditorGUILayout.BeginFadeGroup(m_ShowColorTint.faded)) 138 { 139 EditorGUILayout.PropertyField(m_ColorBlockProperty); 140 } 141 EditorGUILayout.EndFadeGroup(); 142 143 if (EditorGUILayout.BeginFadeGroup(m_ShowSpriteTrasition.faded)) 144 { 145 EditorGUILayout.PropertyField(m_SpriteStateProperty); 146 } 147 EditorGUILayout.EndFadeGroup(); 148 149 if (EditorGUILayout.BeginFadeGroup(m_ShowAnimTransition.faded)) 150 { 151 EditorGUILayout.PropertyField(m_AnimTriggerProperty); 152 153 if (animator == null || animator.runtimeAnimatorController == null) 154 { 155 Rect buttonRect = EditorGUILayout.GetControlRect(); 156 buttonRect.xMin += EditorGUIUtility.labelWidth; 157 if (GUI.Button(buttonRect, "Auto Generate Animation", EditorStyles.miniButton)) 158 { 159 var controller = GenerateSelectableAnimatorContoller((target as Selectable).animationTriggers, target as Selectable); 160 if (controller != null) 161 { 162 if (animator == null) 163 animator = (target as Selectable).gameObject.AddComponent<Animator>(); 164 165 Animations.AnimatorController.SetAnimatorController(animator, controller); 166 } 167 } 168 } 169 } 170 EditorGUILayout.EndFadeGroup(); 171 } 172 --EditorGUI.indentLevel; 173 174 EditorGUILayout.Space(); 175 176 EditorGUILayout.PropertyField(m_NavigationProperty); 177 178 EditorGUI.BeginChangeCheck(); 179 Rect toggleRect = EditorGUILayout.GetControlRect(); 180 toggleRect.xMin += EditorGUIUtility.labelWidth; 181 s_ShowNavigation = GUI.Toggle(toggleRect, s_ShowNavigation, m_VisualizeNavigation, EditorStyles.miniButton); 182 if (EditorGUI.EndChangeCheck()) 183 { 184 EditorPrefs.SetBool(s_ShowNavigationKey, s_ShowNavigation); 185 SceneView.RepaintAll(); 186 } 187 188 // We do this here to avoid requiring the user to also write a Editor for their Selectable-derived classes. 189 // This way if we are on a derived class we dont draw anything else, otherwise draw the remaining properties. 190 ChildClassPropertiesGUI(); 191 192 serializedObject.ApplyModifiedProperties(); 193 } 194 195 // Draw the extra SerializedProperties of the child class. 196 // We need to make sure that m_PropertyPathToExcludeForChildClasses has all the Selectable properties and in the correct order. 197 // TODO: find a nicer way of doing this. (creating a InheritedEditor class that automagically does this) 198 private void ChildClassPropertiesGUI() 199 { 200 if (IsDerivedSelectableEditor()) 201 return; 202 203 DrawPropertiesExcluding(serializedObject, m_PropertyPathToExcludeForChildClasses); 204 } 205 206 private bool IsDerivedSelectableEditor() 207 { 208 return GetType() != typeof(SelectableEditor); 209 } 210 211 private static Animations.AnimatorController GenerateSelectableAnimatorContoller(AnimationTriggers animationTriggers, Selectable target) 212 { 213 if (target == null) 214 return null; 215 216 // Where should we create the controller? 217 var path = GetSaveControllerPath(target); 218 if (string.IsNullOrEmpty(path)) 219 return null; 220 221 // figure out clip names 222 var normalName = string.IsNullOrEmpty(animationTriggers.normalTrigger) ? "Normal" : animationTriggers.normalTrigger; 223 var highlightedName = string.IsNullOrEmpty(animationTriggers.highlightedTrigger) ? "Highlighted" : animationTriggers.highlightedTrigger; 224 var pressedName = string.IsNullOrEmpty(animationTriggers.pressedTrigger) ? "Pressed" : animationTriggers.pressedTrigger; 225 var selectedName = string.IsNullOrEmpty(animationTriggers.selectedTrigger) ? "Selected" : animationTriggers.selectedTrigger; 226 var disabledName = string.IsNullOrEmpty(animationTriggers.disabledTrigger) ? "Disabled" : animationTriggers.disabledTrigger; 227 228 // Create controller and hook up transitions. 229 var controller = Animations.AnimatorController.CreateAnimatorControllerAtPath(path); 230 GenerateTriggerableTransition(normalName, controller); 231 GenerateTriggerableTransition(highlightedName, controller); 232 GenerateTriggerableTransition(pressedName, controller); 233 GenerateTriggerableTransition(selectedName, controller); 234 GenerateTriggerableTransition(disabledName, controller); 235 236 AssetDatabase.ImportAsset(path); 237 238 return controller; 239 } 240 241 private static string GetSaveControllerPath(Selectable target) 242 { 243 var defaultName = target.gameObject.name; 244 var message = string.Format("Create a new animator for the game object '{0}':", defaultName); 245 return EditorUtility.SaveFilePanelInProject("New Animation Contoller", defaultName, "controller", message); 246 } 247 248 private static void SetUpCurves(AnimationClip highlightedClip, AnimationClip pressedClip, string animationPath) 249 { 250 string[] channels = { "m_LocalScale.x", "m_LocalScale.y", "m_LocalScale.z" }; 251 252 var highlightedKeys = new[] { new Keyframe(0f, 1f), new Keyframe(0.5f, 1.1f), new Keyframe(1f, 1f) }; 253 var highlightedCurve = new AnimationCurve(highlightedKeys); 254 foreach (var channel in channels) 255 AnimationUtility.SetEditorCurve(highlightedClip, EditorCurveBinding.FloatCurve(animationPath, typeof(Transform), channel), highlightedCurve); 256 257 var pressedKeys = new[] { new Keyframe(0f, 1.15f) }; 258 var pressedCurve = new AnimationCurve(pressedKeys); 259 foreach (var channel in channels) 260 AnimationUtility.SetEditorCurve(pressedClip, EditorCurveBinding.FloatCurve(animationPath, typeof(Transform), channel), pressedCurve); 261 } 262 263 private static string BuildAnimationPath(Selectable target) 264 { 265 // if no target don't hook up any curves. 266 var highlight = target.targetGraphic; 267 if (highlight == null) 268 return string.Empty; 269 270 var startGo = highlight.gameObject; 271 var toFindGo = target.gameObject; 272 273 var pathComponents = new Stack<string>(); 274 while (toFindGo != startGo) 275 { 276 pathComponents.Push(startGo.name); 277 278 // didn't exist in hierarchy! 279 if (startGo.transform.parent == null) 280 return string.Empty; 281 282 startGo = startGo.transform.parent.gameObject; 283 } 284 285 // calculate path 286 var animPath = new StringBuilder(); 287 if (pathComponents.Count > 0) 288 animPath.Append(pathComponents.Pop()); 289 290 while (pathComponents.Count > 0) 291 animPath.Append("/").Append(pathComponents.Pop()); 292 293 return animPath.ToString(); 294 } 295 296 private static AnimationClip GenerateTriggerableTransition(string name, Animations.AnimatorController controller) 297 { 298 // Create the clip 299 var clip = Animations.AnimatorController.AllocateAnimatorClip(name); 300 AssetDatabase.AddObjectToAsset(clip, controller); 301 302 // Create a state in the animatior controller for this clip 303 var state = controller.AddMotion(clip); 304 305 // Add a transition property 306 controller.AddParameter(name, AnimatorControllerParameterType.Trigger); 307 308 // Add an any state transition 309 var stateMachine = controller.layers[0].stateMachine; 310 var transition = stateMachine.AddAnyStateTransition(state); 311 transition.AddCondition(Animations.AnimatorConditionMode.If, 0, name); 312 return clip; 313 } 314 315 private static void StaticOnSceneGUI(SceneView view) 316 { 317 if (!s_ShowNavigation) 318 return; 319 320 Selectable[] selectables = Selectable.allSelectablesArray; 321 322 for (int i = 0; i < selectables.Length; i++) 323 { 324 Selectable s = selectables[i]; 325 if (SceneManagement.StageUtility.IsGameObjectRenderedByCamera(s.gameObject, Camera.current)) 326 DrawNavigationForSelectable(s); 327 } 328 } 329 330 private static void DrawNavigationForSelectable(Selectable sel) 331 { 332 if (sel == null) 333 return; 334 335 Transform transform = sel.transform; 336 bool active = Selection.transforms.Any(e => e == transform); 337 338 Handles.color = new Color(1.0f, 0.6f, 0.2f, active ? 1.0f : 0.4f); 339 DrawNavigationArrow(-Vector2.right, sel, sel.FindSelectableOnLeft()); 340 DrawNavigationArrow(Vector2.up, sel, sel.FindSelectableOnUp()); 341 342 Handles.color = new Color(1.0f, 0.9f, 0.1f, active ? 1.0f : 0.4f); 343 DrawNavigationArrow(Vector2.right, sel, sel.FindSelectableOnRight()); 344 DrawNavigationArrow(-Vector2.up, sel, sel.FindSelectableOnDown()); 345 } 346 347 const float kArrowThickness = 2.5f; 348 const float kArrowHeadSize = 1.2f; 349 350 private static void DrawNavigationArrow(Vector2 direction, Selectable fromObj, Selectable toObj) 351 { 352 if (fromObj == null || toObj == null) 353 return; 354 Transform fromTransform = fromObj.transform; 355 Transform toTransform = toObj.transform; 356 357 Vector2 sideDir = new Vector2(direction.y, -direction.x); 358 Vector3 fromPoint = fromTransform.TransformPoint(GetPointOnRectEdge(fromTransform as RectTransform, direction)); 359 Vector3 toPoint = toTransform.TransformPoint(GetPointOnRectEdge(toTransform as RectTransform, -direction)); 360 float fromSize = HandleUtility.GetHandleSize(fromPoint) * 0.05f; 361 float toSize = HandleUtility.GetHandleSize(toPoint) * 0.05f; 362 fromPoint += fromTransform.TransformDirection(sideDir) * fromSize; 363 toPoint += toTransform.TransformDirection(sideDir) * toSize; 364 float length = Vector3.Distance(fromPoint, toPoint); 365 Vector3 fromTangent = fromTransform.rotation * direction * length * 0.3f; 366 Vector3 toTangent = toTransform.rotation * -direction * length * 0.3f; 367 368 Handles.DrawBezier(fromPoint, toPoint, fromPoint + fromTangent, toPoint + toTangent, Handles.color, null, kArrowThickness); 369 Handles.DrawAAPolyLine(kArrowThickness, toPoint, toPoint + toTransform.rotation * (-direction - sideDir) * toSize * kArrowHeadSize); 370 Handles.DrawAAPolyLine(kArrowThickness, toPoint, toPoint + toTransform.rotation * (-direction + sideDir) * toSize * kArrowHeadSize); 371 } 372 373 private static Vector3 GetPointOnRectEdge(RectTransform rect, Vector2 dir) 374 { 375 if (rect == null) 376 return Vector3.zero; 377 if (dir != Vector2.zero) 378 dir /= Mathf.Max(Mathf.Abs(dir.x), Mathf.Abs(dir.y)); 379 dir = rect.rect.center + Vector2.Scale(rect.rect.size, dir * 0.5f); 380 return dir; 381 } 382 } 383}