303 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			303 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | 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; | ||
|  |         } | ||
|  | 
 | ||
|  |         // This is called when performing Undo operations. | ||
|  |         // It needs to be called here since some operations are not | ||
|  |         // allowed (EditorUtility.InstanceIDToObject, for example) | ||
|  |         // during the ISerializationCallbackReceiver methods. | ||
|  |         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; | ||
|  | 
 | ||
|  |             EditorApplication.delayCall += () => { m_WindowState.SetCurrentSequencePath(m_SerializedPath, true); }; | ||
|  |         } | ||
|  | 
 | ||
|  |         public void Add(TimelineAsset asset, PlayableDirector director, TimelineClip hostClip) | ||
|  |         { | ||
|  |             if (hostClip == null) | ||
|  |                 AddToCurrentUndoGroup(this); // Merge with selection undo | ||
|  |             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 = SelectionUtility.IdToObject(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); | ||
|  |         } | ||
|  |     } | ||
|  | } |