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);
 | |
|         }
 | |
|     }
 | |
| }
 |