【UnityTimeline】Timeline内部的拷贝资产函数 / TimelineHelpers等

仅用于方便自己查看代码

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.Timeline;
using UnityEngine.Playables;
using Object = UnityEngine.Object;

namespace UnityEditor.Timeline
{
    static class TimelineHelpers
    {
        static List<Type> s_SubClassesOfTrackDrawer;

        static ScriptableObject CloneReferencedPlayableAsset(ScriptableObject original, PlayableDirector directorInstance)
        {
            var clone = Object.Instantiate(original);
            TimelineUndo.RegisterCreatedObjectUndo(clone, "Create clip");
            if (clone == null || (clone as IPlayableAsset) == null)
            {
                throw new InvalidCastException("could not cast instantiated object into IPlayableAsset");
            }

            if (directorInstance != null)
            {
                // Use serialize object to make a copy
                var originalObject = new SerializedObject(original);
                var cloneObject = new SerializedObject(clone);
                SerializedProperty prop = originalObject.GetIterator();
                if (prop.Next(true))
                {
                    do { cloneObject.CopyFromSerializedProperty(prop); } while (prop.Next(false));
                }
                cloneObject.ApplyModifiedProperties();

                EditorUtility.SetDirty(directorInstance);

                // The exposedName of exposed properties needs to be cleared otherwise the clone will act as an instance
                var exposedRefs = clone.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Where(f => f.FieldType.IsGenericType && f.FieldType.GetGenericTypeDefinition() == typeof(ExposedReference<>)).ToList();
                foreach (FieldInfo fi in exposedRefs)
                {
                    var exposedRefInstance = fi.GetValue(clone);
                    var exposedNameProperty = exposedRefInstance.GetType().GetField("exposedName");
                    if (exposedNameProperty != null)
                    {
                        var exposedNameValue = (PropertyName)exposedNameProperty.GetValue(exposedRefInstance);

                        bool isValid = false;
                        var originalReference = directorInstance.GetReferenceValue(exposedNameValue, out isValid);

                        if (isValid)
                        {
                            // generate new reference in directorInstance
                            var newPropertyName = new PropertyName(GUID.Generate().ToString());
                            directorInstance.SetReferenceValue(newPropertyName, originalReference);
                            exposedNameProperty.SetValue(exposedRefInstance, newPropertyName);

                            if (!EditorApplication.isPlaying)
                                EditorSceneManager.MarkSceneDirty(directorInstance.gameObject.scene);
                        }
                    }
                    fi.SetValue(clone, exposedRefInstance);
                }
            }

            return clone;
        }

        static void SaveCloneToAsset(Object clone, Object newOwner)
        {
            var containerPath = AssetDatabase.GetAssetPath(newOwner);
            var containerAsset = AssetDatabase.LoadAssetAtPath<Object>(containerPath);
            if (containerAsset != null)
            {
                TimelineCreateUtilities.SaveAssetIntoObject(clone, containerAsset);
                EditorUtility.SetDirty(containerAsset);
            }
        }

        static AnimationClip CloneAnimationClip(AnimationClip clip, Object owner)
        {
            if (clip == null)
                return null;

            var newClip = Object.Instantiate(clip);
            newClip.name = AnimationTrackRecorder.GetUniqueRecordedClipName(owner, clip.name);

            TimelineUndo.RegisterCreatedObjectUndo(newClip, "Create clip");
            SaveAnimClipIntoObject(newClip, owner);

            return newClip;
        }

        public static TimelineClip Clone(TimelineClip clip, PlayableDirector director, double time, PlayableAsset newOwner = null)
        {
            if (newOwner == null)
                newOwner = clip.parentTrack;

            TimelineClip newClip = DuplicateClip(clip, director, newOwner);
            newClip.start = time;
            var track = newClip.parentTrack;
            track.SortClips();
            TrackExtensions.ComputeBlendsFromOverlaps(track.clips);
            return newClip;
        }

