| using System; |
| using System.Collections.Generic; |
| using UnityEngine; |
| using UnityEngine.Animations; |
| using UnityEngine.Audio; |
| using UnityEngine.Playables; |
|
|
| namespace UnityEngine.Timeline |
| { |
| |
| internal interface ITimelineEvaluateCallback |
| { |
| void Evaluate(); |
| } |
|
|
|
|
| #if UNITY_EDITOR |
| |
| |
| |
| class IntervalTreeRebalancer |
| { |
| private IntervalTree<RuntimeElement> m_Tree; |
| public IntervalTreeRebalancer(IntervalTree<RuntimeElement> tree) |
| { |
| m_Tree = tree; |
| } |
|
|
| public bool Rebalance() |
| { |
| m_Tree.UpdateIntervals(); |
| return m_Tree.dirty; |
| } |
| } |
| #endif |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| |
| |
| |
| public class TimelinePlayable : PlayableBehaviour |
| { |
| private IntervalTree<RuntimeElement> m_IntervalTree = new IntervalTree<RuntimeElement>(); |
| private List<RuntimeElement> m_ActiveClips = new List<RuntimeElement>(); |
| private List<RuntimeElement> m_CurrentListOfActiveClips; |
| private int m_ActiveBit = 0; |
|
|
| private List<ITimelineEvaluateCallback> m_EvaluateCallbacks = new List<ITimelineEvaluateCallback>(); |
|
|
| private Dictionary<TrackAsset, Playable> m_PlayableCache = new Dictionary<TrackAsset, Playable>(); |
|
|
| internal static bool muteAudioScrubbing = true; |
|
|
| #if UNITY_EDITOR |
| private IntervalTreeRebalancer m_Rebalancer; |
| internal static event Action<Playable> playableLooped; |
| #endif |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public static ScriptPlayable<TimelinePlayable> Create(PlayableGraph graph, IEnumerable<TrackAsset> tracks, GameObject go, bool autoRebalance, bool createOutputs) |
| { |
| if (tracks == null) |
| throw new ArgumentNullException("Tracks list is null", "tracks"); |
|
|
| if (go == null) |
| throw new ArgumentNullException("GameObject parameter is null", "go"); |
|
|
| var playable = ScriptPlayable<TimelinePlayable>.Create(graph); |
| playable.SetTraversalMode(PlayableTraversalMode.Passthrough); |
| var sequence = playable.GetBehaviour(); |
| sequence.Compile(graph, playable, tracks, go, autoRebalance, createOutputs); |
| return playable; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public void Compile(PlayableGraph graph, Playable timelinePlayable, IEnumerable<TrackAsset> tracks, GameObject go, bool autoRebalance, bool createOutputs) |
| { |
| if (tracks == null) |
| throw new ArgumentNullException("Tracks list is null", "tracks"); |
|
|
| if (go == null) |
| throw new ArgumentNullException("GameObject parameter is null", "go"); |
|
|
| var outputTrackList = new List<TrackAsset>(tracks); |
| var maximumNumberOfIntersections = outputTrackList.Count * 2 + outputTrackList.Count; |
| m_CurrentListOfActiveClips = new List<RuntimeElement>(maximumNumberOfIntersections); |
| m_ActiveClips = new List<RuntimeElement>(maximumNumberOfIntersections); |
|
|
| m_EvaluateCallbacks.Clear(); |
| m_PlayableCache.Clear(); |
|
|
| CompileTrackList(graph, timelinePlayable, outputTrackList, go, createOutputs); |
|
|
| #if UNITY_EDITOR |
| if (autoRebalance) |
| { |
| m_Rebalancer = new IntervalTreeRebalancer(m_IntervalTree); |
| } |
| #endif |
| } |
|
|
| private void CompileTrackList(PlayableGraph graph, Playable timelinePlayable, IEnumerable<TrackAsset> tracks, GameObject go, bool createOutputs) |
| { |
| foreach (var track in tracks) |
| { |
| if (!track.IsCompilable()) |
| continue; |
|
|
| if (!m_PlayableCache.ContainsKey(track)) |
| { |
| track.SortClips(); |
| CreateTrackPlayable(graph, timelinePlayable, track, go, createOutputs); |
| } |
| } |
| } |
|
|
| void CreateTrackOutput(PlayableGraph graph, TrackAsset track, GameObject go, Playable playable, int port) |
| { |
| if (track.isSubTrack) |
| return; |
|
|
| var bindings = track.outputs; |
| foreach (var binding in bindings) |
| { |
| var playableOutput = binding.CreateOutput(graph); |
| playableOutput.SetReferenceObject(binding.sourceObject); |
| playableOutput.SetSourcePlayable(playable, port); |
| playableOutput.SetWeight(1.0f); |
|
|
| |
| if (track as AnimationTrack != null) |
| { |
| EvaluateWeightsForAnimationPlayableOutput(track, (AnimationPlayableOutput)playableOutput); |
| #if UNITY_EDITOR |
| if (!Application.isPlaying) |
| EvaluateAnimationPreviewUpdateCallback(track, (AnimationPlayableOutput)playableOutput); |
| #endif |
| } |
| if (playableOutput.IsPlayableOutputOfType<AudioPlayableOutput>()) |
| ((AudioPlayableOutput)playableOutput).SetEvaluateOnSeek(!muteAudioScrubbing); |
|
|
| |
| if (track.timelineAsset.markerTrack == track) |
| { |
| var director = go.GetComponent<PlayableDirector>(); |
| playableOutput.SetUserData(director); |
| foreach (var c in go.GetComponents<INotificationReceiver>()) |
| { |
| playableOutput.AddNotificationReceiver(c); |
| } |
| } |
| } |
| } |
|
|
| void EvaluateWeightsForAnimationPlayableOutput(TrackAsset track, AnimationPlayableOutput animOutput) |
| { |
| m_EvaluateCallbacks.Add(new AnimationOutputWeightProcessor(animOutput)); |
| } |
|
|
| void EvaluateAnimationPreviewUpdateCallback(TrackAsset track, AnimationPlayableOutput animOutput) |
| { |
| m_EvaluateCallbacks.Add(new AnimationPreviewUpdateCallback(animOutput)); |
| } |
|
|
| Playable CreateTrackPlayable(PlayableGraph graph, Playable timelinePlayable, TrackAsset track, GameObject go, bool createOutputs) |
| { |
| if (!track.IsCompilable()) |
| return timelinePlayable; |
|
|
| Playable playable; |
| if (m_PlayableCache.TryGetValue(track, out playable)) |
| return playable; |
|
|
| if (track.name == "root") |
| return timelinePlayable; |
|
|
| TrackAsset parentActor = track.parent as TrackAsset; |
| var parentPlayable = parentActor != null ? CreateTrackPlayable(graph, timelinePlayable, parentActor, go, createOutputs) : timelinePlayable; |
| var actorPlayable = track.CreatePlayableGraph(graph, go, m_IntervalTree, timelinePlayable); |
| bool connected = false; |
|
|
| if (!actorPlayable.IsValid()) |
| { |
| |
| throw new InvalidOperationException(track.name + "(" + track.GetType() + ") did not produce a valid playable."); |
| } |
|
|
|
|
| |
| if (parentPlayable.IsValid() && actorPlayable.IsValid()) |
| { |
| int port = parentPlayable.GetInputCount(); |
| parentPlayable.SetInputCount(port + 1); |
| connected = graph.Connect(actorPlayable, 0, parentPlayable, port); |
| parentPlayable.SetInputWeight(port, 1.0f); |
| } |
|
|
| if (createOutputs && connected) |
| { |
| CreateTrackOutput(graph, track, go, parentPlayable, parentPlayable.GetInputCount() - 1); |
| } |
|
|
| CacheTrack(track, actorPlayable, connected ? (parentPlayable.GetInputCount() - 1) : -1, parentPlayable); |
| return actorPlayable; |
| } |
|
|
| |
| |
| |
| |
| |
| public override void PrepareFrame(Playable playable, FrameData info) |
| { |
| #if UNITY_EDITOR |
| if (m_Rebalancer != null) |
| m_Rebalancer.Rebalance(); |
| |
| |
| if (playableLooped != null && info.timeLooped) |
| playableLooped.Invoke(playable); |
| #endif |
|
|
| |
| |
| |
| Evaluate(playable, info); |
| } |
|
|
| private void Evaluate(Playable playable, FrameData frameData) |
| { |
| if (m_IntervalTree == null) |
| return; |
|
|
| double localTime = playable.GetTime(); |
| m_ActiveBit = m_ActiveBit == 0 ? 1 : 0; |
|
|
| m_CurrentListOfActiveClips.Clear(); |
| m_IntervalTree.IntersectsWith(DiscreteTime.GetNearestTick(localTime), m_CurrentListOfActiveClips); |
|
|
| foreach (var c in m_CurrentListOfActiveClips) |
| { |
| c.intervalBit = m_ActiveBit; |
| } |
|
|
| |
| |
| var timelineEnd = (double)new DiscreteTime(playable.GetDuration()); |
| foreach (var c in m_ActiveClips) |
| { |
| if (c.intervalBit != m_ActiveBit) |
| c.DisableAt(localTime, timelineEnd, frameData); |
| } |
|
|
| m_ActiveClips.Clear(); |
| |
| for (var a = 0; a < m_CurrentListOfActiveClips.Count; a++) |
| { |
| m_CurrentListOfActiveClips[a].EvaluateAt(localTime, frameData); |
| m_ActiveClips.Add(m_CurrentListOfActiveClips[a]); |
| } |
|
|
| int count = m_EvaluateCallbacks.Count; |
| for (int i = 0; i < count; i++) |
| { |
| m_EvaluateCallbacks[i].Evaluate(); |
| } |
| } |
|
|
| private void CacheTrack(TrackAsset track, Playable playable, int port, Playable parent) |
| { |
| m_PlayableCache[track] = playable; |
| } |
|
|
| |
| static void ForAOTCompilationOnly() |
| { |
| new List<IntervalTree<RuntimeElement>.Entry>(); |
| } |
| } |
| } |
|
|