A game about forced loneliness, made by TACStudios
at master 258 lines 10 kB view raw
1using System; 2using System.Collections.Generic; 3using UnityEngine.Playables; 4 5namespace UnityEngine.Timeline 6{ 7 /// <summary> 8 /// Use this PlayableBehaviour to send notifications at a given time. 9 /// </summary> 10 /// <seealso cref="UnityEngine.Timeline.NotificationFlags"/> 11 public class TimeNotificationBehaviour : PlayableBehaviour 12 { 13 struct NotificationEntry 14 { 15 public double time; 16 public INotification payload; 17 public bool notificationFired; 18 public NotificationFlags flags; 19 20 public bool triggerInEditor 21 { 22 get { return (flags & NotificationFlags.TriggerInEditMode) != 0; } 23 } 24 public bool prewarm 25 { 26 get { return (flags & NotificationFlags.Retroactive) != 0; } 27 } 28 public bool triggerOnce 29 { 30 get { return (flags & NotificationFlags.TriggerOnce) != 0; } 31 } 32 } 33 34 readonly List<NotificationEntry> m_Notifications = new List<NotificationEntry>(); 35 double m_PreviousTime; 36 bool m_NeedSortNotifications; 37 38 Playable m_TimeSource; 39 40 /// <summary> 41 /// Sets an optional Playable that provides duration and Wrap mode information. 42 /// </summary> 43 /// <remarks> 44 /// timeSource is optional. By default, the duration and Wrap mode will come from the current Playable. 45 /// </remarks> 46 public Playable timeSource 47 { 48 set { m_TimeSource = value; } 49 } 50 51 /// <summary> 52 /// Creates and initializes a ScriptPlayable with a TimeNotificationBehaviour. 53 /// </summary> 54 /// <param name="graph">The playable graph.</param> 55 /// <param name="duration">The duration of the playable.</param> 56 /// <param name="loopMode">The loop mode of the playable.</param> 57 /// <returns>A new TimeNotificationBehaviour linked to the PlayableGraph.</returns> 58 public static ScriptPlayable<TimeNotificationBehaviour> Create(PlayableGraph graph, double duration, DirectorWrapMode loopMode) 59 { 60 var notificationsPlayable = ScriptPlayable<TimeNotificationBehaviour>.Create(graph); 61 notificationsPlayable.SetDuration(duration); 62 notificationsPlayable.SetTimeWrapMode(loopMode); 63 notificationsPlayable.SetPropagateSetTime(true); 64 return notificationsPlayable; 65 } 66 67 /// <summary> 68 /// Adds a notification to be sent with flags, at a specific time. 69 /// </summary> 70 /// <param name="time">The time to send the notification.</param> 71 /// <param name="payload">The notification.</param> 72 /// <param name="flags">The notification flags that determine the notification behaviour. This parameter is set to Retroactive by default.</param> 73 /// <seealso cref="UnityEngine.Timeline.NotificationFlags"/> 74 public void AddNotification(double time, INotification payload, NotificationFlags flags = NotificationFlags.Retroactive) 75 { 76 m_Notifications.Add(new NotificationEntry 77 { 78 time = time, 79 payload = payload, 80 flags = flags 81 }); 82 m_NeedSortNotifications = true; 83 } 84 85 /// <summary> 86 /// This method is called when the PlayableGraph that owns this PlayableBehaviour starts. 87 /// </summary> 88 /// <param name="playable">The reference to the playable associated with this PlayableBehaviour.</param> 89 public override void OnGraphStart(Playable playable) 90 { 91 SortNotifications(); 92 var currentTime = playable.GetTime(); 93 for (var i = 0; i < m_Notifications.Count; i++) 94 { 95 // case 1257208 - when a timeline is _resumed_, only reset notifications after the resumed time 96 if (m_Notifications[i].time > currentTime && !m_Notifications[i].triggerOnce) 97 { 98 var notification = m_Notifications[i]; 99 notification.notificationFired = false; 100 m_Notifications[i] = notification; 101 } 102 } 103 m_PreviousTime = playable.GetTime(); 104 } 105 106 /// <summary> 107 /// This method is called when the Playable play state is changed to PlayState.Paused 108 /// </summary> 109 /// <param name="playable">The reference to the playable associated with this PlayableBehaviour.</param> 110 /// <param name="info">Playable context information such as weight, evaluationType, and so on.</param> 111 public override void OnBehaviourPause(Playable playable, FrameData info) 112 { 113 if (playable.IsDone()) 114 { 115 SortNotifications(); 116 for (var i = 0; i < m_Notifications.Count; i++) 117 { 118 var e = m_Notifications[i]; 119 if (!e.notificationFired) 120 { 121 var duration = playable.GetDuration(); 122 var canTrigger = m_PreviousTime <= e.time && e.time <= duration; 123 if (canTrigger) 124 { 125 Trigger_internal(playable, info.output, ref e); 126 m_Notifications[i] = e; 127 } 128 } 129 } 130 } 131 } 132 133 /// <summary> 134 /// This method is called during the PrepareFrame phase of the PlayableGraph. 135 /// </summary> 136 /// <remarks> 137 /// Called once before processing starts. 138 /// </remarks> 139 /// <param name="playable">The reference to the playable associated with this PlayableBehaviour.</param> 140 /// <param name="info">Playable context information such as weight, evaluationType, and so on.</param> 141 public override void PrepareFrame(Playable playable, FrameData info) 142 { 143 // Never trigger on scrub 144 if (info.evaluationType == FrameData.EvaluationType.Evaluate) 145 { 146 return; 147 } 148 149 SyncDurationWithExternalSource(playable); 150 SortNotifications(); 151 var currentTime = playable.GetTime(); 152 153 // Fire notifications from previousTime till the end 154 if (info.timeLooped) 155 { 156 var duration = playable.GetDuration(); 157 TriggerNotificationsInRange(m_PreviousTime, duration, info, playable, true); 158 var dx = playable.GetDuration() - m_PreviousTime; 159 var nFullTimelines = (int)((info.deltaTime * info.effectiveSpeed - dx) / playable.GetDuration()); 160 for (var i = 0; i < nFullTimelines; i++) 161 { 162 TriggerNotificationsInRange(0, duration, info, playable, false); 163 } 164 TriggerNotificationsInRange(0, currentTime, info, playable, false); 165 } 166 else 167 { 168 var pt = playable.GetTime(); 169 TriggerNotificationsInRange(m_PreviousTime, pt, info, 170 playable, true); 171 } 172 173 for (var i = 0; i < m_Notifications.Count; ++i) 174 { 175 var e = m_Notifications[i]; 176 if (e.notificationFired && CanRestoreNotification(e, info, currentTime, m_PreviousTime)) 177 { 178 Restore_internal(ref e); 179 m_Notifications[i] = e; 180 } 181 } 182 183 m_PreviousTime = playable.GetTime(); 184 } 185 186 void SortNotifications() 187 { 188 if (m_NeedSortNotifications) 189 { 190 m_Notifications.Sort((x, y) => x.time.CompareTo(y.time)); 191 m_NeedSortNotifications = false; 192 } 193 } 194 195 static bool CanRestoreNotification(NotificationEntry e, FrameData info, double currentTime, double previousTime) 196 { 197 if (e.triggerOnce) 198 return false; 199 if (info.timeLooped) 200 return true; 201 202 //case 1111595: restore the notification if the time is manually set before it 203 return previousTime > currentTime && currentTime <= e.time; 204 } 205 206 void TriggerNotificationsInRange(double start, double end, FrameData info, Playable playable, bool checkState) 207 { 208 if (start <= end) 209 { 210 var playMode = Application.isPlaying; 211 for (var i = 0; i < m_Notifications.Count; i++) 212 { 213 var e = m_Notifications[i]; 214 if (e.notificationFired && (checkState || e.triggerOnce)) 215 continue; 216 217 var notificationTime = e.time; 218 if (e.prewarm && notificationTime < end && (e.triggerInEditor || playMode)) 219 { 220 Trigger_internal(playable, info.output, ref e); 221 m_Notifications[i] = e; 222 } 223 else 224 { 225 if (notificationTime < start || notificationTime > end) 226 continue; 227 228 if (e.triggerInEditor || playMode) 229 { 230 Trigger_internal(playable, info.output, ref e); 231 m_Notifications[i] = e; 232 } 233 } 234 } 235 } 236 } 237 238 void SyncDurationWithExternalSource(Playable playable) 239 { 240 if (m_TimeSource.IsValid()) 241 { 242 playable.SetDuration(m_TimeSource.GetDuration()); 243 playable.SetTimeWrapMode(m_TimeSource.GetTimeWrapMode()); 244 } 245 } 246 247 static void Trigger_internal(Playable playable, PlayableOutput output, ref NotificationEntry e) 248 { 249 output.PushNotification(playable, e.payload); 250 e.notificationFired = true; 251 } 252 253 static void Restore_internal(ref NotificationEntry e) 254 { 255 e.notificationFired = false; 256 } 257 } 258}