        // Creates a complete clone of a track and returns it.
        // Does not parent, or add the track to the sequence
        public static TrackAsset Clone(PlayableAsset parent, TrackAsset trackAsset, PlayableDirector director, PlayableAsset assetOwner = null)
        {
            if (trackAsset == null)
                return null;

            var timelineAsset = trackAsset.timelineAsset;
            if (timelineAsset == null)
                return null;

            if (assetOwner == null)
                assetOwner = parent;

            // create a duplicate, then clear the clips and subtracks
            var newTrack = Object.Instantiate(trackAsset);
            newTrack.name = trackAsset.name;
            newTrack.ClearClipsInternal();
            newTrack.parent = parent;
            newTrack.ClearSubTracksInternal();

            if (trackAsset.hasCurves)
                newTrack.curves = CloneAnimationClip(trackAsset.curves, assetOwner);

            var animTrack = trackAsset as AnimationTrack;
            if (animTrack != null && animTrack.infiniteClip != null)
                ((AnimationTrack)newTrack).infiniteClip = CloneAnimationClip(animTrack.infiniteClip, assetOwner);

            foreach (var clip in trackAsset.clips)
            {
                var newClip = DuplicateClip(clip, director, assetOwner);
                newClip.parentTrack = newTrack;
            }

            newTrack.ClearMarkers();
            foreach (var e in trackAsset.GetMarkersRaw())
            {
                var newMarker = Object.Instantiate(e);
                newTrack.AddMarker(newMarker);
                SaveCloneToAsset(newMarker, assetOwner);
                if (newMarker is IMarker)
                {
                    (newMarker as IMarker).Initialize(newTrack);
                }
            }

            newTrack.SetCollapsed(trackAsset.GetCollapsed());

            // calling code is responsible for adding to asset, adding to sequence, and parenting,
            // and duplicating subtracks
            return newTrack;
        }

        public static IEnumerable<ITimelineItem> DuplicateItemsUsingCurrentEditMode(WindowState state, ItemsPerTrack items, TrackAsset targetParent, double candidateTime, string undoOperation)
        {
            if (targetParent != null)
            {
                var aTrack = targetParent as AnimationTrack;
                if (aTrack != null)
                    aTrack.ConvertToClipMode();

                var duplicatedItems = DuplicateItems(items, targetParent, state, undoOperation);
                FinalizeInsertItemsUsingCurrentEditMode(state, new[] {duplicatedItems}, candidateTime);
                return duplicatedItems.items;
            }

            return Enumerable.Empty<ITimelineItem>();
        }

        public static IEnumerable<ITimelineItem> DuplicateItemsUsingCurrentEditMode(WindowState state, IEnumerable<ItemsPerTrack> items, double candidateTime, string undoOperation)
        {
            var duplicatedItemsGroups = new List<ItemsPerTrack>();
            foreach (var i in items)
                duplicatedItemsGroups.Add(DuplicateItems(i, i.targetTrack, state, undoOperation));

            FinalizeInsertItemsUsingCurrentEditMode(state, duplicatedItemsGroups, candidateTime);
            return duplicatedItemsGroups.SelectMany(i => i.items);
        }

        internal static ItemsPerTrack DuplicateItems(ItemsPerTrack items, TrackAsset target, WindowState state, string undoOperation)
        {
            var duplicatedItems = new List<ITimelineItem>();

            PlayableDirector director = null;
            if (state != null)
                director = state.editSequence.director;

            var clips = items.clips.ToList();
            if (clips.Any())
            {
                TimelineUndo.PushUndo(target, undoOperation);
                duplicatedItems.AddRange(DuplicateClips(clips, director, target).ToItems());
                TimelineUndo.PushUndo(target, undoOperation); // second undo causes reference fixups on redo (case 1063868)
            }

            var markers = items.markers.ToList();
            if (markers.Any())
            {
                duplicatedItems.AddRange(MarkerModifier.CloneMarkersToParent(markers, target).ToItems());
            }

            return new ItemsPerTrack(target, duplicatedItems.ToArray());
        }

        static void FinalizeInsertItemsUsingCurrentEditMode(WindowState state, IList<ItemsPerTrack> itemsGroups, double candidateTime)
        {
            EditMode.FinalizeInsertItemsAtTime(itemsGroups, candidateTime);

            SelectionManager.Clear();
            foreach (var itemsGroup in itemsGroups)
            {
                var track = itemsGroup.targetTrack;
                var items = itemsGroup.items;

                EditModeUtils.SetParentTrack(items, track);

                track.SortClips();

                TrackExtensions.ComputeBlendsFromOverlaps(track.clips);
                track.CalculateExtrapolationTimes();

                foreach (var item in items)
                    if (item.gui != null) item.gui.Select();
            }

            var allItems = itemsGroups.SelectMany(x => x.items).ToList();
            foreach (var item in allItems)
            {
                SelectionManager.Add(item);
            }

            FrameItems(state, allItems);
        }

