暂无描述
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

SelectableEditor.cs 17KB

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