Unity-jump-proj
This commit is contained in:
@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
interface ISequenceState : IDisposable
|
||||
{
|
||||
TimelineAsset asset { get; }
|
||||
PlayableDirector director { get; }
|
||||
TimelineClip hostClip { get; }
|
||||
List<UnityEngine.Object> cachedChildAssets { get; }
|
||||
double start { get; }
|
||||
double timeScale { get; }
|
||||
double duration { get; }
|
||||
bool isReadOnly { get; }
|
||||
bool isAssetOnly { get; set; }
|
||||
TimelineAssetViewModel viewModel { get; }
|
||||
double time { get; set; }
|
||||
int frame { get; set; }
|
||||
double frameRate { get; set; }
|
||||
|
||||
Range GetEvaluableRange();
|
||||
double ToGlobalTime(double t);
|
||||
double ToLocalTime(double t);
|
||||
void ResetIsReadOnly();
|
||||
void InvalidateChildAssetCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used to hold default values for an implementation of ISequenceState.
|
||||
* It could be removed in a phase 2, but it is currently used to limit the scope of
|
||||
* this refactoring: it allows client code to access sequence info without having to
|
||||
* worry about `currentSequence` being null.
|
||||
* In the future this should be removed and we should pass around the correct data
|
||||
* structure (i.e. SequenceState OR WindowState) based on the situation.
|
||||
*/
|
||||
class NullSequenceState : ISequenceState
|
||||
{
|
||||
public TimelineAsset asset { get { return null; } }
|
||||
public PlayableDirector director { get { return null; } }
|
||||
public TimelineClip hostClip { get { return null; } }
|
||||
|
||||
List<UnityEngine.Object> ISequenceState.cachedChildAssets { get { return null; } }
|
||||
|
||||
public double start { get { return 0.0; } }
|
||||
public double timeScale { get { return 1.0; } }
|
||||
public double duration { get { return 0.0; } }
|
||||
public bool isReadOnly { get { return false; } }
|
||||
public bool isAssetOnly { get { return false; } set { } }
|
||||
|
||||
TimelineAssetViewModel m_ViewModel;
|
||||
|
||||
public TimelineAssetViewModel viewModel
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_ViewModel == null)
|
||||
m_ViewModel = TimelineWindowViewPrefs.CreateUnassociatedViewModel();
|
||||
return m_ViewModel;
|
||||
}
|
||||
}
|
||||
|
||||
public double time
|
||||
{
|
||||
get { return 0.0; }
|
||||
set { /* NO-OP*/ }
|
||||
}
|
||||
|
||||
public int frame
|
||||
{
|
||||
get { return 0; }
|
||||
set { /* NO-OP*/ }
|
||||
}
|
||||
|
||||
public double frameRate
|
||||
{
|
||||
get { return TimelineAsset.EditorSettings.kDefaultFrameRate; }
|
||||
set { /* NO-OP*/ }
|
||||
}
|
||||
|
||||
public Range GetEvaluableRange()
|
||||
{
|
||||
return new Range();
|
||||
}
|
||||
|
||||
public double ToGlobalTime(double t)
|
||||
{
|
||||
return t;
|
||||
}
|
||||
|
||||
public double ToLocalTime(double t)
|
||||
{
|
||||
return t;
|
||||
}
|
||||
|
||||
public void ResetIsReadOnly()
|
||||
{
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
public void InvalidateChildAssetCache()
|
||||
{
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// NO-OP
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 167329c8289a3a14a9e342df49fc4104
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,45 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[Serializable]
|
||||
struct PlayRange : IEquatable<PlayRange>
|
||||
{
|
||||
public bool Equals(PlayRange other)
|
||||
{
|
||||
return other != null && start.Equals(other.start) && end.Equals(other.end);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is PlayRange other && Equals(other);
|
||||
}
|
||||
|
||||
public static bool operator ==(PlayRange left, PlayRange right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(PlayRange left, PlayRange right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (start.GetHashCode() * 397) ^ end.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public PlayRange(double a, double b)
|
||||
{
|
||||
start = a;
|
||||
end = b;
|
||||
}
|
||||
|
||||
public double start;
|
||||
public double end;
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 82f98a3276a149b5833357e702c1d843
|
||||
timeCreated: 1622645517
|
@ -0,0 +1,302 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6901fab4d5157ac48b9f263730387c03
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[Serializable]
|
||||
class SequencePath
|
||||
{
|
||||
[SerializeField] int m_SelectionRoot;
|
||||
|
||||
public int selectionRoot
|
||||
{
|
||||
get { return m_SelectionRoot; }
|
||||
}
|
||||
|
||||
[SerializeField] List<SequencePathSubElement> m_SubElements;
|
||||
|
||||
public List<SequencePathSubElement> subElements
|
||||
{
|
||||
get { return m_SubElements ?? (m_SubElements = new List<SequencePathSubElement>()); }
|
||||
}
|
||||
|
||||
public void SetSelectionRoot(int instanceID)
|
||||
{
|
||||
m_SelectionRoot = instanceID;
|
||||
subElements.Clear();
|
||||
}
|
||||
|
||||
public void AddSubSequence(ISequenceState state, IExposedPropertyTable resolver)
|
||||
{
|
||||
subElements.Add(SequencePathSubElement.Create(state, resolver));
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
m_SelectionRoot = 0;
|
||||
subElements.Clear();
|
||||
}
|
||||
|
||||
public static bool AreEqual(SequencePath lhs, SequencePath rhs)
|
||||
{
|
||||
if (ReferenceEquals(lhs, null) && ReferenceEquals(rhs, null)) return true;
|
||||
if (ReferenceEquals(lhs, null) || ReferenceEquals(rhs, null)) return false;
|
||||
if (ReferenceEquals(lhs, rhs)) return true;
|
||||
|
||||
var result = lhs.selectionRoot == rhs.selectionRoot &&
|
||||
lhs.subElements.Count == rhs.subElements.Count;
|
||||
|
||||
if (!result)
|
||||
return false;
|
||||
|
||||
for (int i = 0, n = lhs.subElements.Count; i < n; ++i)
|
||||
result = result && SequencePathSubElement.AreEqual(lhs.subElements[i], rhs.subElements[i]);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendFormat("[{0}]", m_SelectionRoot.ToString());
|
||||
|
||||
if (m_SubElements != null && m_SubElements.Count > 0)
|
||||
{
|
||||
foreach (var element in m_SubElements)
|
||||
{
|
||||
sb.Append(" > ");
|
||||
sb.Append(element.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
class SequencePathSubElement
|
||||
{
|
||||
public int trackInstanceID;
|
||||
public int trackHash;
|
||||
public int clipIndex;
|
||||
public int clipHash;
|
||||
public int subDirectorIndex;
|
||||
|
||||
public static SequencePathSubElement Create(ISequenceState state, IExposedPropertyTable resolver)
|
||||
{
|
||||
var clip = state.hostClip;
|
||||
Debug.Assert(clip != null);
|
||||
var track = clip.GetParentTrack();
|
||||
Debug.Assert(track != null);
|
||||
var asset = track.timelineAsset;
|
||||
Debug.Assert(asset != null);
|
||||
var directors = TimelineUtility.GetSubTimelines(clip, resolver as PlayableDirector);
|
||||
|
||||
return new SequencePathSubElement
|
||||
{
|
||||
trackInstanceID = track.GetInstanceID(),
|
||||
trackHash = track.Hash(),
|
||||
clipIndex = Array.IndexOf(track.clips, clip),
|
||||
clipHash = clip.Hash(),
|
||||
subDirectorIndex = directors.IndexOf(state.director)
|
||||
};
|
||||
}
|
||||
|
||||
public static bool AreEqual(SequencePathSubElement lhs, SequencePathSubElement rhs)
|
||||
{
|
||||
if (ReferenceEquals(lhs, null) && ReferenceEquals(rhs, null)) return true;
|
||||
if (ReferenceEquals(lhs, null) || ReferenceEquals(rhs, null)) return false;
|
||||
if (ReferenceEquals(lhs, rhs)) return true;
|
||||
|
||||
return lhs.trackInstanceID == rhs.trackInstanceID &&
|
||||
lhs.trackHash == rhs.trackHash &&
|
||||
lhs.clipIndex == rhs.clipIndex &&
|
||||
lhs.clipHash == rhs.clipHash &&
|
||||
lhs.subDirectorIndex == rhs.subDirectorIndex;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(
|
||||
"[track[{0}] ({1}) > clip[{2}] ({3})]",
|
||||
trackInstanceID.ToString(), trackHash.ToString(),
|
||||
clipIndex.ToString(), clipHash.ToString());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1207768d96c479488b6b81f3483e0c1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,243 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
class SequenceState : ISequenceState
|
||||
{
|
||||
readonly WindowState m_WindowState;
|
||||
readonly SequenceState m_ParentSequence;
|
||||
|
||||
double m_Time;
|
||||
Range? m_CachedEvaluableRange;
|
||||
|
||||
public TimelineAsset asset { get; }
|
||||
public PlayableDirector director { get; }
|
||||
public TimelineClip hostClip { get; }
|
||||
public double start { get; }
|
||||
public double timeScale { get; }
|
||||
|
||||
public bool isAssetOnly { get; set; }
|
||||
|
||||
public double duration
|
||||
{
|
||||
get
|
||||
{
|
||||
if (asset == null)
|
||||
return 0.0;
|
||||
|
||||
var assetDuration = asset.durationMode == TimelineAsset.DurationMode.FixedLength ? asset.fixedDuration : asset.duration;
|
||||
return hostClip == null ? assetDuration : Math.Min(hostClip.duration, assetDuration);
|
||||
}
|
||||
}
|
||||
|
||||
[NonSerialized] List<UnityEngine.Object> m_CachedChildAssets;
|
||||
public List<UnityEngine.Object> cachedChildAssets
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_CachedChildAssets == null && asset != null)
|
||||
{
|
||||
m_CachedChildAssets = AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(asset)).ToList();
|
||||
}
|
||||
|
||||
return m_CachedChildAssets;
|
||||
}
|
||||
}
|
||||
|
||||
public void InvalidateChildAssetCache()
|
||||
{
|
||||
m_CachedChildAssets = null;
|
||||
}
|
||||
|
||||
bool? m_IsReadOnly;
|
||||
public bool isReadOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_IsReadOnly.HasValue)
|
||||
m_IsReadOnly = FileUtility.IsReadOnly(asset);
|
||||
return m_IsReadOnly.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetIsReadOnly()
|
||||
{
|
||||
m_IsReadOnly = null;
|
||||
}
|
||||
|
||||
public TimelineAssetViewModel viewModel
|
||||
{
|
||||
get
|
||||
{
|
||||
return TimelineWindowViewPrefs.GetOrCreateViewModel(asset);
|
||||
}
|
||||
}
|
||||
|
||||
public double time
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_ParentSequence != null)
|
||||
return hostClip.ToLocalTimeUnbound(m_ParentSequence.time);
|
||||
|
||||
return GetLocalTime();
|
||||
}
|
||||
set
|
||||
{
|
||||
var correctedValue = Math.Min(value, TimeUtility.k_MaxTimelineDurationInSeconds);
|
||||
viewModel.windowTime = correctedValue;
|
||||
|
||||
if (m_ParentSequence != null)
|
||||
m_ParentSequence.time = hostClip.FromLocalTimeUnbound(correctedValue);
|
||||
else
|
||||
SetLocalTime(correctedValue);
|
||||
}
|
||||
}
|
||||
|
||||
public int frame
|
||||
{
|
||||
get { return TimeUtility.ToFrames(time, frameRate); }
|
||||
set { time = TimeUtility.FromFrames(Mathf.Max(0, value), frameRate); }
|
||||
}
|
||||
|
||||
public double frameRate
|
||||
{
|
||||
get
|
||||
{
|
||||
if (asset != null)
|
||||
return asset.editorSettings.frameRate;
|
||||
|
||||
return TimelineAsset.EditorSettings.kDefaultFrameRate;
|
||||
}
|
||||
set
|
||||
{
|
||||
var settings = asset.editorSettings;
|
||||
if (Math.Abs(settings.frameRate - value) > TimeUtility.kFrameRateEpsilon)
|
||||
{
|
||||
settings.frameRate = Math.Max(value, TimeUtility.kFrameRateEpsilon);
|
||||
EditorUtility.SetDirty(asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SequenceState(WindowState windowState, TimelineAsset asset, PlayableDirector director, TimelineClip hostClip, SequenceState parentSequence)
|
||||
{
|
||||
m_WindowState = windowState;
|
||||
m_ParentSequence = parentSequence;
|
||||
|
||||
this.asset = asset;
|
||||
this.director = director;
|
||||
this.hostClip = hostClip;
|
||||
isAssetOnly = asset != null && director == null;
|
||||
|
||||
start = hostClip == null ? 0.0 : hostClip.start;
|
||||
timeScale = hostClip == null ? 1.0 : hostClip.timeScale * parentSequence.timeScale;
|
||||
|
||||
if (asset != null)
|
||||
{
|
||||
asset.AssetModifiedOnDisk += AssetOnAssetModifiedOnDisk;
|
||||
}
|
||||
}
|
||||
|
||||
void AssetOnAssetModifiedOnDisk()
|
||||
{
|
||||
m_WindowState?.Refresh();
|
||||
}
|
||||
|
||||
public Range GetEvaluableRange()
|
||||
{
|
||||
if (hostClip == null)
|
||||
return new Range
|
||||
{
|
||||
start = 0.0,
|
||||
end = duration
|
||||
};
|
||||
|
||||
if (!m_CachedEvaluableRange.HasValue)
|
||||
{
|
||||
var globalRange = GetGlobalEvaluableRange();
|
||||
m_CachedEvaluableRange = new Range
|
||||
{
|
||||
start = ToLocalTime(globalRange.start),
|
||||
end = ToLocalTime(globalRange.end)
|
||||
};
|
||||
}
|
||||
|
||||
return m_CachedEvaluableRange.Value;
|
||||
}
|
||||
|
||||
public double ToGlobalTime(double t)
|
||||
{
|
||||
if (hostClip == null)
|
||||
return t;
|
||||
|
||||
return m_ParentSequence.ToGlobalTime(hostClip.FromLocalTimeUnbound(t));
|
||||
}
|
||||
|
||||
public double ToLocalTime(double t)
|
||||
{
|
||||
if (hostClip == null)
|
||||
return t;
|
||||
|
||||
return hostClip.ToLocalTimeUnbound(m_ParentSequence.ToLocalTime(t));
|
||||
}
|
||||
|
||||
double GetLocalTime()
|
||||
{
|
||||
if (!m_WindowState.previewMode && !Application.isPlaying)
|
||||
return viewModel.windowTime;
|
||||
|
||||
// the time needs to always be synchronized with the director
|
||||
if (director != null)
|
||||
m_Time = director.time;
|
||||
|
||||
return m_Time;
|
||||
}
|
||||
|
||||
void SetLocalTime(double newTime)
|
||||
{
|
||||
// do this prior to the calback, because the callback pulls from the get
|
||||
if (director != null)
|
||||
director.time = newTime;
|
||||
|
||||
if (Math.Abs(m_Time - newTime) > TimeUtility.kTimeEpsilon)
|
||||
{
|
||||
m_Time = newTime;
|
||||
m_WindowState.InvokeTimeChangeCallback();
|
||||
}
|
||||
}
|
||||
|
||||
Range GetGlobalEvaluableRange()
|
||||
{
|
||||
if (hostClip == null)
|
||||
return new Range
|
||||
{
|
||||
start = 0.0,
|
||||
end = duration
|
||||
};
|
||||
|
||||
var currentRange = new Range
|
||||
{
|
||||
start = hostClip.ToLocalTimeUnbound(ToGlobalTime(hostClip.start)),
|
||||
end = hostClip.ToLocalTimeUnbound(ToGlobalTime(hostClip.end))
|
||||
};
|
||||
|
||||
return Range.Intersection(currentRange, m_ParentSequence.GetGlobalEvaluableRange());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (asset != null)
|
||||
{
|
||||
asset.AssetModifiedOnDisk -= AssetOnAssetModifiedOnDisk;
|
||||
}
|
||||
|
||||
TimelineWindowViewPrefs.SaveViewModel(asset);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09f4db536a377bc40a9ac110af702bfa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,1215 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditorInternal;
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
using UnityEditor.SceneManagement;
|
||||
#else
|
||||
using UnityEditor.Experimental.SceneManagement;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
#if !UNITY_2020_2_OR_NEWER
|
||||
using UnityEngine.Experimental.Animations;
|
||||
#endif
|
||||
using UnityEngine.Animations;
|
||||
using Object = System.Object;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
delegate bool PendingUpdateDelegate(WindowState state, Event currentEvent);
|
||||
|
||||
/// <summary>
|
||||
/// Interface for faking purposes
|
||||
/// </summary>
|
||||
interface IWindowState
|
||||
{
|
||||
ISequenceState masterSequence { get; }
|
||||
ISequenceState editSequence { get; }
|
||||
IEnumerable<ISequenceState> allSequences { get; }
|
||||
PlayRange playRange { get; set; }
|
||||
void SetCurrentSequence(TimelineAsset timelineAsset, PlayableDirector director, TimelineClip hostClip);
|
||||
void PopSequencesUntilCount(int count);
|
||||
IEnumerable<SequenceContext> GetSubSequences();
|
||||
void SetPlaying(bool start);
|
||||
}
|
||||
class WindowState : IWindowState
|
||||
{
|
||||
const int k_TimeCodeTextFieldId = 3790;
|
||||
|
||||
readonly TimelineWindow m_Window;
|
||||
bool m_Recording;
|
||||
|
||||
readonly SpacePartitioner m_SpacePartitioner = new SpacePartitioner();
|
||||
readonly SpacePartitioner m_HeaderSpacePartitioner = new SpacePartitioner();
|
||||
readonly List<Manipulator> m_CaptureSession = new List<Manipulator>();
|
||||
|
||||
int m_DirtyStamp;
|
||||
float m_BindingAreaWidth = WindowConstants.defaultBindingAreaWidth;
|
||||
|
||||
bool m_MustRebuildGraph;
|
||||
|
||||
float m_LastTime;
|
||||
|
||||
readonly PropertyCollector m_PropertyCollector = new PropertyCollector();
|
||||
|
||||
static AnimationModeDriver s_PreviewDriver;
|
||||
PreviewedBindings<Animator> m_PreviewedAnimators;
|
||||
List<Component> m_PreviewedComponents;
|
||||
IEnumerable<IAnimationWindowPreview> previewedComponents =>
|
||||
m_PreviewedComponents.Where(component => component != null).Cast<IAnimationWindowPreview>();
|
||||
|
||||
public static double kTimeEpsilon { get { return TimeUtility.kTimeEpsilon; } }
|
||||
public static readonly float kMaxShownTime = (float)TimeUtility.k_MaxTimelineDurationInSeconds;
|
||||
|
||||
static readonly ISequenceState k_NullSequenceState = new NullSequenceState();
|
||||
|
||||
// which tracks are armed for record - only one allowed per 'actor'
|
||||
Dictionary<TrackAsset, TrackAsset> m_ArmedTracks = new Dictionary<TrackAsset, TrackAsset>();
|
||||
|
||||
TimelineWindow.TimelineWindowPreferences m_Preferences;
|
||||
|
||||
List<PendingUpdateDelegate> m_OnStartFrameUpdates;
|
||||
List<PendingUpdateDelegate> m_OnEndFrameUpdates;
|
||||
|
||||
readonly SequenceHierarchy m_SequenceHierarchy;
|
||||
|
||||
public event Action<WindowState, Event> windowOnGuiStarted;
|
||||
|
||||
public event Action<bool> OnPlayStateChange;
|
||||
public event System.Action OnDirtyStampChange;
|
||||
public event System.Action OnRebuildGraphChange;
|
||||
public event System.Action OnTimeChange;
|
||||
public event System.Action OnRecordingChange;
|
||||
|
||||
public event System.Action OnBeforeSequenceChange;
|
||||
public event System.Action OnAfterSequenceChange;
|
||||
|
||||
public WindowState(TimelineWindow w, SequenceHierarchy hierarchy)
|
||||
{
|
||||
m_Window = w;
|
||||
m_Preferences = w.preferences;
|
||||
hierarchy.Init(this);
|
||||
m_SequenceHierarchy = hierarchy;
|
||||
TimelinePlayable.muteAudioScrubbing = muteAudioScrubbing;
|
||||
}
|
||||
|
||||
public static AnimationModeDriver previewDriver
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_PreviewDriver == null)
|
||||
{
|
||||
s_PreviewDriver = ScriptableObject.CreateInstance<AnimationModeDriver>();
|
||||
AnimationPreviewUtilities.s_PreviewDriver = s_PreviewDriver;
|
||||
}
|
||||
return s_PreviewDriver;
|
||||
}
|
||||
}
|
||||
|
||||
public EditorWindow editorWindow
|
||||
{
|
||||
get { return m_Window; }
|
||||
}
|
||||
|
||||
public ISequenceState editSequence
|
||||
{
|
||||
get
|
||||
{
|
||||
// Using "null" ISequenceState to avoid checking against null all the time.
|
||||
// This *should* be removed in a phase 2 of refactoring, where we make sure
|
||||
// to pass around the correct state object instead of letting clients dig
|
||||
// into the WindowState for whatever they want.
|
||||
return m_SequenceHierarchy.editSequence ?? k_NullSequenceState;
|
||||
}
|
||||
}
|
||||
|
||||
public ISequenceState masterSequence
|
||||
{
|
||||
get { return m_SequenceHierarchy.masterSequence ?? k_NullSequenceState; }
|
||||
}
|
||||
|
||||
public ISequenceState referenceSequence
|
||||
{
|
||||
get { return timeReferenceMode == TimeReferenceMode.Local ? editSequence : masterSequence; }
|
||||
}
|
||||
|
||||
public IEnumerable<ISequenceState> allSequences
|
||||
{
|
||||
get { return m_SequenceHierarchy.allSequences; }
|
||||
}
|
||||
|
||||
public bool rebuildGraph
|
||||
{
|
||||
get { return m_MustRebuildGraph; }
|
||||
set { SyncNotifyValue(ref m_MustRebuildGraph, value, OnRebuildGraphChange); }
|
||||
}
|
||||
|
||||
public float mouseDragLag { get; set; }
|
||||
|
||||
public SpacePartitioner spacePartitioner
|
||||
{
|
||||
get { return m_SpacePartitioner; }
|
||||
}
|
||||
|
||||
public SpacePartitioner headerSpacePartitioner
|
||||
{
|
||||
get { return m_HeaderSpacePartitioner; }
|
||||
}
|
||||
|
||||
public List<Manipulator> captured
|
||||
{
|
||||
get { return m_CaptureSession; }
|
||||
}
|
||||
|
||||
public void AddCaptured(Manipulator manipulator)
|
||||
{
|
||||
if (!m_CaptureSession.Contains(manipulator))
|
||||
m_CaptureSession.Add(manipulator);
|
||||
}
|
||||
|
||||
public void RemoveCaptured(Manipulator manipulator)
|
||||
{
|
||||
m_CaptureSession.Remove(manipulator);
|
||||
}
|
||||
|
||||
public bool isJogging { get; set; }
|
||||
|
||||
public int viewStateHash { get; private set; }
|
||||
|
||||
public float bindingAreaWidth
|
||||
{
|
||||
get { return m_BindingAreaWidth; }
|
||||
set { m_BindingAreaWidth = value; }
|
||||
}
|
||||
|
||||
public float sequencerHeaderWidth
|
||||
{
|
||||
get { return editSequence.viewModel.sequencerHeaderWidth; }
|
||||
set
|
||||
{
|
||||
editSequence.viewModel.sequencerHeaderWidth = Mathf.Clamp(value, WindowConstants.minHeaderWidth, WindowConstants.maxHeaderWidth);
|
||||
}
|
||||
}
|
||||
|
||||
public float mainAreaWidth { get; set; }
|
||||
|
||||
public float trackScale
|
||||
{
|
||||
get { return editSequence.viewModel.trackScale; }
|
||||
set
|
||||
{
|
||||
editSequence.viewModel.trackScale = value;
|
||||
m_Window.treeView.CalculateRowRects();
|
||||
}
|
||||
}
|
||||
|
||||
public int dirtyStamp
|
||||
{
|
||||
get { return m_DirtyStamp; }
|
||||
private set { SyncNotifyValue(ref m_DirtyStamp, value, OnDirtyStampChange); }
|
||||
}
|
||||
|
||||
public bool showQuadTree { get; set; }
|
||||
|
||||
public bool canRecord
|
||||
{
|
||||
get { return AnimationMode.InAnimationMode(previewDriver) || !AnimationMode.InAnimationMode(); }
|
||||
}
|
||||
|
||||
public bool recording
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!previewMode)
|
||||
m_Recording = false;
|
||||
return m_Recording;
|
||||
}
|
||||
// set can only be used to disable recording
|
||||
set
|
||||
{
|
||||
if (ignorePreview)
|
||||
return;
|
||||
|
||||
// force preview mode on
|
||||
if (value)
|
||||
previewMode = true;
|
||||
|
||||
bool newValue = value;
|
||||
if (!previewMode)
|
||||
newValue = false;
|
||||
|
||||
if (newValue && m_ArmedTracks.Count == 0)
|
||||
{
|
||||
Debug.LogError("Cannot enable recording without an armed track");
|
||||
newValue = false;
|
||||
}
|
||||
|
||||
if (!newValue)
|
||||
m_ArmedTracks.Clear();
|
||||
|
||||
if (newValue != m_Recording)
|
||||
{
|
||||
if (newValue)
|
||||
AnimationMode.StartAnimationRecording();
|
||||
else
|
||||
AnimationMode.StopAnimationRecording();
|
||||
|
||||
InspectorWindow.RepaintAllInspectors();
|
||||
}
|
||||
|
||||
SyncNotifyValue(ref m_Recording, newValue, OnRecordingChange);
|
||||
}
|
||||
}
|
||||
|
||||
public bool previewMode
|
||||
{
|
||||
get { return ignorePreview || AnimationMode.InAnimationMode(previewDriver); }
|
||||
set
|
||||
{
|
||||
if (ignorePreview)
|
||||
return;
|
||||
bool inAnimationMode = AnimationMode.InAnimationMode(previewDriver);
|
||||
if (!value)
|
||||
{
|
||||
if (inAnimationMode)
|
||||
{
|
||||
Stop();
|
||||
|
||||
OnStopPreview();
|
||||
|
||||
AnimationMode.StopAnimationMode(previewDriver);
|
||||
|
||||
AnimationPropertyContextualMenu.Instance.SetResponder(null);
|
||||
previewedDirectors = null;
|
||||
}
|
||||
}
|
||||
else if (!inAnimationMode)
|
||||
{
|
||||
editSequence.time = editSequence.viewModel.windowTime;
|
||||
EvaluateImmediate(); // does appropriate caching prior to enabling
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool playing
|
||||
{
|
||||
get
|
||||
{
|
||||
return masterSequence.director != null && masterSequence.director.state == PlayState.Playing;
|
||||
}
|
||||
}
|
||||
|
||||
public float playbackSpeed { get; set; }
|
||||
|
||||
public bool frameSnap
|
||||
{
|
||||
get { return TimelinePreferences.instance.snapToFrame; }
|
||||
set { TimelinePreferences.instance.snapToFrame = value; }
|
||||
}
|
||||
|
||||
public bool edgeSnaps
|
||||
{
|
||||
get { return TimelinePreferences.instance.edgeSnap; }
|
||||
set { TimelinePreferences.instance.edgeSnap = value; }
|
||||
}
|
||||
|
||||
public bool muteAudioScrubbing
|
||||
{
|
||||
get { return !TimelinePreferences.instance.audioScrubbing; }
|
||||
set
|
||||
{
|
||||
TimelinePreferences.instance.audioScrubbing = !value;
|
||||
TimelinePlayable.muteAudioScrubbing = value;
|
||||
RebuildPlayableGraph();
|
||||
}
|
||||
}
|
||||
|
||||
public TimeReferenceMode timeReferenceMode
|
||||
{
|
||||
get { return m_Preferences.timeReferenceMode; }
|
||||
set { m_Preferences.timeReferenceMode = value; }
|
||||
}
|
||||
|
||||
public TimeFormat timeFormat
|
||||
{
|
||||
get { return TimelinePreferences.instance.timeFormat; }
|
||||
set { TimelinePreferences.instance.timeFormat = value; }
|
||||
}
|
||||
|
||||
public bool showAudioWaveform
|
||||
{
|
||||
get { return TimelinePreferences.instance.showAudioWaveform; }
|
||||
set { TimelinePreferences.instance.showAudioWaveform = value; }
|
||||
}
|
||||
|
||||
public PlayRange playRange
|
||||
{
|
||||
get { return masterSequence.viewModel.timeAreaPlayRange; }
|
||||
set { masterSequence.viewModel.timeAreaPlayRange = ValidatePlayRange(value, masterSequence); }
|
||||
}
|
||||
|
||||
public bool showMarkerHeader
|
||||
{
|
||||
get { return editSequence.asset != null && editSequence.asset.markerTrack != null && editSequence.asset.markerTrack.GetShowMarkers(); }
|
||||
set { GetWindow().SetShowMarkerHeader(value); }
|
||||
}
|
||||
|
||||
public EditMode.EditType editType
|
||||
{
|
||||
get { return m_Preferences.editType; }
|
||||
set { m_Preferences.editType = value; }
|
||||
}
|
||||
|
||||
public PlaybackScrollMode autoScrollMode
|
||||
{
|
||||
get { return TimelinePreferences.instance.playbackScrollMode; }
|
||||
set { TimelinePreferences.instance.playbackScrollMode = value; }
|
||||
}
|
||||
|
||||
public List<PlayableDirector> previewedDirectors { get; private set; }
|
||||
|
||||
public void OnDestroy()
|
||||
{
|
||||
if (!ignorePreview)
|
||||
Stop();
|
||||
|
||||
if (m_OnStartFrameUpdates != null)
|
||||
m_OnStartFrameUpdates.Clear();
|
||||
|
||||
if (m_OnEndFrameUpdates != null)
|
||||
m_OnEndFrameUpdates.Clear();
|
||||
|
||||
m_SequenceHierarchy.Clear();
|
||||
windowOnGuiStarted = null;
|
||||
}
|
||||
|
||||
public void OnSceneSaved()
|
||||
{
|
||||
// the director will reset it's time when the scene is saved.
|
||||
EnsureWindowTimeConsistency();
|
||||
}
|
||||
|
||||
public void SetCurrentSequence(TimelineAsset timelineAsset, PlayableDirector director, TimelineClip hostClip)
|
||||
{
|
||||
if (OnBeforeSequenceChange != null)
|
||||
OnBeforeSequenceChange.Invoke();
|
||||
|
||||
OnCurrentDirectorWillChange();
|
||||
|
||||
if (hostClip == null || timelineAsset == null)
|
||||
{
|
||||
m_PropertyCollector.Clear();
|
||||
m_SequenceHierarchy.Clear();
|
||||
}
|
||||
|
||||
if (timelineAsset != null)
|
||||
m_SequenceHierarchy.Add(timelineAsset, director, hostClip);
|
||||
|
||||
if (OnAfterSequenceChange != null)
|
||||
OnAfterSequenceChange.Invoke();
|
||||
}
|
||||
|
||||
public void PopSequencesUntilCount(int count)
|
||||
{
|
||||
if (count >= m_SequenceHierarchy.count) return;
|
||||
if (count < 1) return;
|
||||
|
||||
if (OnBeforeSequenceChange != null)
|
||||
OnBeforeSequenceChange.Invoke();
|
||||
|
||||
var nextDirector = m_SequenceHierarchy.GetStateAtIndex(count - 1).director;
|
||||
OnCurrentDirectorWillChange();
|
||||
|
||||
m_SequenceHierarchy.RemoveUntilCount(count);
|
||||
|
||||
EnsureWindowTimeConsistency();
|
||||
|
||||
if (OnAfterSequenceChange != null)
|
||||
OnAfterSequenceChange.Invoke();
|
||||
}
|
||||
|
||||
public SequencePath GetCurrentSequencePath()
|
||||
{
|
||||
return m_SequenceHierarchy.ToSequencePath();
|
||||
}
|
||||
|
||||
public void SetCurrentSequencePath(SequencePath path, bool forceRebuild)
|
||||
{
|
||||
if (!m_SequenceHierarchy.NeedsUpdate(path, forceRebuild))
|
||||
return;
|
||||
|
||||
if (OnBeforeSequenceChange != null)
|
||||
OnBeforeSequenceChange.Invoke();
|
||||
|
||||
m_SequenceHierarchy.FromSequencePath(path, forceRebuild);
|
||||
|
||||
if (OnAfterSequenceChange != null)
|
||||
OnAfterSequenceChange.Invoke();
|
||||
}
|
||||
|
||||
public IEnumerable<ISequenceState> GetAllSequences()
|
||||
{
|
||||
return m_SequenceHierarchy.allSequences;
|
||||
}
|
||||
|
||||
public IEnumerable<SequenceContext> GetSubSequences()
|
||||
{
|
||||
var contexts =
|
||||
editSequence.asset?.flattenedTracks
|
||||
.SelectMany(x => x.clips)
|
||||
.Where((TimelineUtility.HasCustomEditor))
|
||||
.SelectMany((clip =>
|
||||
TimelineUtility.GetSubTimelines(clip, TimelineEditor.inspectedDirector)
|
||||
.Select(director => new SequenceContext(director, clip))));
|
||||
|
||||
return contexts;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
recording = false;
|
||||
previewMode = false;
|
||||
}
|
||||
|
||||
public double GetSnappedTimeAtMousePosition(Vector2 mousePos)
|
||||
{
|
||||
return TimeReferenceUtility.SnapToFrameIfRequired(ScreenSpacePixelToTimeAreaTime(mousePos.x));
|
||||
}
|
||||
|
||||
static void SyncNotifyValue<T>(ref T oldValue, T newValue, System.Action changeStateCallback)
|
||||
{
|
||||
var stateChanged = false;
|
||||
|
||||
if (oldValue == null)
|
||||
{
|
||||
oldValue = newValue;
|
||||
stateChanged = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!oldValue.Equals(newValue))
|
||||
{
|
||||
oldValue = newValue;
|
||||
stateChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (stateChanged && changeStateCallback != null)
|
||||
{
|
||||
changeStateCallback.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public TimelineWindowAnalytics analytics = new TimelineWindowAnalytics();
|
||||
|
||||
public void SetTimeAreaTransform(Vector2 newTranslation, Vector2 newScale)
|
||||
{
|
||||
m_Window.timeArea.SetTransform(newTranslation, newScale);
|
||||
TimeAreaChanged();
|
||||
}
|
||||
|
||||
public void SetTimeAreaShownRange(float min, float max)
|
||||
{
|
||||
m_Window.timeArea.SetShownHRange(min, max);
|
||||
TimeAreaChanged();
|
||||
}
|
||||
|
||||
internal void TimeAreaChanged()
|
||||
{
|
||||
if (editSequence.asset != null)
|
||||
{
|
||||
editSequence.viewModel.timeAreaShownRange = new Vector2(m_Window.timeArea.shownArea.x, m_Window.timeArea.shownArea.xMax);
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetPreviewMode()
|
||||
{
|
||||
var mode = previewMode;
|
||||
previewMode = false;
|
||||
previewMode = mode;
|
||||
}
|
||||
|
||||
public bool TimeIsInRange(float value)
|
||||
{
|
||||
Rect shownArea = m_Window.timeArea.shownArea;
|
||||
return value >= shownArea.x && value <= shownArea.xMax;
|
||||
}
|
||||
|
||||
public bool RangeIsVisible(Range range)
|
||||
{
|
||||
var shownArea = m_Window.timeArea.shownArea;
|
||||
return range.start < shownArea.xMax && range.end > shownArea.xMin;
|
||||
}
|
||||
|
||||
public void EnsurePlayHeadIsVisible()
|
||||
{
|
||||
double minDisplayedTime = PixelToTime(timeAreaRect.xMin);
|
||||
double maxDisplayedTime = PixelToTime(timeAreaRect.xMax);
|
||||
|
||||
double currentTime = editSequence.time;
|
||||
if (currentTime >= minDisplayedTime && currentTime <= maxDisplayedTime)
|
||||
return;
|
||||
|
||||
float displayedTimeRange = (float)(maxDisplayedTime - minDisplayedTime);
|
||||
float minimumTimeToDisplay = (float)currentTime - displayedTimeRange / 2.0f;
|
||||
float maximumTimeToDisplay = (float)currentTime + displayedTimeRange / 2.0f;
|
||||
SetTimeAreaShownRange(minimumTimeToDisplay, maximumTimeToDisplay);
|
||||
}
|
||||
|
||||
public void SetPlayHeadToMiddle()
|
||||
{
|
||||
double minDisplayedTime = PixelToTime(timeAreaRect.xMin);
|
||||
double maxDisplayedTime = PixelToTime(timeAreaRect.xMax);
|
||||
|
||||
double currentTime = editSequence.time;
|
||||
float displayedTimeRange = (float)(maxDisplayedTime - minDisplayedTime);
|
||||
|
||||
if (currentTime >= minDisplayedTime && currentTime <= maxDisplayedTime)
|
||||
{
|
||||
if (currentTime < minDisplayedTime + displayedTimeRange / 2)
|
||||
return;
|
||||
}
|
||||
|
||||
const float kCatchUpSpeed = 3f;
|
||||
float realDelta = Mathf.Clamp(Time.realtimeSinceStartup - m_LastTime, 0f, 1f) * kCatchUpSpeed;
|
||||
float scrollCatchupAmount = kCatchUpSpeed * realDelta * displayedTimeRange / 2;
|
||||
|
||||
if (currentTime < minDisplayedTime)
|
||||
{
|
||||
SetTimeAreaShownRange((float)currentTime, (float)currentTime + displayedTimeRange);
|
||||
}
|
||||
else if (currentTime > maxDisplayedTime)
|
||||
{
|
||||
SetTimeAreaShownRange((float)currentTime - displayedTimeRange + scrollCatchupAmount, (float)currentTime + scrollCatchupAmount);
|
||||
}
|
||||
else if (currentTime > minDisplayedTime + displayedTimeRange / 2)
|
||||
{
|
||||
float targetMinDisplayedTime = Mathf.Min((float)minDisplayedTime + scrollCatchupAmount,
|
||||
(float)(currentTime - displayedTimeRange / 2));
|
||||
SetTimeAreaShownRange(targetMinDisplayedTime, targetMinDisplayedTime + displayedTimeRange);
|
||||
}
|
||||
}
|
||||
|
||||
internal void UpdateLastFrameTime()
|
||||
{
|
||||
m_LastTime = Time.realtimeSinceStartup;
|
||||
}
|
||||
|
||||
public Vector2 timeAreaShownRange
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Window.state.editSequence.asset != null)
|
||||
return editSequence.viewModel.timeAreaShownRange;
|
||||
|
||||
return TimelineAssetViewModel.TimeAreaDefaultRange;
|
||||
}
|
||||
set
|
||||
{
|
||||
SetTimeAreaShownRange(value.x, value.y);
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 timeAreaTranslation
|
||||
{
|
||||
get { return m_Window.timeArea.translation; }
|
||||
}
|
||||
|
||||
public Vector2 timeAreaScale
|
||||
{
|
||||
get { return m_Window.timeArea.scale; }
|
||||
}
|
||||
|
||||
public Rect timeAreaRect
|
||||
{
|
||||
get
|
||||
{
|
||||
var sequenceContentRect = m_Window.sequenceContentRect;
|
||||
return new Rect(
|
||||
sequenceContentRect.x,
|
||||
WindowConstants.timeAreaYPosition,
|
||||
Mathf.Max(sequenceContentRect.width, WindowConstants.timeAreaMinWidth),
|
||||
WindowConstants.timeAreaHeight
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public float windowHeight
|
||||
{
|
||||
get { return m_Window.position.height; }
|
||||
}
|
||||
|
||||
public bool playRangeEnabled
|
||||
{
|
||||
get { return !ignorePreview && masterSequence.viewModel.playRangeEnabled && !IsEditingASubTimeline(); }
|
||||
set
|
||||
{
|
||||
if (!ignorePreview)
|
||||
masterSequence.viewModel.playRangeEnabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ignorePreview
|
||||
{
|
||||
get
|
||||
{
|
||||
var shouldIgnorePreview = masterSequence.asset != null && !masterSequence.asset.editorSettings.scenePreview;
|
||||
return Application.isPlaying || shouldIgnorePreview;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public TimelineWindow GetWindow()
|
||||
{
|
||||
return m_Window;
|
||||
}
|
||||
|
||||
public void Play()
|
||||
{
|
||||
if (masterSequence.director == null)
|
||||
return;
|
||||
|
||||
if (!previewMode)
|
||||
previewMode = true;
|
||||
|
||||
if (previewMode)
|
||||
{
|
||||
if (masterSequence.time > masterSequence.duration)
|
||||
masterSequence.time = 0;
|
||||
#if TIMELINE_FRAMEACCURATE
|
||||
if (TimelinePreferences.instance.playbackLockedToFrame)
|
||||
{
|
||||
FrameRate frameRate = FrameRate.DoubleToFrameRate(masterSequence.asset.editorSettings.frameRate);
|
||||
masterSequence.director.Play(frameRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
masterSequence.director.Play();
|
||||
}
|
||||
#else
|
||||
masterSequence.director.Play();
|
||||
#endif
|
||||
masterSequence.director.ProcessPendingGraphChanges();
|
||||
PlayableDirector.ResetFrameTiming();
|
||||
InvokePlayStateChangeCallback(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
if (masterSequence.director != null)
|
||||
{
|
||||
masterSequence.director.Pause();
|
||||
masterSequence.director.ProcessPendingGraphChanges();
|
||||
SynchronizeSequencesAfterPlayback();
|
||||
InvokePlayStateChangeCallback(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPlaying(bool start)
|
||||
{
|
||||
if (start && !playing)
|
||||
{
|
||||
Play();
|
||||
}
|
||||
|
||||
if (!start && playing)
|
||||
{
|
||||
Pause();
|
||||
}
|
||||
|
||||
analytics.SendPlayEvent(start);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (masterSequence.director != null)
|
||||
{
|
||||
masterSequence.director.Stop();
|
||||
masterSequence.director.ProcessPendingGraphChanges();
|
||||
InvokePlayStateChangeCallback(false);
|
||||
}
|
||||
}
|
||||
|
||||
void InvokePlayStateChangeCallback(bool isPlaying)
|
||||
{
|
||||
if (OnPlayStateChange != null)
|
||||
OnPlayStateChange.Invoke(isPlaying);
|
||||
}
|
||||
|
||||
public void RebuildPlayableGraph()
|
||||
{
|
||||
if (masterSequence.director != null)
|
||||
{
|
||||
masterSequence.director.RebuildGraph();
|
||||
// rebuild both the parent and the edit sequences. control tracks don't necessary
|
||||
// rebuild the subdirector on recreation
|
||||
if (editSequence.director != null && editSequence.director != masterSequence.director)
|
||||
{
|
||||
editSequence.director.RebuildGraph();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Evaluate()
|
||||
{
|
||||
if (masterSequence.director != null)
|
||||
{
|
||||
if (!EditorApplication.isPlaying && !previewMode)
|
||||
GatherProperties(masterSequence.director);
|
||||
|
||||
ForceTimeOnDirector(masterSequence.director);
|
||||
masterSequence.director.DeferredEvaluate();
|
||||
|
||||
if (EditorApplication.isPlaying == false)
|
||||
{
|
||||
PlayModeView.RepaintAll();
|
||||
SceneView.RepaintAll();
|
||||
AudioMixerWindow.RepaintAudioMixerWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void EvaluateImmediate()
|
||||
{
|
||||
if (masterSequence.director != null && masterSequence.director.isActiveAndEnabled)
|
||||
{
|
||||
if (!EditorApplication.isPlaying && !previewMode)
|
||||
GatherProperties(masterSequence.director);
|
||||
|
||||
if (previewMode)
|
||||
{
|
||||
ForceTimeOnDirector(masterSequence.director);
|
||||
masterSequence.director.ProcessPendingGraphChanges();
|
||||
masterSequence.director.Evaluate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
CheckRecordingState();
|
||||
dirtyStamp = dirtyStamp + 1;
|
||||
|
||||
rebuildGraph = true;
|
||||
}
|
||||
|
||||
public void UpdateViewStateHash()
|
||||
{
|
||||
viewStateHash = timeAreaTranslation.GetHashCode()
|
||||
.CombineHash(timeAreaScale.GetHashCode())
|
||||
.CombineHash(trackScale.GetHashCode());
|
||||
}
|
||||
|
||||
public bool IsEditingASubTimeline()
|
||||
{
|
||||
return editSequence != masterSequence;
|
||||
}
|
||||
|
||||
public bool IsEditingAnEmptyTimeline()
|
||||
{
|
||||
return editSequence.asset == null;
|
||||
}
|
||||
|
||||
public bool IsEditingAPrefabAsset()
|
||||
{
|
||||
var stage = PrefabStageUtility.GetCurrentPrefabStage();
|
||||
return stage != null && editSequence.director != null && stage.IsPartOfPrefabContents(editSequence.director.gameObject);
|
||||
}
|
||||
|
||||
public bool IsCurrentEditingASequencerTextField()
|
||||
{
|
||||
if (editSequence.asset == null)
|
||||
return false;
|
||||
|
||||
if (k_TimeCodeTextFieldId == GUIUtility.keyboardControl)
|
||||
return true;
|
||||
|
||||
return editSequence.asset.flattenedTracks.Count(t => t.GetInstanceID() == GUIUtility.keyboardControl) != 0;
|
||||
}
|
||||
|
||||
public float TimeToTimeAreaPixel(double t) // TimeToTimeAreaPixel
|
||||
{
|
||||
float pixelX = (float)t;
|
||||
pixelX *= timeAreaScale.x;
|
||||
pixelX += timeAreaTranslation.x + sequencerHeaderWidth;
|
||||
return pixelX;
|
||||
}
|
||||
|
||||
public float TimeToScreenSpacePixel(double time)
|
||||
{
|
||||
float pixelX = (float)time;
|
||||
pixelX *= timeAreaScale.x;
|
||||
pixelX += timeAreaTranslation.x;
|
||||
return pixelX;
|
||||
}
|
||||
|
||||
public float TimeToPixel(double time)
|
||||
{
|
||||
return m_Window.timeArea.TimeToPixel((float)time, timeAreaRect);
|
||||
}
|
||||
|
||||
public float PixelToTime(float pixel)
|
||||
{
|
||||
return m_Window.timeArea.PixelToTime(pixel, timeAreaRect);
|
||||
}
|
||||
|
||||
public float PixelDeltaToDeltaTime(float p)
|
||||
{
|
||||
return PixelToTime(p) - PixelToTime(0);
|
||||
}
|
||||
|
||||
public float TimeAreaPixelToTime(float pixel)
|
||||
{
|
||||
return PixelToTime(pixel);
|
||||
}
|
||||
|
||||
public float ScreenSpacePixelToTimeAreaTime(float p)
|
||||
{
|
||||
// transform into track space by offsetting the pixel by the screen-space offset of the time area
|
||||
p -= timeAreaRect.x;
|
||||
return TrackSpacePixelToTimeAreaTime(p);
|
||||
}
|
||||
|
||||
public float TrackSpacePixelToTimeAreaTime(float p)
|
||||
{
|
||||
p -= timeAreaTranslation.x;
|
||||
|
||||
if (timeAreaScale.x > 0.0f)
|
||||
return p / timeAreaScale.x;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
public void OffsetTimeArea(int pixels)
|
||||
{
|
||||
Vector3 tx = timeAreaTranslation;
|
||||
tx.x += pixels;
|
||||
SetTimeAreaTransform(tx, timeAreaScale);
|
||||
}
|
||||
|
||||
public GameObject GetSceneReference(TrackAsset asset)
|
||||
{
|
||||
if (editSequence.director == null)
|
||||
return null; // no player bound
|
||||
|
||||
return TimelineUtility.GetSceneGameObject(editSequence.director, asset);
|
||||
}
|
||||
|
||||
public void CalculateRowRects()
|
||||
{
|
||||
// arming a track might add inline curve tracks, recalc track heights
|
||||
if (m_Window != null && m_Window.treeView != null)
|
||||
m_Window.treeView.CalculateRowRects();
|
||||
}
|
||||
|
||||
// Only one track within a 'track' hierarchy can be armed
|
||||
public void ArmForRecord(TrackAsset track)
|
||||
{
|
||||
m_ArmedTracks[TimelineUtility.GetSceneReferenceTrack(track)] = track;
|
||||
if (track != null && !recording)
|
||||
recording = true;
|
||||
if (!recording)
|
||||
return;
|
||||
|
||||
track.OnRecordingArmed(editSequence.director);
|
||||
CalculateRowRects();
|
||||
}
|
||||
|
||||
public void UnarmForRecord(TrackAsset track)
|
||||
{
|
||||
m_ArmedTracks.Remove(TimelineUtility.GetSceneReferenceTrack(track));
|
||||
if (m_ArmedTracks.Count == 0)
|
||||
recording = false;
|
||||
track.OnRecordingUnarmed(editSequence.director);
|
||||
}
|
||||
|
||||
public void UpdateRecordingState()
|
||||
{
|
||||
if (recording)
|
||||
{
|
||||
foreach (var track in m_ArmedTracks.Values)
|
||||
{
|
||||
if (track != null)
|
||||
track.OnRecordingTimeChanged(editSequence.director);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsTrackRecordable(TrackAsset track)
|
||||
{
|
||||
// A track with animated parameters can always be recorded to
|
||||
return IsArmedForRecord(track) || track.HasAnyAnimatableParameters();
|
||||
}
|
||||
|
||||
public bool IsArmedForRecord(TrackAsset track)
|
||||
{
|
||||
return track == GetArmedTrack(track);
|
||||
}
|
||||
|
||||
public TrackAsset GetArmedTrack(TrackAsset track)
|
||||
{
|
||||
TrackAsset outTrack;
|
||||
m_ArmedTracks.TryGetValue(TimelineUtility.GetSceneReferenceTrack(track), out outTrack);
|
||||
return outTrack;
|
||||
}
|
||||
|
||||
void CheckRecordingState()
|
||||
{
|
||||
// checks for deleted tracks, and makes sure the recording state matches
|
||||
if (m_ArmedTracks.Any(t => t.Value == null))
|
||||
{
|
||||
m_ArmedTracks = m_ArmedTracks.Where(t => t.Value != null).ToDictionary(t => t.Key, t => t.Value);
|
||||
if (m_ArmedTracks.Count == 0)
|
||||
recording = false;
|
||||
}
|
||||
}
|
||||
|
||||
void OnCurrentDirectorWillChange()
|
||||
{
|
||||
if (ignorePreview)
|
||||
return;
|
||||
|
||||
SynchronizeViewModelTime(editSequence);
|
||||
Stop();
|
||||
rebuildGraph = true; // needed for asset previews
|
||||
}
|
||||
|
||||
public void GatherProperties(PlayableDirector director)
|
||||
{
|
||||
if (director == null || Application.isPlaying)
|
||||
return;
|
||||
|
||||
var asset = director.playableAsset as TimelineAsset;
|
||||
if (asset != null && !asset.editorSettings.scenePreview)
|
||||
return;
|
||||
|
||||
if (!previewMode)
|
||||
{
|
||||
AnimationMode.StartAnimationMode(previewDriver);
|
||||
|
||||
OnStartPreview(director);
|
||||
|
||||
AnimationPropertyContextualMenu.Instance.SetResponder(new TimelineRecordingContextualResponder(this));
|
||||
if (!previewMode)
|
||||
return;
|
||||
EnsureWindowTimeConsistency();
|
||||
}
|
||||
|
||||
if (asset != null)
|
||||
{
|
||||
m_PropertyCollector.Reset();
|
||||
m_PropertyCollector.PushActiveGameObject(null); // avoid overflow on unbound tracks
|
||||
asset.GatherProperties(director, m_PropertyCollector);
|
||||
}
|
||||
}
|
||||
|
||||
void OnStartPreview(PlayableDirector director)
|
||||
{
|
||||
previewedDirectors = TimelineUtility.GetAllDirectorsInHierarchy(director).ToList();
|
||||
|
||||
if (previewedDirectors == null)
|
||||
return;
|
||||
|
||||
m_PreviewedAnimators = PreviewedBindings<Animator>.GetPreviewedBindings(previewedDirectors);
|
||||
|
||||
m_PreviewedComponents = m_PreviewedAnimators.GetUniqueBindings()
|
||||
.SelectMany(animator => animator.GetComponents<IAnimationWindowPreview>()
|
||||
.Cast<Component>())
|
||||
.ToList();
|
||||
|
||||
foreach (var previewedComponent in previewedComponents)
|
||||
{
|
||||
previewedComponent.StartPreview();
|
||||
}
|
||||
#if UNITY_2022_2_OR_NEWER
|
||||
PrefabUtility.allowRecordingPrefabPropertyOverridesFor += AllowRecordingPrefabPropertyOverridesFor;
|
||||
#endif //UNITY_2022_2_OR_NEWER
|
||||
}
|
||||
|
||||
internal bool AllowRecordingPrefabPropertyOverridesFor(Object componentOrGameObject)
|
||||
{
|
||||
if (componentOrGameObject == null)
|
||||
throw new ArgumentNullException(nameof(componentOrGameObject));
|
||||
|
||||
if (previewMode == false)
|
||||
return true;
|
||||
|
||||
if (m_ArmedTracks.Count == 0)
|
||||
return true;
|
||||
|
||||
GameObject inputGameObject = null;
|
||||
if (componentOrGameObject is Component component)
|
||||
{
|
||||
inputGameObject = component.gameObject;
|
||||
}
|
||||
else if (componentOrGameObject is GameObject gameObject)
|
||||
{
|
||||
inputGameObject = gameObject;
|
||||
}
|
||||
|
||||
if (inputGameObject == null)
|
||||
return true;
|
||||
|
||||
var armedTracks = m_ArmedTracks.Keys;
|
||||
foreach (var track in armedTracks)
|
||||
{
|
||||
var animators = m_PreviewedAnimators.GetBindingsForObject(track);
|
||||
foreach (var animator in animators)
|
||||
{
|
||||
if (inputGameObject.transform.IsChildOf(animator.transform))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnStopPreview()
|
||||
{
|
||||
if (m_PreviewedComponents != null)
|
||||
{
|
||||
foreach (var previewComponent in previewedComponents)
|
||||
{
|
||||
previewComponent.StopPreview();
|
||||
}
|
||||
|
||||
m_PreviewedComponents = null;
|
||||
}
|
||||
|
||||
foreach (var previewAnimator in m_PreviewedAnimators.GetUniqueBindings())
|
||||
{
|
||||
if (previewAnimator != null)
|
||||
previewAnimator.UnbindAllHandles();
|
||||
}
|
||||
m_PreviewedAnimators = default;
|
||||
|
||||
#if UNITY_2022_2_OR_NEWER
|
||||
PrefabUtility.allowRecordingPrefabPropertyOverridesFor -= AllowRecordingPrefabPropertyOverridesFor;
|
||||
#endif //UNITY_2022_2_OR_NEWER
|
||||
}
|
||||
|
||||
internal void ProcessStartFramePendingUpdates()
|
||||
{
|
||||
if (m_OnStartFrameUpdates != null)
|
||||
m_OnStartFrameUpdates.RemoveAll(callback => callback.Invoke(this, Event.current));
|
||||
}
|
||||
|
||||
internal void ProcessEndFramePendingUpdates()
|
||||
{
|
||||
if (m_OnEndFrameUpdates != null)
|
||||
m_OnEndFrameUpdates.RemoveAll(callback => callback.Invoke(this, Event.current));
|
||||
}
|
||||
|
||||
public void AddStartFrameDelegate(PendingUpdateDelegate updateDelegate)
|
||||
{
|
||||
if (m_OnStartFrameUpdates == null)
|
||||
m_OnStartFrameUpdates = new List<PendingUpdateDelegate>();
|
||||
if (m_OnStartFrameUpdates.Contains(updateDelegate))
|
||||
return;
|
||||
m_OnStartFrameUpdates.Add(updateDelegate);
|
||||
}
|
||||
|
||||
public void AddEndFrameDelegate(PendingUpdateDelegate updateDelegate)
|
||||
{
|
||||
if (m_OnEndFrameUpdates == null)
|
||||
m_OnEndFrameUpdates = new List<PendingUpdateDelegate>();
|
||||
if (m_OnEndFrameUpdates.Contains(updateDelegate))
|
||||
return;
|
||||
m_OnEndFrameUpdates.Add(updateDelegate);
|
||||
}
|
||||
|
||||
internal void InvokeWindowOnGuiStarted(Event evt)
|
||||
{
|
||||
if (windowOnGuiStarted != null)
|
||||
windowOnGuiStarted.Invoke(this, evt);
|
||||
}
|
||||
|
||||
public void UpdateRootPlayableDuration(double duration)
|
||||
{
|
||||
if (editSequence.director != null)
|
||||
{
|
||||
if (editSequence.director.playableGraph.IsValid())
|
||||
{
|
||||
if (editSequence.director.playableGraph.GetRootPlayableCount() > 0)
|
||||
{
|
||||
var rootPlayable = editSequence.director.playableGraph.GetRootPlayable(0);
|
||||
if (rootPlayable.IsValid())
|
||||
rootPlayable.SetDuration(duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void InvokeTimeChangeCallback()
|
||||
{
|
||||
if (OnTimeChange != null)
|
||||
OnTimeChange.Invoke();
|
||||
}
|
||||
|
||||
PlayRange ValidatePlayRange(PlayRange range, ISequenceState sequenceState)
|
||||
{
|
||||
if (range == TimelineAssetViewModel.NoPlayRangeSet)
|
||||
return range;
|
||||
|
||||
double minimumPlayRangeTime = (0.01 / Math.Max(1.0, sequenceState.frameRate));
|
||||
|
||||
// Validate min
|
||||
if (range.end - range.start < minimumPlayRangeTime)
|
||||
range.start = range.end - minimumPlayRangeTime;
|
||||
|
||||
if (range.start < 0.0)
|
||||
range.start = 0.0;
|
||||
|
||||
// Validate max
|
||||
if (range.end > sequenceState.duration)
|
||||
range.end = sequenceState.duration;
|
||||
|
||||
if (range.end - range.start < minimumPlayRangeTime)
|
||||
range.end = Math.Min(range.start + minimumPlayRangeTime, sequenceState.duration);
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
void EnsureWindowTimeConsistency()
|
||||
{
|
||||
if (masterSequence.director != null && masterSequence.viewModel != null && !ignorePreview)
|
||||
masterSequence.time = masterSequence.viewModel.windowTime;
|
||||
}
|
||||
|
||||
void SynchronizeSequencesAfterPlayback()
|
||||
{
|
||||
// Synchronizing editSequence will synchronize all view models up to the master
|
||||
SynchronizeViewModelTime(editSequence);
|
||||
}
|
||||
|
||||
static void SynchronizeViewModelTime(ISequenceState state)
|
||||
{
|
||||
if (state.director == null || state.viewModel == null)
|
||||
return;
|
||||
|
||||
var t = state.time;
|
||||
state.time = t;
|
||||
}
|
||||
|
||||
// because we may be evaluating outside the duration of the root playable
|
||||
// we explicitly set the time - this causes the graph to not 'advance' the time
|
||||
// because advancing it can force it to change due to wrapping to the duration
|
||||
// This can happen if the graph is force evaluated outside it's duration
|
||||
// case 910114, 936844 and 943377
|
||||
static void ForceTimeOnDirector(PlayableDirector director)
|
||||
{
|
||||
var directorTime = director.time;
|
||||
director.time = directorTime;
|
||||
}
|
||||
|
||||
public bool IsPlayableGraphDone()
|
||||
{
|
||||
return masterSequence.director != null
|
||||
&& masterSequence.director.playableGraph.IsValid()
|
||||
&& masterSequence.director.playableGraph.IsDone();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df8df80bb65e9ec4280229a9921c4f3c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user