        internal static TimelineClip Clone(TimelineClip clip, PlayableDirector director, PlayableAsset newOwner)
        {
            var editorClip = EditorClipFactory.GetEditorClip(clip);
            // Workaround for Clips not being unity object, assign it to a editor clip wrapper, clone it, and pull the clip back out
            var newClip = Object.Instantiate(editorClip).clip;

            // perform fix ups for what Instantiate cannot properly detect
            SelectionManager.Remove(newClip);
            newClip.parentTrack = null;
            newClip.curves = null; // instantiate might copy the reference, we need to clear it

            // curves are explicitly owned by the clip
            if (clip.curves != null)
            {
                newClip.CreateCurves(AnimationTrackRecorder.GetUniqueRecordedClipName(newOwner, clip.curves.name));
                EditorUtility.CopySerialized(clip.curves, newClip.curves);
                TimelineCreateUtilities.SaveAssetIntoObject(newClip.curves, newOwner);
            }

            ScriptableObject playableAsset = newClip.asset as ScriptableObject;
            if (playableAsset != null && newClip.asset is IPlayableAsset)
            {
                var clone = CloneReferencedPlayableAsset(playableAsset, director);
                SaveCloneToAsset(clone, newOwner);

                newClip.asset = clone;

                // special case to make the name match the recordable clips, but only if they match on the original clip
                var originalRecordedAsset = clip.asset as AnimationPlayableAsset;
                if (clip.recordable && originalRecordedAsset != null && originalRecordedAsset.clip != null)
                {
                    AnimationPlayableAsset clonedAnimationAsset = clone as AnimationPlayableAsset;
                    if (clonedAnimationAsset != null && clonedAnimationAsset.clip != null)
                    {
                        clonedAnimationAsset.clip = CloneAnimationClip(originalRecordedAsset.clip, newOwner);
                        if (clip.displayName == originalRecordedAsset.clip.name && newClip.recordable)
                        {
                            clonedAnimationAsset.name = clonedAnimationAsset.clip.name;
                            newClip.displayName = clonedAnimationAsset.name;
                        }
                    }
                }
            }

            return newClip;
        }

        static TimelineClip[] DuplicateClips(IEnumerable<TimelineClip> clips, PlayableDirector playableDirector, PlayableAsset newOwner)
        {
            var newClips = new TimelineClip[clips.Count()];

            int i = 0;

            foreach (var clip in clips)
            {
                var newParent = newOwner == null ? clip.parentTrack : newOwner;
                var newClip = DuplicateClip(clip, playableDirector, newParent);
                newClip.parentTrack = null;
                newClips[i++] = newClip;
            }

            return newClips;
        }

        static TimelineClip DuplicateClip(TimelineClip clip, PlayableDirector director, PlayableAsset newOwner)
        {
            var newClip = Clone(clip, director, newOwner);

            var track = clip.parentTrack;
            if (track != null)
            {
                newClip.parentTrack = track;
                track.AddClip(newClip);
            }

            var editor = CustomTimelineEditorCache.GetClipEditor(clip);
            try
            {
                editor.OnCreate(newClip, track, clip);
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }

            return newClip;
        }

        // Given a track type, return all the playable asset types that should be
        //  visible to the user via menus

        // Given a track type, return all the playable asset types


        public static Type GetCustomDrawer(Type trackType)
        {
            if (s_SubClassesOfTrackDrawer == null)
            {
                s_SubClassesOfTrackDrawer = EditorAssemblies.SubclassesOf(typeof(TrackDrawer)).ToList();
            }

            foreach (var drawer in s_SubClassesOfTrackDrawer)
            {
                var attr = Attribute.GetCustomAttribute(drawer, typeof(CustomTrackDrawerAttribute), false) as CustomTrackDrawerAttribute;
                if (attr != null && attr.assetType.IsAssignableFrom(trackType))
                    return drawer;
            }

            return typeof(TrackDrawer);
        }

        public static bool HaveSameContainerAsset(Object assetA, Object assetB)
        {
            if (assetA == null || assetB == null)
                return false;

            if ((assetA.hideFlags & HideFlags.DontSave) != 0  && (assetB.hideFlags & HideFlags.DontSave) != 0)
                return true;

            return AssetDatabase.GetAssetPath(assetA) == AssetDatabase.GetAssetPath(assetB);
        }

