CinemachineVirtualCameraEditor.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. using UnityEngine;
  2. using UnityEditor;
  3. using System;
  4. using System.Collections.Generic;
  5. using Cinemachine.Utility;
  6. using System.Reflection;
  7. using System.Linq;
  8. namespace Cinemachine.Editor
  9. {
  10. [CustomEditor(typeof(CinemachineVirtualCamera))]
  11. [CanEditMultipleObjects]
  12. internal class CinemachineVirtualCameraEditor
  13. : CinemachineVirtualCameraBaseEditor<CinemachineVirtualCamera>
  14. {
  15. // Static state and caches - Call UpdateStaticData() to refresh this
  16. struct StageData
  17. {
  18. string ExpandedKey { get { return "CNMCN_Core_Vcam_Expanded_" + Name; } }
  19. public bool IsExpanded
  20. {
  21. get { return EditorPrefs.GetBool(ExpandedKey, false); }
  22. set { EditorPrefs.SetBool(ExpandedKey, value); }
  23. }
  24. public string Name;
  25. public Type[] types; // first entry is null
  26. public GUIContent[] PopupOptions;
  27. }
  28. static StageData[] sStageData = null;
  29. bool[] m_hasSameStageDataTypes = new bool[Enum.GetValues(typeof(CinemachineCore.Stage)).Length];
  30. // Instance data - call UpdateInstanceData() to refresh this
  31. int[] m_stageState = null;
  32. bool[] m_stageError = null;
  33. CinemachineComponentBase[] m_components;
  34. UnityEditor.Editor[] m_componentEditors = new UnityEditor.Editor[0];
  35. bool IsPrefab { get; set; }
  36. protected override void OnEnable()
  37. {
  38. // Build static menu arrays via reflection
  39. base.OnEnable();
  40. IsPrefab = Target.gameObject.scene.name == null; // causes a small GC alloc
  41. UpdateStaticData();
  42. UpdateStageDataTypeMatchesForMultiSelection();
  43. Undo.undoRedoPerformed += ResetTargetOnUndo;
  44. }
  45. void ResetTargetOnUndo()
  46. {
  47. UpdateInstanceData();
  48. ResetTarget();
  49. }
  50. protected override void OnDisable()
  51. {
  52. base.OnDisable();
  53. Undo.undoRedoPerformed -= ResetTargetOnUndo;
  54. // Must destroy editors or we get exceptions
  55. if (m_componentEditors != null)
  56. foreach (UnityEditor.Editor e in m_componentEditors)
  57. if (e != null)
  58. UnityEngine.Object.DestroyImmediate(e);
  59. }
  60. Vector3 mPreviousPosition;
  61. private void OnSceneGUI()
  62. {
  63. if (!Target.UserIsDragging)
  64. mPreviousPosition = Target.transform.position;
  65. if (Selection.Contains(Target.gameObject) && Tools.current == Tool.Move
  66. && Event.current.type == EventType.MouseDrag)
  67. {
  68. // User might be dragging our position handle
  69. Target.UserIsDragging = true;
  70. Vector3 delta = Target.transform.position - mPreviousPosition;
  71. if (!delta.AlmostZero())
  72. {
  73. OnPositionDragged(delta);
  74. mPreviousPosition = Target.transform.position;
  75. }
  76. }
  77. else if (GUIUtility.hotControl == 0 && Target.UserIsDragging)
  78. {
  79. // We're not dragging anything now, but we were
  80. InspectorUtility.RepaintGameView();
  81. Target.UserIsDragging = false;
  82. }
  83. }
  84. [MenuItem("CONTEXT/CinemachineVirtualCamera/Adopt Current Camera Settings")]
  85. static void AdoptCurrentCameraSettings(MenuCommand command)
  86. {
  87. var vcam = command.context as CinemachineVirtualCamera;
  88. var brain = CinemachineCore.Instance.FindPotentialTargetBrain(vcam);
  89. if (brain != null)
  90. {
  91. vcam.m_Lens = brain.CurrentCameraState.Lens;
  92. vcam.transform.position = brain.transform.position;
  93. vcam.transform.rotation = brain.transform.rotation;
  94. }
  95. }
  96. [MenuItem("CONTEXT/CinemachineVirtualCamera/Adopt Scene View Camera Settings")]
  97. static void AdoptSceneViewCameraSettings(MenuCommand command)
  98. {
  99. var vcam = command.context as CinemachineVirtualCamera;
  100. CinemachineMenu.SetVcamFromSceneView(vcam);
  101. }
  102. void OnPositionDragged(Vector3 delta)
  103. {
  104. if (m_componentEditors != null)
  105. {
  106. foreach (UnityEditor.Editor e in m_componentEditors)
  107. {
  108. if (e != null)
  109. {
  110. MethodInfo mi = e.GetType().GetMethod("OnVcamPositionDragged"
  111. , BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
  112. if (mi != null && e.target != null)
  113. {
  114. mi.Invoke(e, new object[] { delta } );
  115. }
  116. }
  117. }
  118. }
  119. }
  120. public override void OnInspectorGUI()
  121. {
  122. BeginInspector();
  123. DrawHeaderInInspector();
  124. DrawPropertyInInspector(FindProperty(x => x.m_Priority));
  125. DrawTargetsInInspector(FindProperty(x => x.m_Follow), FindProperty(x => x.m_LookAt));
  126. DrawRemainingPropertiesInInspector();
  127. DrawPipelineInInspector();
  128. DrawExtensionsWidgetInInspector();
  129. }
  130. protected void DrawPipelineInInspector()
  131. {
  132. UpdateInstanceData();
  133. foreach (CinemachineCore.Stage stage in Enum.GetValues(typeof(CinemachineCore.Stage)))
  134. {
  135. int index = (int)stage;
  136. // Skip pipeline stages that have no implementations
  137. if (index < 0 || sStageData[index].PopupOptions.Length <= 1)
  138. continue;
  139. const float indentOffset = 4;
  140. GUIStyle stageBoxStyle = GUI.skin.box;
  141. EditorGUILayout.BeginVertical(stageBoxStyle);
  142. Rect rect = EditorGUILayout.GetControlRect(true);
  143. // Don't use PrefixLabel() because it will link the enabled status of field and label
  144. GUIContent label = new GUIContent(InspectorUtility.NicifyClassName(stage.ToString()));
  145. if (m_stageError[index])
  146. label.image = EditorGUIUtility.IconContent("console.warnicon.sml").image;
  147. float labelWidth = EditorGUIUtility.labelWidth - (indentOffset + EditorGUI.indentLevel * 15);
  148. Rect r = rect; r.width = labelWidth;
  149. EditorGUI.LabelField(r, label);
  150. r = rect; r.width -= labelWidth; r.x += labelWidth;
  151. EditorGUI.BeginChangeCheck();
  152. GUI.enabled = !StageIsLocked(stage);
  153. EditorGUI.showMixedValue = !m_hasSameStageDataTypes[index];
  154. int newSelection = EditorGUI.Popup(r, m_stageState[index], sStageData[index].PopupOptions);
  155. EditorGUI.showMixedValue = false;
  156. GUI.enabled = true;
  157. Type type = sStageData[index].types[newSelection];
  158. if (EditorGUI.EndChangeCheck())
  159. {
  160. SetPipelineStage(stage, type);
  161. if (newSelection != 0)
  162. sStageData[index].IsExpanded = true;
  163. UpdateInstanceData(); // because we changed it
  164. ResetTarget(); // to allow multi-selection correctly adjust every target
  165. return;
  166. }
  167. if (type != null)
  168. {
  169. Rect stageRect = new Rect(
  170. rect.x - indentOffset, rect.y, rect.width + indentOffset, rect.height);
  171. sStageData[index].IsExpanded = EditorGUI.Foldout(
  172. stageRect, sStageData[index].IsExpanded, GUIContent.none, true);
  173. if (sStageData[index].IsExpanded)
  174. {
  175. // Make the editor for that stage
  176. UnityEditor.Editor e = GetEditorForPipelineStage(stage);
  177. if (e != null)
  178. {
  179. ++EditorGUI.indentLevel;
  180. EditorGUILayout.Separator();
  181. e.OnInspectorGUI();
  182. EditorGUILayout.Separator();
  183. --EditorGUI.indentLevel;
  184. }
  185. }
  186. }
  187. EditorGUILayout.EndVertical();
  188. }
  189. }
  190. bool StageIsLocked(CinemachineCore.Stage stage)
  191. {
  192. if (IsPrefab)
  193. return true;
  194. CinemachineCore.Stage[] locked = Target.m_LockStageInInspector;
  195. if (locked != null)
  196. for (int i = 0; i < locked.Length; ++i)
  197. if (locked[i] == stage)
  198. return true;
  199. return false;
  200. }
  201. UnityEditor.Editor GetEditorForPipelineStage(CinemachineCore.Stage stage)
  202. {
  203. if (m_componentEditors != null)
  204. {
  205. foreach (UnityEditor.Editor e in m_componentEditors)
  206. {
  207. if (e != null)
  208. {
  209. CinemachineComponentBase c = e.target as CinemachineComponentBase;
  210. if (c != null && c.Stage == stage)
  211. return e;
  212. }
  213. }
  214. }
  215. return null;
  216. }
  217. /// <summary>
  218. /// Register with CinemachineVirtualCamera to create the pipeline in an undo-friendly manner
  219. /// </summary>
  220. [InitializeOnLoad]
  221. class CreatePipelineWithUndo
  222. {
  223. static CreatePipelineWithUndo()
  224. {
  225. CinemachineVirtualCamera.CreatePipelineOverride =
  226. (CinemachineVirtualCamera vcam, string name, CinemachineComponentBase[] copyFrom) =>
  227. {
  228. // Create a new pipeline
  229. GameObject go = InspectorUtility.CreateGameObject(name);
  230. Undo.RegisterCreatedObjectUndo(go, "created pipeline");
  231. bool partOfPrefab = PrefabUtility.IsPartOfAnyPrefab(vcam.gameObject);
  232. if (!partOfPrefab)
  233. Undo.SetTransformParent(go.transform, vcam.transform, "parenting pipeline");
  234. Undo.AddComponent<CinemachinePipeline>(go);
  235. // If copying, transfer the components
  236. if (copyFrom != null)
  237. {
  238. foreach (Component c in copyFrom)
  239. {
  240. Component copy = Undo.AddComponent(go, c.GetType());
  241. Undo.RecordObject(copy, "copying pipeline");
  242. ReflectionHelpers.CopyFields(c, copy);
  243. }
  244. }
  245. return go.transform;
  246. };
  247. CinemachineVirtualCamera.DestroyPipelineOverride = (GameObject pipeline) =>
  248. {
  249. Undo.DestroyObjectImmediate(pipeline);
  250. };
  251. }
  252. }
  253. void SetPipelineStage(CinemachineCore.Stage stage, Type type)
  254. {
  255. Undo.SetCurrentGroupName("Cinemachine pipeline change");
  256. // Get the existing components
  257. for(int j = 0; j < targets.Length; j++)
  258. {
  259. var vCam = targets[j] as CinemachineVirtualCamera;
  260. Transform owner = vCam.GetComponentOwner();
  261. if (owner == null)
  262. continue; // maybe it's a prefab
  263. CinemachineComponentBase[] components = owner.GetComponents<CinemachineComponentBase>();
  264. if (components == null)
  265. components = new CinemachineComponentBase[0];
  266. // Find an appropriate insertion point
  267. int numComponents = components.Length;
  268. int insertPoint = 0;
  269. for (insertPoint = 0; insertPoint < numComponents; ++insertPoint)
  270. if (components[insertPoint].Stage >= stage)
  271. break;
  272. // Remove the existing components at that stage
  273. for (int i = numComponents - 1; i >= 0; --i)
  274. {
  275. if (components[i].Stage == stage)
  276. {
  277. Undo.DestroyObjectImmediate(components[i]);
  278. components[i] = null;
  279. --numComponents;
  280. if (i < insertPoint)
  281. --insertPoint;
  282. }
  283. }
  284. // Add the new stage
  285. if (type != null)
  286. {
  287. MonoBehaviour b = Undo.AddComponent(owner.gameObject, type) as MonoBehaviour;
  288. while (numComponents-- > insertPoint)
  289. UnityEditorInternal.ComponentUtility.MoveComponentDown(b);
  290. }
  291. }
  292. }
  293. // This code dynamically discovers eligible classes and builds the menu
  294. // data for the various component pipeline stages.
  295. static void UpdateStaticData()
  296. {
  297. if (sStageData != null)
  298. return;
  299. sStageData = new StageData[Enum.GetValues(typeof(CinemachineCore.Stage)).Length];
  300. var stageTypes = new List<Type>[Enum.GetValues(typeof(CinemachineCore.Stage)).Length];
  301. for (int i = 0; i < stageTypes.Length; ++i)
  302. {
  303. sStageData[i].Name = ((CinemachineCore.Stage)i).ToString();
  304. stageTypes[i] = new List<Type>();
  305. }
  306. // Get all ICinemachineComponents
  307. var allTypes
  308. = ReflectionHelpers.GetTypesInAllDependentAssemblies(
  309. (Type t) => typeof(CinemachineComponentBase).IsAssignableFrom(t) && !t.IsAbstract);
  310. // Create a temp game object so we can instance behaviours
  311. GameObject go = new GameObject("Cinemachine Temp Object");
  312. go.hideFlags = HideFlags.DontSaveInBuild | HideFlags.DontSaveInEditor;
  313. foreach (Type t in allTypes)
  314. {
  315. MonoBehaviour b = go.AddComponent(t) as MonoBehaviour;
  316. CinemachineComponentBase c = b != null ? (CinemachineComponentBase)b : null;
  317. if (c != null)
  318. {
  319. CinemachineCore.Stage stage = c.Stage;
  320. stageTypes[(int)stage].Add(t);
  321. }
  322. }
  323. GameObject.DestroyImmediate(go);
  324. // Create the static lists
  325. for (int i = 0; i < stageTypes.Length; ++i)
  326. {
  327. stageTypes[i].Insert(0, null); // first item is "none"
  328. sStageData[i].types = stageTypes[i].ToArray();
  329. GUIContent[] names = new GUIContent[sStageData[i].types.Length];
  330. for (int n = 0; n < names.Length; ++n)
  331. {
  332. if (n == 0)
  333. {
  334. bool useSimple
  335. = (i == (int)CinemachineCore.Stage.Aim)
  336. || (i == (int)CinemachineCore.Stage.Body);
  337. names[n] = new GUIContent((useSimple) ? "Do nothing" : "none");
  338. }
  339. else
  340. names[n] = new GUIContent(InspectorUtility.NicifyClassName(sStageData[i].types[n].Name));
  341. }
  342. sStageData[i].PopupOptions = names;
  343. }
  344. }
  345. void GetPipelineTypes(CinemachineVirtualCamera vcam, ref Type[] types)
  346. {
  347. for (int i = 0; i < types.Length; ++i)
  348. types[i] = null;
  349. if (vcam != null)
  350. {
  351. var components = vcam.GetComponentPipeline();
  352. for (int j = 0; j < components.Length; ++j)
  353. types[(int)components[j].Stage] = components[j].GetType();
  354. }
  355. }
  356. // scratch buffers for pipeline types
  357. Type[] m_PipelineTypeCache0 = new Type[Enum.GetValues(typeof(CinemachineCore.Stage)).Length];
  358. Type[] m_PipelineTypeCacheN = new Type[Enum.GetValues(typeof(CinemachineCore.Stage)).Length];
  359. void UpdateStageDataTypeMatchesForMultiSelection()
  360. {
  361. for (int i = 0; i < m_hasSameStageDataTypes.Length; ++i)
  362. m_hasSameStageDataTypes[i] = true;
  363. if (targets.Length > 1)
  364. {
  365. GetPipelineTypes(serializedObject.targetObjects[0] as CinemachineVirtualCamera, ref m_PipelineTypeCache0);
  366. for (int i = 1; i < targets.Length; ++i)
  367. {
  368. GetPipelineTypes(serializedObject.targetObjects[i] as CinemachineVirtualCamera, ref m_PipelineTypeCacheN);
  369. for (int j = 0; j < m_PipelineTypeCache0.Length; ++j)
  370. if (m_PipelineTypeCache0[j] != m_PipelineTypeCacheN[j])
  371. m_hasSameStageDataTypes[j] = false;
  372. }
  373. }
  374. }
  375. void UpdateInstanceData()
  376. {
  377. // Invalidate the target's cache - this is to support Undo
  378. for (int i = 0; i < targets.Length; i++)
  379. {
  380. var cam = targets[i] as CinemachineVirtualCamera;
  381. if(cam != null)
  382. cam.InvalidateComponentPipeline();
  383. }
  384. UpdateStageDataTypeMatchesForMultiSelection();
  385. UpdateComponentEditors();
  386. UpdateStageState(m_components);
  387. }
  388. // This code dynamically builds editors for the pipeline components.
  389. // Expansion state is cached statically to preserve foldout state.
  390. void UpdateComponentEditors()
  391. {
  392. if (Target == null)
  393. {
  394. m_components = new CinemachineComponentBase[0];
  395. return;
  396. }
  397. CinemachineComponentBase[] components = Target.GetComponentPipeline();
  398. int numComponents = components != null ? components.Length : 0;
  399. if (m_components == null || m_components.Length != numComponents)
  400. m_components = new CinemachineComponentBase[numComponents];
  401. bool dirty = (numComponents == 0);
  402. for (int i = 0; i < numComponents; ++i)
  403. {
  404. if (m_components[i] == null || components[i] != m_components[i])
  405. {
  406. dirty = true;
  407. m_components[i] = components[i];
  408. }
  409. }
  410. if (dirty)
  411. {
  412. // Destroy the subeditors
  413. if (m_componentEditors != null)
  414. foreach (UnityEditor.Editor e in m_componentEditors)
  415. if (e != null)
  416. UnityEngine.Object.DestroyImmediate(e);
  417. // Create new editors
  418. m_componentEditors = new UnityEditor.Editor[numComponents];
  419. for (int i = 0; i < numComponents; ++i)
  420. {
  421. List<MonoBehaviour> behaviours = new List<MonoBehaviour>();
  422. for (int j = 0; j < targets.Length; j++)
  423. {
  424. var cinemachineVirtualCamera = targets[j] as CinemachineVirtualCamera;
  425. if (cinemachineVirtualCamera == null)
  426. continue;
  427. var behaviour = cinemachineVirtualCamera.GetCinemachineComponent(components[i].Stage) as MonoBehaviour;
  428. if (behaviour != null)
  429. behaviours.Add(behaviour);
  430. }
  431. var behaviourArray = behaviours.ToArray();
  432. if (behaviourArray.Length > 0 && m_hasSameStageDataTypes[(int)components[i].Stage])
  433. CreateCachedEditor(behaviourArray, null, ref m_componentEditors[i]);
  434. }
  435. }
  436. }
  437. void UpdateStageState(CinemachineComponentBase[] components)
  438. {
  439. m_stageState = new int[Enum.GetValues(typeof(CinemachineCore.Stage)).Length];
  440. m_stageError = new bool[Enum.GetValues(typeof(CinemachineCore.Stage)).Length];
  441. foreach (var c in components)
  442. {
  443. CinemachineCore.Stage stage = c.Stage;
  444. int index = 0;
  445. for (index = sStageData[(int)stage].types.Length - 1; index > 0; --index)
  446. if (sStageData[(int)stage].types[index] == c.GetType())
  447. break;
  448. m_stageState[(int)stage] = index;
  449. m_stageError[(int)stage] = c == null || !c.IsValid;
  450. }
  451. }
  452. // Because the cinemachine components are attached to hidden objects, their
  453. // gizmos don't get drawn by default. We have to do it explicitly.
  454. [InitializeOnLoad]
  455. static class CollectGizmoDrawers
  456. {
  457. static CollectGizmoDrawers()
  458. {
  459. m_GizmoDrawers = new Dictionary<Type, MethodInfo>();
  460. string definedIn = typeof(CinemachineComponentBase).Assembly.GetName().Name;
  461. Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
  462. foreach (Assembly assembly in assemblies)
  463. {
  464. // Note that we have to call GetName().Name. Just GetName() will not work.
  465. if ((!assembly.GlobalAssemblyCache)
  466. && ((assembly.GetName().Name == definedIn)
  467. || assembly.GetReferencedAssemblies().Any(a => a.Name == definedIn)))
  468. {
  469. try
  470. {
  471. foreach (var type in assembly.GetTypes())
  472. {
  473. try
  474. {
  475. bool added = false;
  476. foreach (var method in type.GetMethods(
  477. BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static))
  478. {
  479. if (added)
  480. break;
  481. if (!method.IsStatic)
  482. continue;
  483. var attributes = method.GetCustomAttributes(typeof(DrawGizmo), true) as DrawGizmo[];
  484. foreach (var a in attributes)
  485. {
  486. if (typeof(CinemachineComponentBase).IsAssignableFrom(a.drawnType) && !a.drawnType.IsAbstract)
  487. {
  488. m_GizmoDrawers.Add(a.drawnType, method);
  489. added = true;
  490. break;
  491. }
  492. }
  493. }
  494. }
  495. catch (System.Exception) {} // Just skip uncooperative types
  496. }
  497. }
  498. catch (System.Exception) {} // Just skip uncooperative assemblies
  499. }
  500. }
  501. }
  502. public static Dictionary<Type, MethodInfo> m_GizmoDrawers;
  503. }
  504. [DrawGizmo(GizmoType.Active | GizmoType.InSelectionHierarchy, typeof(CinemachineVirtualCamera))]
  505. internal static void DrawVirtualCameraGizmos(CinemachineVirtualCamera vcam, GizmoType selectionType)
  506. {
  507. var pipeline = vcam.GetComponentPipeline();
  508. if (pipeline != null)
  509. {
  510. foreach (var c in pipeline)
  511. {
  512. if (c == null)
  513. continue;
  514. MethodInfo method;
  515. if (CollectGizmoDrawers.m_GizmoDrawers.TryGetValue(c.GetType(), out method))
  516. {
  517. if (method != null)
  518. method.Invoke(null, new object[] {c, selectionType});
  519. }
  520. }
  521. }
  522. }
  523. }
  524. }