| using System.Collections.Generic; |
| using System.Linq; |
| using UnityEngine; |
| using UnityEngine.Playables; |
| using UnityEngine.Timeline; |
|
|
| namespace UnityEditor.Timeline |
| { |
| class SequenceHierarchy : ScriptableObject |
| { |
| readonly List<ISequenceState> m_Sequences = new List<ISequenceState>(); |
|
|
| WindowState m_WindowState; |
|
|
| [SerializeField] |
| SequencePath m_SerializedPath; |
|
|
| public ISequenceState masterSequence |
| { |
| get { return m_Sequences.FirstOrDefault(); } |
| } |
|
|
| public ISequenceState editSequence |
| { |
| get { return m_Sequences.LastOrDefault(); } |
| } |
|
|
| public int count |
| { |
| get { return m_Sequences.Count; } |
| } |
|
|
| public IEnumerable<ISequenceState> allSequences |
| { |
| get { return m_Sequences; } |
| } |
|
|
| public static SequenceHierarchy CreateInstance() |
| { |
| var hierarchy = ScriptableObject.CreateInstance<SequenceHierarchy>(); |
| hierarchy.hideFlags = HideFlags.HideAndDontSave; |
| return hierarchy; |
| } |
|
|
| public void Init(WindowState owner) |
| { |
| m_WindowState = owner; |
| } |
|
|
| |
| |
| |
| |
| void OnValidate() |
| { |
| if (m_SerializedPath == null || m_WindowState == null || m_WindowState.GetWindow() == null) |
| return; |
|
|
| bool hasDifferentRoot = m_WindowState.GetCurrentSequencePath().selectionRoot != m_SerializedPath.selectionRoot; |
| if (m_WindowState.GetWindow().locked && hasDifferentRoot) |
| return; |
| m_WindowState.SetCurrentSequencePath(m_SerializedPath, true); |
| } |
|
|
| public void Add(TimelineAsset asset, PlayableDirector director, TimelineClip hostClip) |
| { |
| if (hostClip == null) |
| AddToCurrentUndoGroup(this); |
| else |
| TimelineUndo.PushUndo(this, L10n.Tr("Edit Sub-Timeline")); |
|
|
| Add_Internal(asset, director, hostClip); |
|
|
| UpdateSerializedPath(); |
| } |
|
|
| public void Remove() |
| { |
| if (m_Sequences.Count == 0) return; |
|
|
| TimelineUndo.PushUndo(this, L10n.Tr("Go to Sub-Timeline")); |
|
|
| Remove_Internal(); |
|
|
| UpdateSerializedPath(); |
| } |
|
|
| public ISequenceState GetStateAtIndex(int index) |
| { |
| return m_Sequences[index]; |
| } |
|
|
| public void RemoveUntilCount(int expectedCount) |
| { |
| if (expectedCount < 0 || m_Sequences.Count <= expectedCount) return; |
|
|
| TimelineUndo.PushUndo(this, L10n.Tr("Go to Sub-Timeline")); |
|
|
| RemoveUntilCount_Internal(expectedCount); |
|
|
| UpdateSerializedPath(); |
| } |
|
|
| public void Clear() |
| { |
| if (m_Sequences.Count == 0) return; |
|
|
| AddToCurrentUndoGroup(this); |
| Clear_Internal(); |
| UpdateSerializedPath(); |
| } |
|
|
| public SequencePath ToSequencePath() |
| { |
| var path = new SequencePath(); |
|
|
| if (m_Sequences.Count == 0) |
| return path; |
|
|
| var rootSequence = m_Sequences[0]; |
| var root = 0; |
| if (rootSequence.director != null && rootSequence.director.gameObject != null) |
| root = rootSequence.director.gameObject.GetInstanceID(); |
| else if (rootSequence.asset != null) |
| root = rootSequence.asset.GetInstanceID(); |
|
|
| path.SetSelectionRoot(root); |
|
|
| var resolver = rootSequence.director; |
|
|
| if (m_Sequences.Count > 1) |
| { |
| for (int i = 1, n = m_Sequences.Count; i < n; ++i) |
| { |
| path.AddSubSequence(m_Sequences[i], resolver); |
| resolver = m_Sequences[i].director; |
| } |
| } |
|
|
| return path; |
| } |
|
|
| public bool NeedsUpdate(SequencePath path, bool forceRebuild) |
| { |
| return forceRebuild || !SequencePath.AreEqual(m_SerializedPath, path); |
| } |
|
|
| public void FromSequencePath(SequencePath path, bool forceRebuild) |
| { |
| if (!NeedsUpdate(path, forceRebuild)) |
| return; |
|
|
| Clear_Internal(); |
|
|
| var rootObject = EditorUtility.InstanceIDToObject(path.selectionRoot); |
| if (rootObject == null) |
| { |
| UpdateSerializedPath(); |
| return; |
| } |
|
|
| var candidateAsset = rootObject as TimelineAsset; |
| if (candidateAsset != null) |
| { |
| Add_Internal(candidateAsset, null, null); |
| UpdateSerializedPath(); |
| return; |
| } |
|
|
| var candidateGameObject = rootObject as GameObject; |
| if (candidateGameObject == null) |
| { |
| UpdateSerializedPath(); |
| return; |
| } |
|
|
| var director = TimelineUtility.GetDirectorComponentForGameObject(candidateGameObject); |
| var asset = TimelineUtility.GetTimelineAssetForDirectorComponent(director); |
| Add_Internal(asset, director, null); |
|
|
| if (!path.subElements.Any()) |
| { |
| UpdateSerializedPath(); |
| return; |
| } |
|
|
| List<SequenceBuildingBlock> buildingBlocks; |
| if (ValidateSubElements(path.subElements, director, out buildingBlocks)) |
| { |
| foreach (var buildingBlock in buildingBlocks) |
| Add_Internal(buildingBlock.asset, buildingBlock.director, buildingBlock.hostClip); |
| } |
|
|
| UpdateSerializedPath(); |
| } |
|
|
| void Add_Internal(TimelineAsset asset, PlayableDirector director, TimelineClip hostClip) |
| { |
| if (hostClip == null) |
| Clear_Internal(); |
|
|
| var parent = m_Sequences.Count > 0 ? editSequence : null; |
| m_Sequences.Add(new SequenceState(m_WindowState, asset, director, hostClip, (SequenceState)parent)); |
| } |
|
|
| void Remove_Internal() |
| { |
| m_Sequences.Last().Dispose(); |
| m_Sequences.RemoveAt(m_Sequences.Count - 1); |
| } |
|
|
| void RemoveUntilCount_Internal(int expectedCount) |
| { |
| while (m_Sequences.Count > expectedCount) |
| { |
| Remove_Internal(); |
| } |
| } |
|
|
| void Clear_Internal() |
| { |
| RemoveUntilCount_Internal(0); |
| } |
|
|
| void UpdateSerializedPath() |
| { |
| m_SerializedPath = ToSequencePath(); |
| } |
|
|
| static bool ValidateSubElements(List<SequencePathSubElement> subElements, PlayableDirector director, out List<SequenceBuildingBlock> buildingBlocks) |
| { |
| buildingBlocks = new List<SequenceBuildingBlock>(subElements.Count); |
| var currentDirector = director; |
|
|
| foreach (var element in subElements) |
| { |
| var timeline = currentDirector.playableAsset as TimelineAsset; |
| if (timeline == null) |
| return false; |
| if (timeline.trackObjects == null) |
| return false; |
|
|
| var track = timeline.GetOutputTracks().FirstOrDefault(t => t.GetInstanceID() == element.trackInstanceID); |
| if (track == null) |
| return false; |
| if (track.Hash() != element.trackHash) |
| return false; |
| if (track.clips == null) |
| return false; |
| if (track.clips.Length <= element.clipIndex) |
| return false; |
|
|
| var clip = track.clips[element.clipIndex]; |
| if (clip == null) |
| return false; |
| if (clip.Hash() != element.clipHash) |
| return false; |
|
|
| var candidateDirectors = TimelineUtility.GetSubTimelines(clip, director); |
|
|
| if (element.subDirectorIndex < 0 || element.subDirectorIndex >= candidateDirectors.Count) |
| return false; |
|
|
| var candidateDirector = candidateDirectors[element.subDirectorIndex]; |
|
|
| if (candidateDirector == null || !(candidateDirector.playableAsset is TimelineAsset)) |
| return false; |
|
|
| currentDirector = candidateDirector; |
|
|
| buildingBlocks.Add( |
| new SequenceBuildingBlock |
| { |
| asset = currentDirector.playableAsset as TimelineAsset, |
| director = currentDirector, |
| hostClip = clip |
| }); |
| } |
|
|
| return true; |
| } |
|
|
| struct SequenceBuildingBlock |
| { |
| public TimelineAsset asset; |
| public PlayableDirector director; |
| public TimelineClip hostClip; |
| } |
|
|
| static void AddToCurrentUndoGroup(Object target) |
| { |
| if (target == null) return; |
|
|
| var group = Undo.GetCurrentGroup(); |
| var groupName = Undo.GetCurrentGroupName(); |
| EditorUtility.SetDirty(target); |
| Undo.RegisterCompleteObjectUndo(target, groupName); |
| Undo.CollapseUndoOperations(group); |
| } |
| } |
| } |
|
|