You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
258 lines
10 KiB
258 lines
10 KiB
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; |
|
} |
|
} |
|
}
|
|
|