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