CinemachineSmoothPathEditor.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. using UnityEditor;
  2. using UnityEngine;
  3. using System.Linq;
  4. using System.Collections.Generic;
  5. using UnityEditorInternal;
  6. namespace Cinemachine.Editor
  7. {
  8. [CustomEditor(typeof(CinemachineSmoothPath))]
  9. internal sealed class CinemachineSmoothPathEditor : BaseEditor<CinemachineSmoothPath>
  10. {
  11. private ReorderableList mWaypointList;
  12. /// <summary>Get the property names to exclude in the inspector.</summary>
  13. /// <param name="excluded">Add the names to this list</param>
  14. protected override void GetExcludedPropertiesInInspector(List<string> excluded)
  15. {
  16. base.GetExcludedPropertiesInInspector(excluded);
  17. excluded.Add(FieldPath(x => x.m_Waypoints));
  18. }
  19. void OnEnable()
  20. {
  21. mWaypointList = null;
  22. }
  23. // ReSharper disable once UnusedMember.Global - magic method called when doing Frame Selected
  24. public bool HasFrameBounds()
  25. {
  26. return Target.m_Waypoints != null && Target.m_Waypoints.Length > 0;
  27. }
  28. // ReSharper disable once UnusedMember.Global - magic method called when doing Frame Selected
  29. public Bounds OnGetFrameBounds()
  30. {
  31. Vector3[] wp;
  32. int selected = mWaypointList == null ? -1 : mWaypointList.index;
  33. if (selected >= 0 && selected < Target.m_Waypoints.Length)
  34. wp = new Vector3[1] { Target.m_Waypoints[selected].position };
  35. else
  36. wp = Target.m_Waypoints.Select(p => p.position).ToArray();
  37. return GeometryUtility.CalculateBounds(wp, Target.transform.localToWorldMatrix);
  38. }
  39. public override void OnInspectorGUI()
  40. {
  41. BeginInspector();
  42. if (mWaypointList == null)
  43. SetupWaypointList();
  44. if (mWaypointList.index >= mWaypointList.count)
  45. mWaypointList.index = mWaypointList.count - 1;
  46. // Ordinary properties
  47. DrawRemainingPropertiesInInspector();
  48. // Path length
  49. EditorGUILayout.LabelField("Path Length", Target.PathLength.ToString());
  50. // Waypoints
  51. EditorGUI.BeginChangeCheck();
  52. mWaypointList.DoLayoutList();
  53. if (EditorGUI.EndChangeCheck())
  54. serializedObject.ApplyModifiedProperties();
  55. }
  56. void SetupWaypointList()
  57. {
  58. mWaypointList = new ReorderableList(
  59. serializedObject, FindProperty(x => x.m_Waypoints),
  60. true, true, true, true);
  61. mWaypointList.drawHeaderCallback = (Rect rect) =>
  62. { EditorGUI.LabelField(rect, "Waypoints"); };
  63. mWaypointList.drawElementCallback
  64. = (Rect rect, int index, bool isActive, bool isFocused) =>
  65. { DrawWaypointEditor(rect, index); };
  66. mWaypointList.onAddCallback = (ReorderableList l) =>
  67. { InsertWaypointAtIndex(l.index); };
  68. }
  69. void DrawWaypointEditor(Rect rect, int index)
  70. {
  71. // Needed for accessing string names of fields
  72. CinemachineSmoothPath.Waypoint def = new CinemachineSmoothPath.Waypoint();
  73. SerializedProperty element = mWaypointList.serializedProperty.GetArrayElementAtIndex(index);
  74. float hSpace = 3;
  75. rect.width -= hSpace; rect.y += 1;
  76. Vector2 numberDimension = GUI.skin.label.CalcSize(new GUIContent("999"));
  77. Rect r = new Rect(rect.position, numberDimension);
  78. if (GUI.Button(r, new GUIContent(index.ToString(), "Go to the waypoint in the scene view")))
  79. {
  80. if (SceneView.lastActiveSceneView != null)
  81. {
  82. mWaypointList.index = index;
  83. SceneView.lastActiveSceneView.pivot = Target.EvaluatePosition(index);
  84. SceneView.lastActiveSceneView.size = 4;
  85. SceneView.lastActiveSceneView.Repaint();
  86. }
  87. }
  88. float floatFieldWidth = EditorGUIUtility.singleLineHeight * 2f;
  89. GUIContent rollLabel = new GUIContent("Roll");
  90. Vector2 labelDimension = GUI.skin.label.CalcSize(rollLabel);
  91. float rollWidth = labelDimension.x + floatFieldWidth;
  92. r.x += r.width + hSpace; r.width = rect.width - (r.width + hSpace + rollWidth) - (r.height + hSpace);
  93. EditorGUI.PropertyField(r, element.FindPropertyRelative(() => def.position), GUIContent.none);
  94. r.x += r.width + hSpace; r.width = rollWidth;
  95. float oldWidth = EditorGUIUtility.labelWidth;
  96. EditorGUIUtility.labelWidth = labelDimension.x;
  97. var indent = EditorGUI.indentLevel;
  98. EditorGUI.indentLevel = 0;
  99. EditorGUI.PropertyField(r, element.FindPropertyRelative(() => def.roll), rollLabel);
  100. EditorGUIUtility.labelWidth = oldWidth;
  101. EditorGUI.indentLevel = indent;
  102. r.x += r.width + hSpace; r.height += 1; r.width = r.height;
  103. GUIContent setButtonContent = EditorGUIUtility.IconContent("d_RectTransform Icon");
  104. setButtonContent.tooltip = "Set to scene-view camera position";
  105. if (GUI.Button(r, setButtonContent, GUI.skin.label) && SceneView.lastActiveSceneView != null)
  106. {
  107. Undo.RecordObject(Target, "Set waypoint");
  108. CinemachineSmoothPath.Waypoint wp = Target.m_Waypoints[index];
  109. Vector3 pos = SceneView.lastActiveSceneView.camera.transform.position;
  110. wp.position = Target.transform.InverseTransformPoint(pos);
  111. Target.m_Waypoints[index] = wp;
  112. }
  113. }
  114. void InsertWaypointAtIndex(int indexA)
  115. {
  116. Vector3 pos = Vector3.right;
  117. float roll = 0;
  118. // Get new values from the current indexA (if any)
  119. int numWaypoints = Target.m_Waypoints.Length;
  120. if (indexA < 0)
  121. indexA = numWaypoints - 1;
  122. if (indexA >= 0)
  123. {
  124. int indexB = indexA + 1;
  125. if (Target.m_Looped && indexB >= numWaypoints)
  126. indexB = 0;
  127. if (indexB >= numWaypoints)
  128. {
  129. Vector3 delta = Vector3.right;
  130. if (indexA > 0)
  131. delta = Target.m_Waypoints[indexA].position - Target.m_Waypoints[indexA-1].position;
  132. pos = Target.m_Waypoints[indexA].position + delta;
  133. roll = Target.m_Waypoints[indexA].roll;
  134. }
  135. else
  136. {
  137. // Interpolate
  138. pos = Target.transform.InverseTransformPoint(Target.EvaluatePosition(0.5f + indexA));
  139. roll = Mathf.Lerp(Target.m_Waypoints[indexA].roll, Target.m_Waypoints[indexB].roll, 0.5f);
  140. }
  141. }
  142. Undo.RecordObject(Target, "Add waypoint");
  143. var wp = new CinemachineSmoothPath.Waypoint();
  144. wp.position = pos;
  145. wp.roll = roll;
  146. var list = new List<CinemachineSmoothPath.Waypoint>(Target.m_Waypoints);
  147. list.Insert(indexA + 1, wp);
  148. Target.m_Waypoints = list.ToArray();
  149. Target.InvalidateDistanceCache();
  150. InspectorUtility.RepaintGameView();
  151. mWaypointList.index = indexA + 1; // select it
  152. }
  153. void OnSceneGUI()
  154. {
  155. if (mWaypointList == null)
  156. SetupWaypointList();
  157. if (Tools.current == Tool.Move)
  158. {
  159. Color colorOld = Handles.color;
  160. var localToWorld = Target.transform.localToWorldMatrix;
  161. for (int i = 0; i < Target.m_Waypoints.Length; ++i)
  162. {
  163. DrawSelectionHandle(i, localToWorld);
  164. if (mWaypointList.index == i)
  165. DrawPositionControl(i, localToWorld, Target.transform.rotation); // Waypoint is selected
  166. }
  167. Handles.color = colorOld;
  168. }
  169. }
  170. void DrawSelectionHandle(int i, Matrix4x4 localToWorld)
  171. {
  172. if (Event.current.button != 1)
  173. {
  174. Vector3 pos = localToWorld.MultiplyPoint(Target.m_Waypoints[i].position);
  175. float size = HandleUtility.GetHandleSize(pos) * 0.2f;
  176. Handles.color = Color.white;
  177. if (Handles.Button(pos, Quaternion.identity, size, size, Handles.SphereHandleCap)
  178. && mWaypointList.index != i)
  179. {
  180. mWaypointList.index = i;
  181. InspectorUtility.RepaintGameView();
  182. }
  183. // Label it
  184. Handles.BeginGUI();
  185. Vector2 labelSize = new Vector2(
  186. EditorGUIUtility.singleLineHeight * 2, EditorGUIUtility.singleLineHeight);
  187. Vector2 labelPos = HandleUtility.WorldToGUIPoint(pos);
  188. labelPos.y -= labelSize.y / 2;
  189. labelPos.x -= labelSize.x / 2;
  190. GUILayout.BeginArea(new Rect(labelPos, labelSize));
  191. GUIStyle style = new GUIStyle();
  192. style.normal.textColor = Color.black;
  193. style.alignment = TextAnchor.MiddleCenter;
  194. GUILayout.Label(new GUIContent(i.ToString(), "Waypoint " + i), style);
  195. GUILayout.EndArea();
  196. Handles.EndGUI();
  197. }
  198. }
  199. void DrawPositionControl(int i, Matrix4x4 localToWorld, Quaternion localRotation)
  200. {
  201. CinemachineSmoothPath.Waypoint wp = Target.m_Waypoints[i];
  202. Vector3 pos = localToWorld.MultiplyPoint(wp.position);
  203. EditorGUI.BeginChangeCheck();
  204. Handles.color = Target.m_Appearance.pathColor;
  205. Quaternion rotation = (Tools.pivotRotation == PivotRotation.Local)
  206. ? localRotation : Quaternion.identity;
  207. float size = HandleUtility.GetHandleSize(pos) * 0.1f;
  208. Handles.SphereHandleCap(0, pos, rotation, size, EventType.Repaint);
  209. pos = Handles.PositionHandle(pos, rotation);
  210. if (EditorGUI.EndChangeCheck())
  211. {
  212. Undo.RecordObject(target, "Move Waypoint");
  213. wp.position = Matrix4x4.Inverse(localToWorld).MultiplyPoint(pos);
  214. Target.m_Waypoints[i] = wp;
  215. Target.InvalidateDistanceCache();
  216. InspectorUtility.RepaintGameView();
  217. }
  218. }
  219. [DrawGizmo(GizmoType.Active | GizmoType.NotInSelectionHierarchy
  220. | GizmoType.InSelectionHierarchy | GizmoType.Pickable, typeof(CinemachineSmoothPath))]
  221. static void DrawGizmos(CinemachineSmoothPath path, GizmoType selectionType)
  222. {
  223. var isActive = Selection.activeGameObject == path.gameObject;
  224. CinemachinePathEditor.DrawPathGizmo(path,
  225. isActive ? path.m_Appearance.pathColor : path.m_Appearance.inactivePathColor, isActive);
  226. }
  227. }
  228. }