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