A game about forced loneliness, made by TACStudios
at master 348 lines 14 kB view raw
1using System; 2using System.Collections.Generic; 3using System.Linq; 4using UnityEditor.SceneManagement; 5using UnityEditor.ShortcutManagement; 6using UnityEditor.Timeline.Actions; 7using UnityEngine; 8using UnityEngine.Animations; 9using UnityEngine.Playables; 10using UnityEngine.SceneManagement; 11using UnityEngine.Timeline; 12 13namespace UnityEditor.Timeline 14{ 15 partial class TimelineWindow 16 { 17 private int m_ComponentAddedFrame; 18 19 void OnSelectionChangedInactive() 20 { 21 // Case 946942 -- when selection changes and the window is open but hidden, timeline 22 // needs to update selection immediately so preview mode is correctly released 23 // Case 1123119 -- except when recording 24 if (!hasFocus) 25 { 26 RefreshSelection(!locked && state != null && !state.recording); 27 } 28 } 29 30 void InitializeEditorCallbacks() 31 { 32 Undo.postprocessModifications += PostprocessAnimationRecordingModifications; 33 Undo.postprocessModifications += ProcessAssetModifications; 34 Undo.undoRedoPerformed += OnUndoRedo; 35 EditorApplication.playModeStateChanged += OnPlayModeStateChanged; 36 AnimationUtility.onCurveWasModified += OnCurveModified; 37 EditorApplication.editorApplicationQuit += OnEditorQuit; 38 Selection.selectionChanged += OnSelectionChangedInactive; 39 EditorSceneManager.sceneSaved += OnSceneSaved; 40 ObjectFactory.componentWasAdded += OnComponentWasAdded; 41 PrefabUtility.prefabInstanceUpdated += OnPrefabApplied; 42 EditorApplication.pauseStateChanged += OnPlayModePause; 43 EditorApplication.globalEventHandler += GlobalEventHandler; 44#if TIMELINE_FRAMEACCURATE 45 TimelinePlayable.playableLooped += OnPlayableLooped; 46#endif 47 } 48 49 // This callback is needed because the Animation window registers "Animation/Key Selected" as a global hotkey 50 // and we want to also react to the key. 51 void GlobalEventHandler() 52 { 53 if (instance == null || !state.previewMode) 54 { 55 return; 56 } 57 58 var keyBinding = ShortcutManager.instance.GetShortcutBinding("Animation/Key Selected"); 59 if (keyBinding.Equals(ShortcutBinding.empty)) 60 { 61 return; 62 } 63 64 var evtCombo = KeyCombination.FromKeyboardInput(Event.current); 65 if (keyBinding.keyCombinationSequence.Contains(evtCombo)) 66 { 67 Invoker.InvokeWithSelected<KeyAllAnimated>(); 68 } 69 } 70 71 void OnEditorQuit() 72 { 73 TimelineWindowViewPrefs.SaveAll(); 74 } 75 76 void RemoveEditorCallbacks() 77 { 78 EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; 79 80 Undo.undoRedoPerformed -= OnUndoRedo; 81 Undo.postprocessModifications -= PostprocessAnimationRecordingModifications; 82 Undo.postprocessModifications -= ProcessAssetModifications; 83 AnimationUtility.onCurveWasModified -= OnCurveModified; 84 EditorApplication.editorApplicationQuit -= OnEditorQuit; 85 Selection.selectionChanged -= OnSelectionChangedInactive; 86 EditorSceneManager.sceneSaved -= OnSceneSaved; 87 ObjectFactory.componentWasAdded -= OnComponentWasAdded; 88 PrefabUtility.prefabInstanceUpdated -= OnPrefabApplied; 89 EditorApplication.pauseStateChanged -= OnPlayModePause; 90 EditorApplication.globalEventHandler -= GlobalEventHandler; 91#if TIMELINE_FRAMEACCURATE 92 TimelinePlayable.playableLooped -= OnPlayableLooped; 93#endif 94 } 95 96 void OnPlayModePause(PauseState state) 97 { 98 // in PlayMode, if the timeline is playing, a constant repaint cycle occurs. Pausing the editor 99 // breaks the cycle, so this will restart it 100 Repaint(); 101 } 102 103 // Called when a prefab change is applied to the scene. 104 // Redraw so control tracks that use prefabs can show changes 105 void OnPrefabApplied(GameObject go) 106 { 107 if (!state.previewMode) 108 return; 109 110 // if we added a component this frame, then rebuild, otherwise just let 111 // the individual playable handle the prefab application 112 if (Time.frameCount == m_ComponentAddedFrame) 113 TimelineEditor.Refresh(RefreshReason.ContentsModified); 114 else 115 TimelineEditor.Refresh(RefreshReason.SceneNeedsUpdate); 116 } 117 118 // When the scene is save the director time will get reset. 119 void OnSceneSaved(Scene scene) 120 { 121 if (state != null) 122 state.OnSceneSaved(); 123 } 124 125 void OnCurveModified(AnimationClip clip, EditorCurveBinding binding, AnimationUtility.CurveModifiedType type) 126 { 127 InspectorWindow.RepaintAllInspectors(); 128 if (state == null) 129 return; 130 131 //Force refresh of curve when modified by another editor. 132 Repaint(); 133 134 if (state.previewMode == false) 135 return; 136 137 bool hasPlayable = m_PlayableLookup.GetPlayableFromAnimClip(clip, out Playable playable); 138 139 // mark the timeline clip as dirty 140 TimelineClip timelineClip = m_PlayableLookup.GetTimelineClipFromCurves(clip); 141 if (timelineClip != null) 142 timelineClip.MarkDirty(); 143 144 if (type == AnimationUtility.CurveModifiedType.CurveModified) 145 { 146 if (hasPlayable) 147 { 148 playable.SetAnimatedProperties(clip); 149 } 150 151 // updates the duration of the graph without rebuilding 152 AnimationUtility.SyncEditorCurves(clip); // deleted keys are not synced when this is sent out, so duration could be incorrect 153 state.UpdateRootPlayableDuration(state.editSequence.duration); 154 155 bool isRecording = TimelineRecording.IsRecordingAnimationTrack; 156 PlayableDirector masterDirector = TimelineEditor.masterDirector; 157 bool isGraphValid = masterDirector != null && masterDirector.playableGraph.IsValid(); 158 159 // don't evaluate if this is caused by recording on an animation track, the extra evaluation can cause hiccups 160 // Prevent graphs to be resurrected by a changed clip. 161 if (!isRecording && isGraphValid) 162 state.Evaluate(); 163 } 164 else if (EditorUtility.IsDirty(clip)) // curve added/removed, or clip added/removed 165 { 166 state.rebuildGraph |= timelineClip != null || hasPlayable; 167 } 168 } 169 170 void OnPlayModeStateChanged(PlayModeStateChange playModeState) 171 { 172 // case 923506 - make sure we save view data before switching modes 173 if (playModeState == PlayModeStateChange.ExitingEditMode || 174 playModeState == PlayModeStateChange.ExitingPlayMode) 175 TimelineWindowViewPrefs.SaveAll(); 176 177 bool isPlaymodeAboutToChange = playModeState == PlayModeStateChange.ExitingEditMode || playModeState == PlayModeStateChange.ExitingPlayMode; 178 179 // Important to stop the graph on any director so temporary objects are properly cleaned up 180 if (isPlaymodeAboutToChange && state != null) 181 state.Stop(); 182 } 183 184 UndoPropertyModification[] PostprocessAnimationRecordingModifications(UndoPropertyModification[] modifications) 185 { 186 DirtyModifiedObjects(modifications); 187 188 var remaining = TimelineRecording.ProcessUndoModification(modifications, state); 189 // if we've changed, we need to repaint the sequence window to show clip length changes 190 if (remaining != modifications) 191 { 192 // only update if us or the sequencer window has focus 193 // Prevents color pickers and other dialogs from being wrongly dismissed 194 bool repaint = (focusedWindow == null) || 195 (focusedWindow is InspectorWindow) || 196 (focusedWindow is TimelineWindow); 197 198 if (repaint) 199 Repaint(); 200 } 201 202 203 return remaining; 204 } 205 206 void DirtyModifiedObjects(UndoPropertyModification[] modifications) 207 { 208 foreach (var m in modifications) 209 { 210 if (m.currentValue == null || m.currentValue.target == null) 211 continue; 212 213 var track = m.currentValue.target as TrackAsset; 214 var playableAsset = m.currentValue.target as PlayableAsset; 215 var editorClip = m.currentValue.target as EditorClip; 216 217 if (track != null) 218 { 219 track.MarkDirtyTrackAndClips(); 220 } 221 else if (playableAsset != null) 222 { 223 var clip = TimelineRecording.FindClipWithAsset(state.editSequence.asset, playableAsset); 224 if (clip != null) 225 { 226 clip.MarkDirty(); 227 } 228 } 229 else if (editorClip != null && editorClip.clip != null) 230 { 231 editorClip.clip.MarkDirty(); 232 } 233 } 234 } 235 236 UndoPropertyModification[] ProcessAssetModifications(UndoPropertyModification[] modifications) 237 { 238 bool rebuildGraph = false; 239 240 for (int i = 0; i < modifications.Length && !rebuildGraph; i++) 241 { 242 var mod = modifications[i]; 243 244 if (mod.currentValue != null && mod.currentValue.target is IMarker currentMarker) 245 { 246 if (currentMarker.parent != null && currentMarker.parent.timelineAsset == state.editSequence.asset) 247 { 248 if (mod.currentValue.target is INotification) 249 TimelineEditor.Refresh(RefreshReason.ContentsModified); 250 else 251 TimelineEditor.Refresh(RefreshReason.WindowNeedsRedraw); 252 } 253 } 254 else if (mod.previousValue != null && mod.previousValue.target is AvatarMask) // check if an Avatar Mask has been modified 255 { 256 rebuildGraph = state.editSequence.asset != null && 257 state.editSequence.asset.flattenedTracks 258 .OfType<UnityEngine.Timeline.AnimationTrack>() 259 .Any(x => mod.previousValue.target == x.avatarMask); 260 } 261 } 262 263 if (rebuildGraph) 264 { 265 state.rebuildGraph = true; 266 Repaint(); 267 } 268 269 return modifications; 270 } 271 272 void OnUndoRedo() 273 { 274 var undos = new List<string>(); 275 var redos = new List<string>(); 276 Undo.GetRecords(undos, redos); 277 278 var rebuildAll = redos.Any(x => x.StartsWith("Timeline ")) || undos.Any(x => x.StartsWith("Timeline")); 279 var evalNow = redos.Any(x => x.Contains("Edit Curve")) || undos.Any(x => x.Contains("Edit Curve")); 280 if (rebuildAll || evalNow) 281 { 282 ValidateSelection(); 283 if (state != null) 284 { 285 if (evalNow) // when curves change, the new values need to be set in the transform before the inspector handles the undo 286 state.EvaluateImmediate(); 287 if (rebuildAll) 288 state.Refresh(); 289 } 290 Repaint(); 291 } 292 } 293 294 static void ValidateSelection() 295 { 296 //get all the clips in the selection 297 var selectedClips = Selection.GetFiltered<EditorClip>(SelectionMode.Unfiltered).Select(x => x.clip); 298 foreach (var selectedClip in selectedClips) 299 { 300 var parent = selectedClip.GetParentTrack(); 301 if (selectedClip.GetParentTrack() != null) 302 { 303 if (!parent.clips.Contains(selectedClip)) 304 { 305 SelectionManager.Remove(selectedClip); 306 } 307 } 308 } 309 } 310 311 void OnComponentWasAdded(Component c) 312 { 313 m_ComponentAddedFrame = Time.frameCount; 314 var go = c.gameObject; 315 foreach (var seq in state.GetAllSequences()) 316 { 317 if (seq.director == null || seq.asset == null) 318 { 319 return; 320 } 321 322 var rebind = seq.asset.GetOutputTracks().Any(track => seq.director.GetGenericBinding(track) == go); 323 // Either the playable director has a binding for the GameObject or it is a sibling of the director. 324 // The second case is needed since we have timeline top level markerTracks that do not have a binding, but 325 // are still "targeting" the playable director 326 if (rebind || seq.director.gameObject == go) 327 { 328 seq.director.RebindPlayableGraphOutputs(); 329 } 330 } 331 } 332 333#if TIMELINE_FRAMEACCURATE 334 void OnPlayableLooped(Playable timelinePlayable) 335 { 336 if (state == null || !state.playing || state.masterSequence == null || state.masterSequence.director == null 337 || !state.masterSequence.director.playableGraph.IsValid()) 338 return; 339 var masterPlayable = state.masterSequence.director.playableGraph.GetRootPlayable(0); 340 if (!masterPlayable.Equals(Playable.Null) 341 && masterPlayable.Equals(timelinePlayable) 342 && timelinePlayable.GetGraph().IsMatchFrameRateEnabled()) 343 timelinePlayable.SetTime(0); 344 } 345 346#endif 347 } 348}