You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
301 lines
9.3 KiB
301 lines
9.3 KiB
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; |
|
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 = 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); |
|
} |
|
} |
|
}
|
|
|