        public static void SaveAnimClipIntoObject(AnimationClip clip, Object asset)
        {
            if (asset != null)
            {
                clip.hideFlags = asset.hideFlags & ~HideFlags.HideInHierarchy; // show animation clips, even if the parent track isn't
                if ((clip.hideFlags & HideFlags.DontSave) == 0)
                {
                    AssetDatabase.AddObjectToAsset(clip, asset);
                }
            }
        }

        // Make sure a gameobject has all the required component for the given TrackAsset
        public static Component AddRequiredComponent(GameObject go, TrackAsset asset)
        {
            if (go == null || asset == null)
                return null;

            var bindings = asset.outputs;
            if (!bindings.Any())
                return null;

            var binding = bindings.First();
            if (binding.outputTargetType == null || !typeof(Component).IsAssignableFrom(binding.outputTargetType))
                return null;

            var component = go.GetComponent(binding.outputTargetType);
            if (component == null)
            {
                component = Undo.AddComponent(go, binding.outputTargetType);
            }
            return component;
        }

        public static string GetTrackCategoryName(System.Type trackType)
        {
            if (trackType == null || trackType.Namespace == null)
                return string.Empty;
            if (trackType.Namespace.Contains("UnityEngine"))
                return string.Empty;
            return trackType.Namespace;
        }

        public static string GetTrackMenuName(System.Type trackType)
        {
            return ObjectNames.NicifyVariableName(trackType.Name);
        }

        // retrieve the duration of a single loop, taking into account speed
        public static double GetLoopDuration(TimelineClip clip)
        {
            double length = clip.clipAssetDuration;
            if (double.IsNegativeInfinity(length) || double.IsNaN(length))
                return TimelineClip.kMinDuration;

            if (length == double.MaxValue || double.IsInfinity(length))
            {
                return double.MaxValue;
            }

            return Math.Max(TimelineClip.kMinDuration, length / clip.timeScale);
        }

        public static double GetClipAssetEndTime(TimelineClip clip)
        {
            var d = GetLoopDuration(clip);
            if (d < double.MaxValue)
                d = clip.FromLocalTimeUnbound(d);

            return d;
        }

        // Checks if the underlying asset duration is usable. This means the clip
        //  can loop or hold
        public static bool HasUsableAssetDuration(TimelineClip clip)
        {
            double length = clip.clipAssetDuration;
            return (length < TimelineClip.kMaxTimeValue) && !double.IsInfinity(length) && !double.IsNaN(length);
        }

        // Retrieves the starting point of each loop of a clip, relative to the start of the clip
        //  Note that if clip-in is bigger than the loopDuration, negative times will be added
        public static double[] GetLoopTimes(TimelineClip clip)
        {
            if (!HasUsableAssetDuration(clip))
                return new[] {-clip.clipIn / clip.timeScale};

            var times = new List<double>();
            double loopDuration = GetLoopDuration(clip);

            if (loopDuration <= TimeUtility.kTimeEpsilon)
                return new double[] {};


            double start = -clip.clipIn / clip.timeScale;
            double end = start + loopDuration;

            times.Add(start);
            while (end < clip.duration - WindowState.kTimeEpsilon)
            {
                times.Add(end);
                end += loopDuration;
            }

            return times.ToArray();
        }

        public static double GetCandidateTime(WindowState state, Vector2? mousePosition, params TrackAsset[] trackAssets)
        {
            // Right-Click
            if (mousePosition != null)
                return state.GetSnappedTimeAtMousePosition(mousePosition.Value);

            // Playhead
            if (state != null && state.editSequence.director != null)
                return state.SnapToFrameIfRequired(state.editSequence.time);

            // Specific tracks end
            if (trackAssets != null && trackAssets.Any())
            {
                var items = trackAssets.SelectMany(t => t.GetItems()).ToList();
                return items.Any() ? items.Max(i => i.end) : 0;
            }

            // Timeline tracks end
            if (state != null && state.editSequence.asset != null)
                return state.editSequence.asset.flattenedTracks.Any() ? state.editSequence.asset.flattenedTracks.Max(t => t.end) : 0;

            return 0.0;
        }

        public static TimelineClip CreateClipOnTrack(Object asset, TrackAsset parentTrack, WindowState state)
        {
            return CreateClipOnTrack(asset, parentTrack, GetCandidateTime(state, null, parentTrack), state);
        }

