259 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			259 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using System.Collections.Generic;
 | |
| using UnityEngine.Playables;
 | |
| 
 | |
| namespace UnityEngine.Timeline
 | |
| {
 | |
|     /// <summary>
 | |
|     /// Use this PlayableBehaviour to send notifications at a given time.
 | |
|     /// </summary>
 | |
|     /// <seealso cref="UnityEngine.Timeline.NotificationFlags"/>
 | |
|     public class TimeNotificationBehaviour : PlayableBehaviour
 | |
|     {
 | |
|         struct NotificationEntry
 | |
|         {
 | |
|             public double time;
 | |
|             public INotification payload;
 | |
|             public bool notificationFired;
 | |
|             public NotificationFlags flags;
 | |
| 
 | |
|             public bool triggerInEditor
 | |
|             {
 | |
|                 get { return (flags & NotificationFlags.TriggerInEditMode) != 0; }
 | |
|             }
 | |
|             public bool prewarm
 | |
|             {
 | |
|                 get { return (flags & NotificationFlags.Retroactive) != 0; }
 | |
|             }
 | |
|             public bool triggerOnce
 | |
|             {
 | |
|                 get { return (flags & NotificationFlags.TriggerOnce) != 0; }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         readonly List<NotificationEntry> m_Notifications = new List<NotificationEntry>();
 | |
|         double m_PreviousTime;
 | |
|         bool m_NeedSortNotifications;
 | |
| 
 | |
|         Playable m_TimeSource;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sets an optional Playable that provides duration and Wrap mode information.
 | |
|         /// </summary>
 | |
|         /// <remarks>
 | |
|         /// timeSource is optional. By default, the duration and Wrap mode will come from the current Playable.
 | |
|         /// </remarks>
 | |
|         public Playable timeSource
 | |
|         {
 | |
|             set { m_TimeSource = value; }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Creates and initializes a ScriptPlayable with a TimeNotificationBehaviour.
 | |
|         /// </summary>
 | |
|         /// <param name="graph">The playable graph.</param>
 | |
|         /// <param name="duration">The duration of the playable.</param>
 | |
|         /// <param name="loopMode">The loop mode of the playable.</param>
 | |
|         /// <returns>A new TimeNotificationBehaviour linked to the PlayableGraph.</returns>
 | |
|         public static ScriptPlayable<TimeNotificationBehaviour> Create(PlayableGraph graph, double duration, DirectorWrapMode loopMode)
 | |
|         {
 | |
|             var notificationsPlayable = ScriptPlayable<TimeNotificationBehaviour>.Create(graph);
 | |
|             notificationsPlayable.SetDuration(duration);
 | |
|             notificationsPlayable.SetTimeWrapMode(loopMode);
 | |
|             notificationsPlayable.SetPropagateSetTime(true);
 | |
|             return notificationsPlayable;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Adds a notification to be sent with flags, at a specific time.
 | |
|         /// </summary>
 | |
|         /// <param name="time">The time to send the notification.</param>
 | |
|         /// <param name="payload">The notification.</param>
 | |
|         /// <param name="flags">The notification flags that determine the notification behaviour. This parameter is set to Retroactive by default.</param>
 | |
|         /// <seealso cref="UnityEngine.Timeline.NotificationFlags"/>
 | |
|         public void AddNotification(double time, INotification payload, NotificationFlags flags = NotificationFlags.Retroactive)
 | |
|         {
 | |
|             m_Notifications.Add(new NotificationEntry
 | |
|             {
 | |
|                 time = time,
 | |
|                 payload = payload,
 | |
|                 flags = flags
 | |
|             });
 | |
|             m_NeedSortNotifications = true;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// This method is called when the PlayableGraph that owns this PlayableBehaviour starts.
 | |
|         /// </summary>
 | |
|         /// <param name="playable">The reference to the playable associated with this PlayableBehaviour.</param>
 | |
|         public override void OnGraphStart(Playable playable)
 | |
|         {
 | |
|             SortNotifications();
 | |
|             var currentTime = playable.GetTime();
 | |
|             for (var i = 0; i < m_Notifications.Count; i++)
 | |
|             {
 | |
|                 // case 1257208 - when a timeline is _resumed_, only reset notifications after the resumed time
 | |
|                 if (m_Notifications[i].time > currentTime && !m_Notifications[i].triggerOnce)
 | |
|                 {
 | |
|                     var notification = m_Notifications[i];
 | |
|                     notification.notificationFired = false;
 | |
|                     m_Notifications[i] = notification;
 | |
|                 }
 | |
|             }
 | |
|             m_PreviousTime = playable.GetTime();
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// This method is called when the Playable play state is changed to PlayState.Paused
 | |
|         /// </summary>
 | |
|         /// <param name="playable">The reference to the playable associated with this PlayableBehaviour.</param>
 | |
|         /// <param name="info">Playable context information such as weight, evaluationType, and so on.</param>
 | |
|         public override void OnBehaviourPause(Playable playable, FrameData info)
 | |
|         {
 | |
|             if (playable.IsDone())
 | |
|             {
 | |
|                 SortNotifications();
 | |
|                 for (var i = 0; i < m_Notifications.Count; i++)
 | |
|                 {
 | |
|                     var e = m_Notifications[i];
 | |
|                     if (!e.notificationFired)
 | |
|                     {
 | |
|                         var duration = playable.GetDuration();
 | |
|                         var canTrigger = m_PreviousTime <= e.time && e.time <= duration;
 | |
|                         if (canTrigger)
 | |
|                         {
 | |
|                             Trigger_internal(playable, info.output, ref e);
 | |
|                             m_Notifications[i] = e;
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// This method is called during the PrepareFrame phase of the PlayableGraph.
 | |
|         /// </summary>
 | |
|         /// <remarks>
 | |
|         /// Called once before processing starts.
 | |
|         /// </remarks>
 | |
|         /// <param name="playable">The reference to the playable associated with this PlayableBehaviour.</param>
 | |
|         /// <param name="info">Playable context information such as weight, evaluationType, and so on.</param>
 | |
|         public override void PrepareFrame(Playable playable, FrameData info)
 | |
|         {
 | |
|             // Never trigger on scrub
 | |
|             if (info.evaluationType == FrameData.EvaluationType.Evaluate)
 | |
|             {
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             SyncDurationWithExternalSource(playable);
 | |
|             SortNotifications();
 | |
|             var currentTime = playable.GetTime();
 | |
| 
 | |
|             // Fire notifications from previousTime till the end
 | |
|             if (info.timeLooped)
 | |
|             {
 | |
|                 var duration = playable.GetDuration();
 | |
|                 TriggerNotificationsInRange(m_PreviousTime, duration, info, playable, true);
 | |
|                 var dx = playable.GetDuration() - m_PreviousTime;
 | |
|                 var nFullTimelines = (int)((info.deltaTime * info.effectiveSpeed - dx) / playable.GetDuration());
 | |
|                 for (var i = 0; i < nFullTimelines; i++)
 | |
|                 {
 | |
|                     TriggerNotificationsInRange(0, duration, info, playable, false);
 | |
|                 }
 | |
|                 TriggerNotificationsInRange(0, currentTime, info, playable, false);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 var pt = playable.GetTime();
 | |
|                 TriggerNotificationsInRange(m_PreviousTime, pt, info,
 | |
|                     playable, true);
 | |
|             }
 | |
| 
 | |
|             for (var i = 0; i < m_Notifications.Count; ++i)
 | |
|             {
 | |
|                 var e = m_Notifications[i];
 | |
|                 if (e.notificationFired && CanRestoreNotification(e, info, currentTime, m_PreviousTime))
 | |
|                 {
 | |
|                     Restore_internal(ref e);
 | |
|                     m_Notifications[i] = e;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             m_PreviousTime = playable.GetTime();
 | |
|         }
 | |
| 
 | |
|         void SortNotifications()
 | |
|         {
 | |
|             if (m_NeedSortNotifications)
 | |
|             {
 | |
|                 m_Notifications.Sort((x, y) => x.time.CompareTo(y.time));
 | |
|                 m_NeedSortNotifications = false;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         static bool CanRestoreNotification(NotificationEntry e, FrameData info, double currentTime, double previousTime)
 | |
|         {
 | |
|             if (e.triggerOnce)
 | |
|                 return false;
 | |
|             if (info.timeLooped)
 | |
|                 return true;
 | |
| 
 | |
|             //case 1111595: restore the notification if the time is manually set before it
 | |
|             return previousTime > currentTime && currentTime <= e.time;
 | |
|         }
 | |
| 
 | |
|         void TriggerNotificationsInRange(double start, double end, FrameData info, Playable playable, bool checkState)
 | |
|         {
 | |
|             if (start <= end)
 | |
|             {
 | |
|                 var playMode = Application.isPlaying;
 | |
|                 for (var i = 0; i < m_Notifications.Count; i++)
 | |
|                 {
 | |
|                     var e = m_Notifications[i];
 | |
|                     if (e.notificationFired && (checkState || e.triggerOnce))
 | |
|                         continue;
 | |
| 
 | |
|                     var notificationTime = e.time;
 | |
|                     if (e.prewarm && notificationTime < end && (e.triggerInEditor || playMode))
 | |
|                     {
 | |
|                         Trigger_internal(playable, info.output, ref e);
 | |
|                         m_Notifications[i] = e;
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         if (notificationTime < start || notificationTime > end)
 | |
|                             continue;
 | |
| 
 | |
|                         if (e.triggerInEditor || playMode)
 | |
|                         {
 | |
|                             Trigger_internal(playable, info.output, ref e);
 | |
|                             m_Notifications[i] = e;
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void SyncDurationWithExternalSource(Playable playable)
 | |
|         {
 | |
|             if (m_TimeSource.IsValid())
 | |
|             {
 | |
|                 playable.SetDuration(m_TimeSource.GetDuration());
 | |
|                 playable.SetTimeWrapMode(m_TimeSource.GetTimeWrapMode());
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         static void Trigger_internal(Playable playable, PlayableOutput output, ref NotificationEntry e)
 | |
|         {
 | |
|             output.PushNotification(playable, e.payload);
 | |
|             e.notificationFired = true;
 | |
|         }
 | |
| 
 | |
|         static void Restore_internal(ref NotificationEntry e)
 | |
|         {
 | |
|             e.notificationFired = false;
 | |
|         }
 | |
|     }
 | |
| }
 |