123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497 |
- using System.Collections.Generic;
- using System.Linq;
- using UnityEngine;
- using UnityEngine.Timeline;
- using UnityEngine.Playables;
- namespace UnityEditor.Timeline
- {
- // Handles Undo animated properties on Monobehaviours to create track clips
- static partial class TimelineRecording
- {
- static readonly List<PropertyModification> s_TempPropertyModifications = new List<PropertyModification>(6);
- internal static UndoPropertyModification[] ProcessUndoModification(UndoPropertyModification[] modifications, WindowState state)
- {
- if (HasAnyPlayableAssetModifications(modifications))
- return ProcessPlayableAssetModification(modifications, state);
- return ProcessMonoBehaviourModification(modifications, state);
- }
- static UnityEngine.Object GetTarget(UndoPropertyModification undo)
- {
- if (undo.currentValue != null)
- return undo.currentValue.target;
- if (undo.previousValue != null)
- return undo.previousValue.target;
- return null;
- }
- // Gets the appropriate track for a given game object
- static TrackAsset GetTrackForGameObject(GameObject gameObject, WindowState state)
- {
- if (gameObject == null)
- return null;
- var director = state.editSequence.director;
- if (director == null)
- return null;
- var level = int.MaxValue;
- TrackAsset result = null;
- // search the output tracks
- var outputTracks = state.editSequence.asset.flattenedTracks;
- foreach (var track in outputTracks)
- {
- if (track.GetType() != typeof(AnimationTrack))
- continue;
- if (!state.IsTrackRecordable(track))
- continue;
- var obj = TimelineUtility.GetSceneGameObject(director, track);
- if (obj != null)
- {
- // checks if the effected gameobject is our child
- var childLevel = GetChildLevel(obj, gameObject);
- if (childLevel != -1 && childLevel < level)
- {
- result = track;
- level = childLevel;
- }
- }
- }
- // the resulting track is not armed. checking here avoids accidentally recording objects with their own
- // tracks
- if (result && !state.IsTrackRecordable(result))
- {
- result = null;
- }
- return result;
- }
- // Gets the track this property would record to.
- // Returns null if there is a track, but it's not currently active for recording
- public static TrackAsset GetRecordingTrack(SerializedProperty property, WindowState state)
- {
- var serializedObject = property.serializedObject;
- var component = serializedObject.targetObject as Component;
- if (component == null)
- return null;
- var gameObject = component.gameObject;
- return GetTrackForGameObject(gameObject, state);
- }
- // Given a serialized property, gathers all animatable properties
- static void GatherModifications(SerializedProperty property, List<PropertyModification> modifications)
- {
- // handles child properties (Vector3 is 3 recordable properties)
- if (property.hasChildren)
- {
- var iter = property.Copy();
- var end = property.GetEndProperty(false);
- // recurse over all children properties
- while (iter.Next(true) && !SerializedProperty.EqualContents(iter, end))
- {
- GatherModifications(iter, modifications);
- }
- }
- var isObject = property.propertyType == SerializedPropertyType.ObjectReference;
- var isFloat = property.propertyType == SerializedPropertyType.Float ||
- property.propertyType == SerializedPropertyType.Boolean ||
- property.propertyType == SerializedPropertyType.Integer;
- if (isObject || isFloat)
- {
- var serializedObject = property.serializedObject;
- var modification = new PropertyModification();
- modification.target = serializedObject.targetObject;
- modification.propertyPath = property.propertyPath;
- if (isObject)
- {
- modification.value = string.Empty;
- modification.objectReference = property.objectReferenceValue;
- }
- else
- {
- modification.value = TimelineUtility.PropertyToString(property);
- }
- // Path for monobehaviour based - better to grab the component to get the curvebinding to allow validation
- if (serializedObject.targetObject is Component)
- {
- EditorCurveBinding temp;
- var go = ((Component)serializedObject.targetObject).gameObject;
- if (AnimationUtility.PropertyModificationToEditorCurveBinding(modification, go, out temp) != null)
- {
- modifications.Add(modification);
- }
- }
- else
- {
- modifications.Add(modification);
- }
- }
- }
- public static bool CanRecord(SerializedProperty property, WindowState state)
- {
- if (IsPlayableAssetProperty(property))
- return AnimatedParameterUtility.IsTypeAnimatable(property.propertyType);
- if (GetRecordingTrack(property, state) == null)
- return false;
- s_TempPropertyModifications.Clear();
- GatherModifications(property, s_TempPropertyModifications);
- return s_TempPropertyModifications.Any();
- }
- public static void AddKey(SerializedProperty prop, WindowState state)
- {
- s_TempPropertyModifications.Clear();
- GatherModifications(prop, s_TempPropertyModifications);
- if (s_TempPropertyModifications.Any())
- {
- AddKey(s_TempPropertyModifications, state);
- }
- }
- public static void AddKey(IEnumerable<PropertyModification> modifications, WindowState state)
- {
- var undos = modifications.Select(PropertyModificationToUndoPropertyModification).ToArray();
- ProcessUndoModification(undos, state);
- }
- static UndoPropertyModification PropertyModificationToUndoPropertyModification(PropertyModification prop)
- {
- return new UndoPropertyModification
- {
- previousValue = prop,
- currentValue = new PropertyModification
- {
- objectReference = prop.objectReference,
- propertyPath = prop.propertyPath,
- target = prop.target,
- value = prop.value
- },
- keepPrefabOverride = true
- };
- }
- // Given an animation track, return the clip that we are currently recording to
- static AnimationClip GetRecordingClip(TrackAsset asset, WindowState state, out double startTime, out double timeScale)
- {
- startTime = 0;
- timeScale = 1;
- TimelineClip displayBackground = null;
- asset.FindRecordingClipAtTime(state.editSequence.time, out displayBackground);
- var animClip = asset.FindRecordingAnimationClipAtTime(state.editSequence.time);
- if (displayBackground != null)
- {
- startTime = displayBackground.start;
- timeScale = displayBackground.timeScale;
- }
- return animClip;
- }
- // Helper that finds the animation clip we are recording and the relative time to that clip
- static bool GetClipAndRelativeTime(UnityEngine.Object target, WindowState state,
- out AnimationClip outClip, out double keyTime, out bool keyInRange)
- {
- const float floatToDoubleError = 0.00001f;
- outClip = null;
- keyTime = 0;
- keyInRange = false;
- double startTime = 0;
- double timeScale = 1;
- AnimationClip clip = null;
- IPlayableAsset playableAsset = target as IPlayableAsset;
- Component component = target as Component;
- // Handle recordable playable assets
- if (playableAsset != null)
- {
- var curvesOwner = AnimatedParameterUtility.ToCurvesOwner(playableAsset, state.editSequence.asset);
- if (curvesOwner != null && state.IsTrackRecordable(curvesOwner.targetTrack))
- {
- if (curvesOwner.curves == null)
- curvesOwner.CreateCurves(curvesOwner.GetUniqueRecordedClipName());
- clip = curvesOwner.curves;
- var timelineClip = curvesOwner as TimelineClip;
- if (timelineClip != null)
- {
- startTime = timelineClip.start;
- timeScale = timelineClip.timeScale;
- }
- }
- }
- // Handle recording components, including infinite clip
- else if (component != null)
- {
- var asset = GetTrackForGameObject(component.gameObject, state);
- if (asset != null)
- {
- clip = GetRecordingClip(asset, state, out startTime, out timeScale);
- }
- }
- if (clip == null)
- return false;
- keyTime = (state.editSequence.time - startTime) * timeScale;
- outClip = clip;
- keyInRange = keyTime >= 0 && keyTime <= (clip.length * timeScale + floatToDoubleError);
- return true;
- }
- public static bool HasCurve(IEnumerable<PropertyModification> modifications, UnityEngine.Object target,
- WindowState state)
- {
- return GetKeyTimes(target, modifications, state).Any();
- }
- public static bool HasKey(IEnumerable<PropertyModification> modifications, UnityEngine.Object target,
- WindowState state)
- {
- AnimationClip clip;
- double keyTime;
- bool inRange;
- if (!GetClipAndRelativeTime(target, state, out clip, out keyTime, out inRange))
- return false;
- return GetKeyTimes(target, modifications, state).Any(t => (CurveEditUtility.KeyCompare((float)state.editSequence.time, (float)t, clip.frameRate) == 0));
- }
- // Checks if a key already exists for this property
- static bool HasBinding(UnityEngine.Object target, PropertyModification modification, AnimationClip clip, out EditorCurveBinding binding)
- {
- var component = target as Component;
- var playableAsset = target as IPlayableAsset;
- if (component != null)
- {
- var type = AnimationUtility.PropertyModificationToEditorCurveBinding(modification, component.gameObject, out binding);
- binding = RotationCurveInterpolation.RemapAnimationBindingForRotationCurves(binding, clip);
- return type != null;
- }
- if (playableAsset != null)
- {
- binding = EditorCurveBinding.FloatCurve(string.Empty, target.GetType(),
- AnimatedParameterUtility.GetAnimatedParameterBindingName(target, modification.propertyPath));
- }
- else
- {
- binding = new EditorCurveBinding();
- return false;
- }
- return true;
- }
- public static void RemoveKey(UnityEngine.Object target, IEnumerable<PropertyModification> modifications,
- WindowState state)
- {
- AnimationClip clip;
- double keyTime;
- bool inRange;
- if (!GetClipAndRelativeTime(target, state, out clip, out keyTime, out inRange) || !inRange)
- return;
- var refreshPreview = false;
- TimelineUndo.PushUndo(clip, "Remove Key");
- foreach (var mod in modifications)
- {
- EditorCurveBinding temp;
- if (HasBinding(target, mod, clip, out temp))
- {
- if (temp.isPPtrCurve)
- {
- CurveEditUtility.RemoveObjectKey(clip, temp, keyTime);
- if (CurveEditUtility.GetObjectKeyCount(clip, temp) == 0)
- {
- refreshPreview = true;
- }
- }
- else
- {
- AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, temp);
- if (curve != null)
- {
- CurveEditUtility.RemoveKeyFrameFromCurve(curve, (float)keyTime, clip.frameRate);
- AnimationUtility.SetEditorCurve(clip, temp, curve);
- if (curve.length == 0)
- {
- AnimationUtility.SetEditorCurve(clip, temp, null);
- refreshPreview = true;
- }
- }
- }
- }
- }
- if (refreshPreview)
- {
- state.ResetPreviewMode();
- }
- }
- static HashSet<double> GetKeyTimes(UnityEngine.Object target, IEnumerable<PropertyModification> modifications, WindowState state)
- {
- var keyTimes = new HashSet<double>();
- AnimationClip animationClip;
- double keyTime;
- bool inRange;
- GetClipAndRelativeTime(target, state, out animationClip, out keyTime, out inRange);
- if (animationClip == null)
- return keyTimes;
- var component = target as Component;
- var playableAsset = target as IPlayableAsset;
- var info = AnimationClipCurveCache.Instance.GetCurveInfo(animationClip);
- TimelineClip clip = null;
- if (component != null)
- {
- GetTrackForGameObject(component.gameObject, state).FindRecordingClipAtTime(state.editSequence.time, out clip);
- }
- else if (playableAsset != null)
- {
- clip = FindClipWithAsset(state.editSequence.asset, playableAsset);
- }
- foreach (var mod in modifications)
- {
- EditorCurveBinding temp;
- if (HasBinding(target, mod, animationClip, out temp))
- {
- IEnumerable<double> keys = new HashSet<double>();
- if (temp.isPPtrCurve)
- {
- var curve = info.GetObjectCurveForBinding(temp);
- if (curve != null)
- {
- keys = curve.Select(x => (double)x.time);
- }
- }
- else
- {
- var curve = info.GetCurveForBinding(temp);
- if (curve != null)
- {
- keys = curve.keys.Select(x => (double)x.time);
- }
- }
- // Transform the times in to 'global' space using the clip
- if (clip != null)
- {
- foreach (var k in keys)
- {
- var time = clip.FromLocalTimeUnbound(k);
- const double eps = 1e-5;
- if (time >= clip.start - eps && time <= clip.end + eps)
- {
- keyTimes.Add(time);
- }
- }
- }
- // infinite clip mode, global == local space
- else
- {
- keyTimes.UnionWith(keys);
- }
- }
- }
- return keyTimes;
- }
- public static void NextKey(UnityEngine.Object target, IEnumerable<PropertyModification> modifications, WindowState state)
- {
- const double eps = 1e-5;
- var keyTimes = GetKeyTimes(target, modifications, state);
- if (keyTimes.Count == 0)
- return;
- var nextKeys = keyTimes.Where(x => x > state.editSequence.time + eps);
- if (nextKeys.Any())
- {
- state.editSequence.time = nextKeys.Min();
- }
- }
- public static void PrevKey(UnityEngine.Object target, IEnumerable<PropertyModification> modifications, WindowState state)
- {
- const double eps = 1e-5;
- var keyTimes = GetKeyTimes(target, modifications, state);
- if (keyTimes.Count == 0)
- return;
- var prevKeys = keyTimes.Where(x => x < state.editSequence.time - eps);
- if (prevKeys.Any())
- {
- state.editSequence.time = prevKeys.Max();
- }
- }
- public static void RemoveCurve(UnityEngine.Object target, IEnumerable<PropertyModification> modifications, WindowState state)
- {
- AnimationClip clip = null;
- double keyTime = 0;
- var inRange = false; // not used for curves
- if (!GetClipAndRelativeTime(target, state, out clip, out keyTime, out inRange))
- return;
- TimelineUndo.PushUndo(clip, "Remove Curve");
- foreach (var mod in modifications)
- {
- EditorCurveBinding temp;
- if (HasBinding(target, mod, clip, out temp))
- {
- if (temp.isPPtrCurve)
- AnimationUtility.SetObjectReferenceCurve(clip, temp, null);
- else
- AnimationUtility.SetEditorCurve(clip, temp, null);
- }
- }
- state.ResetPreviewMode();
- }
- public static IEnumerable<GameObject> GetRecordableGameObjects(WindowState state)
- {
- if (state == null || state.editSequence.asset == null || state.editSequence.director == null)
- yield break;
- var outputTracks = state.editSequence.asset.GetOutputTracks();
- foreach (var track in outputTracks)
- {
- if (track.GetType() != typeof(AnimationTrack))
- continue;
- if (!state.IsTrackRecordable(track) && !track.GetChildTracks().Any(state.IsTrackRecordable))
- continue;
- var obj = TimelineUtility.GetSceneGameObject(state.editSequence.director, track);
- if (obj != null)
- {
- yield return obj;
- }
- }
- }
- }
- }
|