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