        public static TimelineClip CreateClipOnTrack(Object asset, TrackAsset parentTrack, double candidateTime)
        {
            WindowState state = null;
            if (TimelineWindow.instance != null)
                state = TimelineWindow.instance.state;

            return CreateClipOnTrack(asset, parentTrack, candidateTime, state);
        }

        public static TimelineClip CreateClipOnTrack(Type playableAssetType, TrackAsset parentTrack, WindowState state)
        {
            return CreateClipOnTrack(playableAssetType, null, parentTrack, GetCandidateTime(state, null, parentTrack), state);
        }

        public static TimelineClip CreateClipOnTrack(Type playableAssetType, TrackAsset parentTrack, double candidateTime)
        {
            return CreateClipOnTrack(playableAssetType, null, parentTrack, candidateTime);
        }

        public static TimelineClip CreateClipOnTrack(Object asset, TrackAsset parentTrack, double candidateTime, WindowState state)
        {
            if (parentTrack == null)
                return null;

            // pick the first clip type available, unless there is one that matches the asset
            var clipType = TypeUtility.GetPlayableAssetsHandledByTrack(parentTrack.GetType()).FirstOrDefault();
            if (asset != null)
                clipType = TypeUtility.GetAssetTypesForObject(parentTrack.GetType(), asset).FirstOrDefault();

            if (clipType == null)
                return null;

            return CreateClipOnTrack(clipType, asset, parentTrack, candidateTime, state);
        }

        public static TimelineClip CreateClipOnTrack(Type playableAssetType, Object assignableObject, TrackAsset parentTrack, double candidateTime)
        {
            WindowState state = null;
            if (TimelineWindow.instance != null)
                state = TimelineWindow.instance.state;

            return CreateClipOnTrack(playableAssetType, assignableObject, parentTrack, candidateTime, state);
        }

        public static TimelineClip CreateClipOnTrack(Type playableAssetType, Object assignableObject, TrackAsset parentTrack, double candidateTime, WindowState state)
        {
            if (parentTrack == null)
                return null;

            bool revertClipMode = false;

            // Ideally this is done automatically by the animation track,
            // but it's editor only because it does animation clip manipulation
            var animTrack = parentTrack as AnimationTrack;
            if (animTrack != null && animTrack.CanConvertToClipMode())
            {
                animTrack.ConvertToClipMode();
                revertClipMode = true;
            }

            TimelineClip newClip = null;
            if (TypeUtility.IsConcretePlayableAsset(playableAssetType))
            {
                try
                {
                    newClip = parentTrack.CreateClipOfType(playableAssetType);
                }
                catch (InvalidOperationException) {}    // expected on a mismatch
            }

            if (newClip == null)
            {
                if (revertClipMode)
                    animTrack.ConvertFromClipMode(animTrack.timelineAsset);

                Debug.LogWarningFormat("Cannot create a clip of type {0} on a track of type {1}", playableAssetType.Name, parentTrack.GetType().Name);
                return null;
            }

            AddClipOnTrack(newClip, parentTrack, candidateTime, assignableObject, state);

            return newClip;
        }

        /// <summary>
        /// Create a clip on track from an existing PlayableAsset
        /// </summary>
        public static TimelineClip CreateClipOnTrackFromPlayableAsset(IPlayableAsset asset, TrackAsset parentTrack, double candidateTime)
        {
            if (parentTrack == null || asset == null || !TypeUtility.IsConcretePlayableAsset(asset.GetType()))
                return null;

            TimelineClip newClip = null;
            try
            {
                newClip = parentTrack.CreateClipFromPlayableAsset(asset);
            }
            catch
            {
                return null;
            }

            WindowState state = null;
            if (TimelineWindow.instance != null)
                state = TimelineWindow.instance.state;

            AddClipOnTrack(newClip, parentTrack, candidateTime, null, state);

            return newClip;
        }

        public static void CreateClipsFromObjects(Type assetType, TrackAsset targetTrack, double candidateTime, IEnumerable<Object> objects)
        {
            foreach (var obj in objects)
            {
                if (ObjectReferenceField.FindObjectReferences(assetType).Any(f => f.IsAssignable(obj)))
                {
                    var clip = CreateClipOnTrack(assetType, obj, targetTrack, candidateTime);
                    candidateTime += clip.duration;
                }
            }
        }

