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

TimelineWindow.cs 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEditor.Callbacks;
  4. using UnityEngine;
  5. using UnityEngine.Events;
  6. using UnityEngine.Playables;
  7. using UnityEngine.SceneManagement;
  8. using UnityEngine.Timeline;
  9. using Object = UnityEngine.Object;
  10. namespace UnityEditor.Timeline
  11. {
  12. internal interface IWindowStateProvider
  13. {
  14. IWindowState windowState { get; }
  15. }
  16. [EditorWindowTitle(title = "Timeline", useTypeNameAsIconName = true)]
  17. partial class TimelineWindow : TimelineEditorWindow, IHasCustomMenu, IWindowStateProvider
  18. {
  19. [Serializable]
  20. public class TimelineWindowPreferences
  21. {
  22. public EditMode.EditType editType = EditMode.EditType.Mix;
  23. public TimeReferenceMode timeReferenceMode = TimeReferenceMode.Local;
  24. }
  25. [SerializeField] TimelineWindowPreferences m_Preferences = new TimelineWindowPreferences();
  26. public TimelineWindowPreferences preferences { get { return m_Preferences; } }
  27. [SerializeField]
  28. EditorGUIUtility.EditorLockTracker m_LockTracker = new EditorGUIUtility.EditorLockTracker();
  29. readonly PreviewResizer m_PreviewResizer = new PreviewResizer();
  30. bool m_LastFrameHadSequence;
  31. bool m_ForceRefreshLastSelection;
  32. int m_CurrentSceneHashCode = -1;
  33. [NonSerialized]
  34. bool m_HasBeenInitialized;
  35. [SerializeField]
  36. SequenceHierarchy m_SequenceHierarchy;
  37. static SequenceHierarchy s_LastHierarchy;
  38. public static TimelineWindow instance { get; private set; }
  39. public Rect clientArea { get; set; }
  40. public bool isDragging { get; set; }
  41. public static DirectorStyles styles { get { return DirectorStyles.Instance; } }
  42. public List<TimelineTrackBaseGUI> allTracks
  43. {
  44. get
  45. {
  46. return treeView != null ? treeView.allTrackGuis : new List<TimelineTrackBaseGUI>();
  47. }
  48. }
  49. public WindowState state { get; private set; }
  50. IWindowState IWindowStateProvider.windowState => state;
  51. public override bool locked
  52. {
  53. get
  54. {
  55. // we can never be in a locked state if there is no timeline asset
  56. if (state.editSequence.asset == null)
  57. return false;
  58. return m_LockTracker.isLocked;
  59. }
  60. set { m_LockTracker.isLocked = value; }
  61. }
  62. public bool hierarchyChangedThisFrame { get; private set; }
  63. public TimelineWindow()
  64. {
  65. InitializeManipulators();
  66. m_LockTracker.lockStateChanged.AddPersistentListener(OnLockStateChanged, UnityEventCallState.EditorAndRuntime);
  67. }
  68. void OnLockStateChanged(bool locked)
  69. {
  70. // Make sure that upon unlocking, any selection change is updated
  71. // Case 1123119 -- only force rebuild if not recording
  72. if (!locked)
  73. RefreshSelection(state != null && !state.recording);
  74. }
  75. void OnEnable()
  76. {
  77. if (m_SequencePath == null)
  78. m_SequencePath = new SequencePath();
  79. if (m_SequenceHierarchy == null)
  80. {
  81. // The sequence hierarchy will become null if maximize on play is used for in/out of playmode
  82. // a static var will hang on to the reference
  83. if (s_LastHierarchy != null)
  84. m_SequenceHierarchy = s_LastHierarchy;
  85. else
  86. m_SequenceHierarchy = SequenceHierarchy.CreateInstance();
  87. state = null;
  88. }
  89. s_LastHierarchy = m_SequenceHierarchy;
  90. titleContent = GetLocalizedTitleContent();
  91. UpdateTitle();
  92. m_PreviewResizer.Init("TimelineWindow");
  93. // Unmaximize fix : when unmaximizing, a new window is enabled and disabled. Prevent it from overriding the instance pointer.
  94. if (instance == null)
  95. instance = this;
  96. AnimationClipCurveCache.Instance.OnEnable();
  97. TrackAsset.OnClipPlayableCreate += m_PlayableLookup.UpdatePlayableLookup;
  98. TrackAsset.OnTrackAnimationPlayableCreate += m_PlayableLookup.UpdatePlayableLookup;
  99. if (state == null)
  100. {
  101. state = new WindowState(this, s_LastHierarchy);
  102. Initialize();
  103. RefreshSelection(true);
  104. m_ForceRefreshLastSelection = true;
  105. }
  106. }
  107. void OnDisable()
  108. {
  109. if (instance == this)
  110. instance = null;
  111. if (state != null)
  112. state.Reset();
  113. if (instance == null)
  114. SelectionManager.RemoveTimelineSelection();
  115. AnimationClipCurveCache.Instance.OnDisable();
  116. TrackAsset.OnClipPlayableCreate -= m_PlayableLookup.UpdatePlayableLookup;
  117. TrackAsset.OnTrackAnimationPlayableCreate -= m_PlayableLookup.UpdatePlayableLookup;
  118. TimelineWindowViewPrefs.SaveAll();
  119. TimelineWindowViewPrefs.UnloadAllViewModels();
  120. }
  121. void OnDestroy()
  122. {
  123. if (state != null)
  124. {
  125. state.OnDestroy();
  126. }
  127. m_HasBeenInitialized = false;
  128. RemoveEditorCallbacks();
  129. AnimationClipCurveCache.Instance.Clear();
  130. TimelineAnimationUtilities.UnlinkAnimationWindow();
  131. }
  132. void OnLostFocus()
  133. {
  134. isDragging = false;
  135. if (state != null)
  136. state.captured.Clear();
  137. Repaint();
  138. }
  139. void OnHierarchyChange()
  140. {
  141. hierarchyChangedThisFrame = true;
  142. Repaint();
  143. }
  144. void OnStateChange()
  145. {
  146. state.UpdateRecordingState();
  147. state.editSequence.InvalidateChildAssetCache();
  148. if (treeView != null && state.editSequence.asset != null)
  149. treeView.Reload();
  150. if (m_MarkerHeaderGUI != null)
  151. m_MarkerHeaderGUI.Rebuild();
  152. }
  153. void OnGUI()
  154. {
  155. InitializeGUIIfRequired();
  156. UpdateGUIConstants();
  157. UpdateViewStateHash();
  158. EditMode.HandleModeClutch(); // TODO We Want that here?
  159. DetectStylesChange();
  160. DetectActiveSceneChanges();
  161. DetectStateChanges();
  162. state.ProcessStartFramePendingUpdates();
  163. var clipRect = new Rect(0.0f, 0.0f, position.width, position.height);
  164. using (new GUIViewportScope(clipRect))
  165. state.InvokeWindowOnGuiStarted(Event.current);
  166. if (Event.current.type == EventType.MouseDrag && state != null && state.mouseDragLag > 0.0f)
  167. {
  168. state.mouseDragLag -= Time.deltaTime;
  169. return;
  170. }
  171. if (PerformUndo())
  172. return;
  173. if (state != null && state.ignorePreview && state.playing)
  174. {
  175. if (state.recording)
  176. state.recording = false;
  177. Repaint();
  178. }
  179. clientArea = position;
  180. PlaybackScroller.AutoScroll(state);
  181. DoLayout();
  182. // overlays
  183. if (state.captured.Count > 0)
  184. {
  185. using (new GUIViewportScope(clipRect))
  186. {
  187. foreach (var o in state.captured)
  188. {
  189. o.Overlay(Event.current, state);
  190. }
  191. Repaint();
  192. }
  193. }
  194. if (state.showQuadTree)
  195. {
  196. var fillColor = new Color(1.0f, 1.0f, 1.0f, 0.1f);
  197. state.spacePartitioner.DebugDraw(fillColor, Color.yellow);
  198. state.headerSpacePartitioner.DebugDraw(fillColor, Color.green);
  199. }
  200. // attempt another rebuild -- this will avoid 1 frame flashes
  201. if (Event.current.type == EventType.Repaint)
  202. {
  203. RebuildGraphIfNecessary();
  204. state.ProcessEndFramePendingUpdates();
  205. }
  206. using (new GUIViewportScope(clipRect))
  207. {
  208. if (Event.current.type == EventType.Repaint)
  209. EditMode.inputHandler.OnGUI(state, Event.current);
  210. }
  211. if (Event.current.type == EventType.Repaint)
  212. {
  213. hierarchyChangedThisFrame = false;
  214. }
  215. if (Event.current.type == EventType.Layout)
  216. {
  217. UpdateTitle();
  218. }
  219. }
  220. void UpdateTitle()
  221. {
  222. #if UNITY_2020_2_OR_NEWER
  223. bool dirty = false;
  224. List<Object> children = state?.editSequence.cachedChildAssets;
  225. if (children != null)
  226. {
  227. foreach (var child in children)
  228. {
  229. dirty = EditorUtility.IsDirty(child);
  230. if (dirty)
  231. {
  232. break;
  233. }
  234. }
  235. }
  236. hasUnsavedChanges = dirty;
  237. #endif
  238. }
  239. static void DetectStylesChange()
  240. {
  241. DirectorStyles.ReloadStylesIfNeeded();
  242. }
  243. void DetectActiveSceneChanges()
  244. {
  245. if (m_CurrentSceneHashCode == -1)
  246. {
  247. m_CurrentSceneHashCode = SceneManager.GetActiveScene().GetHashCode();
  248. }
  249. if (m_CurrentSceneHashCode != SceneManager.GetActiveScene().GetHashCode())
  250. {
  251. bool isSceneStillLoaded = false;
  252. for (int a = 0; a < SceneManager.sceneCount; a++)
  253. {
  254. var scene = SceneManager.GetSceneAt(a);
  255. if (scene.GetHashCode() == m_CurrentSceneHashCode && scene.isLoaded)
  256. {
  257. isSceneStillLoaded = true;
  258. break;
  259. }
  260. }
  261. if (!isSceneStillLoaded)
  262. {
  263. if (!locked)
  264. ClearTimeline();
  265. m_CurrentSceneHashCode = SceneManager.GetActiveScene().GetHashCode();
  266. }
  267. }
  268. }
  269. void DetectStateChanges()
  270. {
  271. if (state != null)
  272. {
  273. foreach (var sequenceState in state.allSequences)
  274. {
  275. sequenceState.ResetIsReadOnly();
  276. }
  277. // detect if the sequence was removed under our feet
  278. if (m_LastFrameHadSequence && state.editSequence.asset == null)
  279. {
  280. ClearTimeline();
  281. }
  282. m_LastFrameHadSequence = state.editSequence.asset != null;
  283. // the currentDirector can get set to null by a deletion or scene unloading so polling is required
  284. if (state.editSequence.director == null)
  285. {
  286. state.recording = false;
  287. state.previewMode = false;
  288. if (locked)
  289. {
  290. //revert lock if the original context was not asset mode
  291. if (!state.masterSequence.isAssetOnly)
  292. locked = false;
  293. }
  294. if (!locked && m_LastFrameHadSequence)
  295. {
  296. // the user may be adding a new PlayableDirector to a selected GameObject, make sure the timeline editor is shows the proper director if none is already showing
  297. var selectedGameObject = Selection.activeObject != null ? Selection.activeObject as GameObject : null;
  298. var selectedDirector = selectedGameObject != null ? selectedGameObject.GetComponent<PlayableDirector>() : null;
  299. if (selectedDirector != null)
  300. {
  301. SetTimeline(selectedDirector);
  302. }
  303. else
  304. {
  305. state.masterSequence.isAssetOnly = true;
  306. }
  307. }
  308. }
  309. else
  310. {
  311. // the user may have changed the timeline associated with the current director
  312. if (state.editSequence.asset != state.editSequence.director.playableAsset)
  313. {
  314. if (!locked)
  315. {
  316. SetTimeline(state.editSequence.director);
  317. }
  318. else
  319. {
  320. // Keep locked on the current timeline but set the current director to null since it's not the timeline owner anymore
  321. SetTimeline(state.editSequence.asset);
  322. }
  323. }
  324. }
  325. }
  326. }
  327. void Initialize()
  328. {
  329. if (!m_HasBeenInitialized)
  330. {
  331. InitializeStateChange();
  332. InitializeEditorCallbacks();
  333. m_HasBeenInitialized = true;
  334. }
  335. }
  336. void RefreshLastSelectionIfRequired()
  337. {
  338. // case 1088918 - workaround for the instanceID to object cache being update during Awake.
  339. // This corrects any playableDirector ptrs with the correct cached version
  340. // This can happen when going from edit to playmode
  341. if (m_ForceRefreshLastSelection)
  342. {
  343. m_ForceRefreshLastSelection = false;
  344. RestoreLastSelection(true);
  345. }
  346. }
  347. void InitializeGUIIfRequired()
  348. {
  349. RefreshLastSelectionIfRequired();
  350. InitializeTimeArea();
  351. if (treeView == null && state.editSequence.asset != null)
  352. {
  353. treeView = new TimelineTreeViewGUI(this, state.editSequence.asset, position);
  354. }
  355. }
  356. void UpdateGUIConstants()
  357. {
  358. m_HorizontalScrollBarSize =
  359. GUI.skin.horizontalScrollbar.fixedHeight + GUI.skin.horizontalScrollbar.margin.top;
  360. m_VerticalScrollBarSize = (treeView != null && treeView.showingVerticalScrollBar)
  361. ? GUI.skin.verticalScrollbar.fixedWidth + GUI.skin.verticalScrollbar.margin.left
  362. : 0;
  363. }
  364. void UpdateViewStateHash()
  365. {
  366. if (Event.current.type == EventType.Layout)
  367. state.UpdateViewStateHash();
  368. }
  369. static bool PerformUndo()
  370. {
  371. if (!Event.current.isKey)
  372. return false;
  373. if (Event.current.keyCode != KeyCode.Z)
  374. return false;
  375. if (!EditorGUI.actionKey)
  376. return false;
  377. return true;
  378. }
  379. public void RebuildGraphIfNecessary(bool evaluate = true)
  380. {
  381. if (state == null || currentMode.mode != TimelineModes.Active || state.editSequence.director == null || state.editSequence.asset == null)
  382. return;
  383. if (state.rebuildGraph)
  384. {
  385. // rebuilding the graph resets the time
  386. double time = state.editSequence.time;
  387. var wasPlaying = false;
  388. // disable preview mode,
  389. if (!state.ignorePreview)
  390. {
  391. wasPlaying = state.playing;
  392. state.previewMode = false;
  393. state.GatherProperties(state.masterSequence.director);
  394. }
  395. state.RebuildPlayableGraph();
  396. state.editSequence.time = time;
  397. if (wasPlaying)
  398. state.Play();
  399. if (evaluate)
  400. {
  401. // put the scene back in the correct state
  402. state.EvaluateImmediate();
  403. // this is necessary to see accurate results when inspector refreshes
  404. // case 1154802 - this will property re-force time on the director, so
  405. // the play head won't snap back to the timeline duration on rebuilds
  406. if (!state.playing)
  407. state.Evaluate();
  408. }
  409. Repaint();
  410. }
  411. state.rebuildGraph = false;
  412. }
  413. // for tests
  414. public new void RepaintImmediately()
  415. {
  416. base.RepaintImmediately();
  417. }
  418. internal static bool IsEditingTimelineAsset(TimelineAsset timelineAsset)
  419. {
  420. return instance != null && instance.state != null && instance.state.editSequence.asset == timelineAsset;
  421. }
  422. internal static void RepaintIfEditingTimelineAsset(TimelineAsset timelineAsset)
  423. {
  424. if (IsEditingTimelineAsset(timelineAsset))
  425. instance.Repaint();
  426. }
  427. internal class DoCreateTimeline : ProjectWindowCallback.EndNameEditAction
  428. {
  429. public override void Action(int instanceId, string pathName, string resourceFile)
  430. {
  431. var timeline = TimelineUtility.CreateAndSaveTimelineAsset(pathName);
  432. ProjectWindowUtil.ShowCreatedAsset(timeline);
  433. }
  434. }
  435. [MenuItem("Assets/Create/Timeline/Timeline", false, -124)]
  436. public static void CreateNewTimeline()
  437. {
  438. var icon = EditorGUIUtility.IconContent("TimelineAsset Icon").image as Texture2D;
  439. ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, CreateInstance<DoCreateTimeline>(), "New Timeline.playable", icon, null);
  440. }
  441. [MenuItem("Window/Sequencing/Timeline", false, 1)]
  442. public static void ShowWindow()
  443. {
  444. GetWindow<TimelineWindow>(typeof(SceneView));
  445. instance.Focus();
  446. }
  447. [OnOpenAsset(1)]
  448. public static bool OnDoubleClick(int instanceID, int line)
  449. {
  450. var assetDoubleClicked = EditorUtility.InstanceIDToObject(instanceID) as TimelineAsset;
  451. if (assetDoubleClicked == null)
  452. return false;
  453. ShowWindow();
  454. instance.SetTimeline(assetDoubleClicked);
  455. return true;
  456. }
  457. public virtual void AddItemsToMenu(GenericMenu menu)
  458. {
  459. bool disabled = state == null || state.editSequence.asset == null;
  460. m_LockTracker.AddItemsToMenu(menu, disabled);
  461. }
  462. protected virtual void ShowButton(Rect r)
  463. {
  464. bool disabled = state == null || state.editSequence.asset == null;
  465. m_LockTracker.ShowButton(r, DirectorStyles.Instance.timelineLockButton, disabled);
  466. }
  467. }
  468. }