        public static void CreateMarkersFromObjects(Type assetType, TrackAsset targetTrack, double candidateTime, IEnumerable<Object> objects)
        {
            var mList = new List<ITimelineItem>();
            foreach (var obj in objects)
            {
                if (ObjectReferenceField.FindObjectReferences(assetType).Any(f => f.IsAssignable(obj)))
                {
                    var marker = CreateMarkerOnTrack(assetType, obj, targetTrack, candidateTime);
                    mList.Add(marker.ToItem());
                }
            }

            var state = TimelineWindow.instance.state;
            for (var i = 1; i < mList.Count; ++i)
            {
                var delta = ItemsUtils.TimeGapBetweenItems(mList[i - 1], mList[i], state);
                mList[i].start += delta;
            }

            FinalizeInsertItemsUsingCurrentEditMode(state, new[] {new ItemsPerTrack(targetTrack, mList)}, candidateTime);
            state.Refresh();
        }

        public static IMarker CreateMarkerOnTrack(Type markerType, Object assignableObject, TrackAsset parentTrack, double candidateTime)
        {
            WindowState state = null;
            if (TimelineWindow.instance != null)
                state = TimelineWindow.instance.state;

            var newMarker = parentTrack.CreateMarker(markerType, candidateTime); //Throws if marker is not an object

            if (assignableObject != null)
            {
                var director = state != null ? state.editSequence.director : null;
                foreach (var field in ObjectReferenceField.FindObjectReferences(markerType))
                {
                    if (field.IsAssignable(assignableObject))
                    {
                        field.Assign(newMarker as ScriptableObject, assignableObject, director);
                        break;
                    }
                }
            }

            try
            {
                CustomTimelineEditorCache.GetMarkerEditor(newMarker).OnCreate(newMarker, null);
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }

            return newMarker;
        }

        public static void CreateClipsFromTypes(IEnumerable<Type> assetTypes, TrackAsset targetTrack, double candidateTime)
        {
            foreach (var assetType in assetTypes)
            {
                var clip = CreateClipOnTrack(assetType, targetTrack, candidateTime);
                candidateTime += clip.duration;
            }
        }

        public static void FrameItems(WindowState state, IEnumerable<ITimelineItem> items)
        {
            if (items == null || !items.Any() || state == null)
                return;

            // if this is called before a repaint, the timeArea can be null
            var window = state.editorWindow as TimelineWindow;
            if (window == null || window.timeArea == null)
                return;

            var start = (float)items.Min(x => x.start);
            var end = (float)items.Max(x => x.end);
            var timeRange = state.timeAreaShownRange;

            // nothing to do
            if (timeRange.x <= start && timeRange.y >= end)
                return;

            var ds = start - timeRange.x;
            var de = end - timeRange.y;

            var padding = state.PixelDeltaToDeltaTime(15);
            var d = Math.Abs(ds) < Math.Abs(de) ? ds - padding : de + padding;

            state.SetTimeAreaShownRange(timeRange.x + d, timeRange.y + d);
        }

        public static void Frame(WindowState state, double start, double end)
        {
            var timeRange = state.timeAreaShownRange;

            // nothing to do
            if (timeRange.x <= start && timeRange.y >= end)
                return;

            var ds = (float)start - timeRange.x;
            var de = (float)end - timeRange.y;

            var padding = state.PixelDeltaToDeltaTime(15);
            var d = Math.Abs(ds) < Math.Abs(de) ? ds - padding : de + padding;

            state.SetTimeAreaShownRange(timeRange.x + d, timeRange.y + d);
        }

        public static void RangeSelect<T>(IList<T> totalCollection, IList<T> currentSelection, T clickedItem, Action<T> selector, Action<T> remover) where T : class
        {
            var firstSelect = currentSelection.FirstOrDefault();
            if (firstSelect == null)
            {
                selector(clickedItem);
                return;
            }

            var idxFirstSelect = totalCollection.IndexOf(firstSelect);
            var idxLastSelect = totalCollection.IndexOf(currentSelection.Last());
            var idxClicked = totalCollection.IndexOf(clickedItem);

            //case 927807: selection is invalid
            if (idxFirstSelect < 0)
            {
                SelectionManager.Clear();
                selector(clickedItem);
                return;
            }

            // Expand the selection between the first selected clip and clicked clip (insertion order is important)
            if (idxFirstSelect < idxClicked)
                for (var i = idxFirstSelect; i <= idxClicked; ++i)
                    selector(totalCollection[i]);
            else
                for (var i = idxFirstSelect; i >= idxClicked; --i)
                    selector(totalCollection[i]);

            // If clicked inside the selected range, shrink the selection between the the click and last selected clip
            if (Math.Min(idxFirstSelect, idxLastSelect) < idxClicked && idxClicked < Math.Max(idxFirstSelect, idxLastSelect))
                for (var i = Math.Min(idxLastSelect, idxClicked); i <= Math.Max(idxLastSelect, idxClicked); ++i)
                    remover(totalCollection[i]);

            // Ensure clicked clip is selected
            selector(clickedItem);
        }

        public static void Bind(TrackAsset track, Object obj, PlayableDirector director)
        {
            if (director != null && track != null)
            {
                var bindType = TypeUtility.GetTrackBindingAttribute(track.GetType());
                if (bindType == null || bindType.type == null)
                    return;

                if (obj == null || bindType.type.IsInstanceOfType(obj))
                {
                    TimelineUndo.PushUndo(director, "Bind Track");
                    director.SetGenericBinding(track, obj);
                }
                else if (obj is GameObject && typeof(Component).IsAssignableFrom(bindType.type))
                {
                    var component = (obj as GameObject).GetComponent(bindType.type);
                    if (component == null)
                        component = Undo.AddComponent(obj as GameObject, bindType.type);

                    TimelineUndo.PushUndo(director, "Bind Track");
                    director.SetGenericBinding(track, component);
                }
            }
        }

        /// <summary>
        /// Shared code for adding a clip to a track
        /// </summary>
       public  static void AddClipOnTrack(TimelineClip newClip, TrackAsset parentTrack, double candidateTime, Object assignableObject, WindowState state)
        {
            var playableAsset = newClip.asset as IPlayableAsset;

            newClip.parentTrack = null;
            newClip.timeScale = 1.0;
            newClip.mixInCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
            newClip.mixOutCurve = AnimationCurve.EaseInOut(0, 1, 1, 0);

            var playableDirector = state != null ? state.editSequence.director : null;

            if (assignableObject != null)
            {
                foreach (var field in ObjectReferenceField.FindObjectReferences(playableAsset.GetType()))
                {
                    if (field.IsAssignable(assignableObject))
                    {
                        newClip.displayName = assignableObject.name;
                        field.Assign(newClip.asset as PlayableAsset, assignableObject, playableDirector);
                        break;
                    }
                }
            }

            // get the clip editor
            try
            {
                CustomTimelineEditorCache.GetClipEditor(newClip).OnCreate(newClip, parentTrack, null);
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }


            // reset the duration as the newly assigned values may have changed the default
            if (playableAsset != null)
            {
                var candidateDuration = playableAsset.duration;

                if (!double.IsInfinity(candidateDuration) && candidateDuration > 0)
                    newClip.duration = Math.Min(Math.Max(candidateDuration, TimelineClip.kMinDuration), TimelineClip.kMaxTimeValue);
            }

            var newClipsByTracks = new[] { new ItemsPerTrack(parentTrack, new[] {newClip.ToItem()}) };

            FinalizeInsertItemsUsingCurrentEditMode(state, newClipsByTracks, candidateTime);

            if (state != null)
                state.Refresh();
        }

        public static TrackAsset CreateTrack(TimelineAsset asset, Type type, TrackAsset parent = null, string name = null)
        {
            if (asset == null)
                return null;

            var track = asset.CreateTrack(type, parent, name);
            if (track != null)
            {
                if (parent != null)
                    parent.SetCollapsed(false);

                var editor = CustomTimelineEditorCache.GetTrackEditor(track);
                try
                {
                    editor.OnCreate(track, null);
                }
                catch (Exception e)
                {
                    Debug.LogException(e);
                }
                TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
            }

            return track;
        }

        public static TrackAsset CreateTrack(Type type, TrackAsset parent = null, string name = null)
        {
            return CreateTrack(TimelineEditor.inspectedAsset, type, parent, name);
        }

        public static T CreateTrack<T>(TimelineAsset asset, TrackAsset parent = null, string name = null) where T : TrackAsset
        {
            return (T)CreateTrack(asset, typeof(T), parent, name);
        }

        public static T CreateTrack<T>(TrackAsset parent = null, string name = null) where T : TrackAsset
        {
            return (T)CreateTrack(TimelineEditor.inspectedAsset, typeof(T), parent, name);
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容