lets say I have a marker at position 10, but right at position 5 I call director.time = 15, this will make the timeline jump to position 15 and will skip the marker at position 10. Problem is that marker still gets evaluated as if it were hit at position 15, I even verified the flags of the marker doesn't include Retroactive. I appreciate any help. Thanks
This is a known issue. The problem is the timeline is still playing and the jump in time crosses the signal boundary in a single frame. Retroactive is where the timeline starts at a time after a signal time. A workaround is to pause the timeline, set the time, then resume it. For example: Code (CSharp): public void JumpTimeBy10Seconds() { var time = GetComponent<PlayableDirector>().time; GetComponent<PlayableDirector>().Pause(); GetComponent<PlayableDirector>().time = time + 10; GetComponent<PlayableDirector>().Play(); }
Thanks, I dont think that would work because the order in which signals are processed when the graph is evaluated which is single threaded after lateUpdate. Meaning that pausing, changing time and resuming will happen in a single frame and then the other signal would hit and still be evaluated. I found another workaround, when processing the notification I check if the time it was triggered is the same time as the position of the signal, like this: Code (CSharp): private static bool ShouldEvaluate(ZNETimeMachineMarker marker, double notificationTime) { // Check if this was evaluated at the position the marker is // This is needed because when skipping it could be evaluated at the skip position instead. if (Math.Abs(marker.time - notificationTime) < 0.1F) return true; return marker.flags.HasFlag(NotificationFlags.Retroactive); } And then when receiving the notification just checking like this: Code (CSharp): public void OnNotify(Playable origin, INotification notification, object context) { if (!Application.isPlaying) return; if (!(notification is ZNETimeMachineMarker marker)) return; var time = origin.IsValid() ? origin.GetTime() : 0.0F; if (!ShouldEvaluate(marker, time)) return; This has been working fine, I really hope Unity fixes this issue soon anyway.
Ok. I did build a small repo of the solution I posted and it worked correctly - the time jump wouldn't take effect until the next frame because Play is called after the graphs are already updated. However, if your solution works for your use case, great! As for a 'fix', whether the current implementation is incorrect is debatable - the timeline continues to play and crosses the signal boundary in a single frame. However, there may be something that we can do to easily allow the behavior you are seeking.
Hi @seant_unity ! I implemented an extension method called "Jump" that essentially implements your solution, with some additional bells and whistles (force-evaluating immediately, checking for whether the timeline is paused, etc.). This works well. Code (CSharp): public static void Jump(this PlayableDirector director, double time) { EnsureArg.IsNotNull(director); var wasPlaying = director.state == PlayState.Playing; if (wasPlaying) { director.Pause(); director.time = time; director.Evaluate(); director.Resume(); } else { director.time = time; director.Evaluate(); } } However, the markers defined exactly on the target jump time do not seem to get triggered when a timeline resumes after a jump. I'm assuming the underlying logic is "play the marker if the time crosses the marker while playing", thus because we just resumed, that condition is false. I therefore need to find a way to trigger the markers manually. I wrote this super hacky piece of logic that works for now, but I'm wondering if you could advise something cleaner: Code (CSharp): public static void Jump(this PlayableDirector director, double time) { EnsureArg.IsNotNull(director); var wasPlaying = director.state == PlayState.Playing; if (wasPlaying) { director.Pause(); director.time = time; director.Evaluate(); director.Resume(); // HACK: Unity does not trigger markers when resuming the timeline on the exact frame on which they are defined. if (director.playableAsset is TimelineAsset timeline) { foreach (var marker in timeline.markerTrack.GetMarkers()) { if (marker is INotification notification && marker.time == time) { foreach (var directorReceiver in director.GetComponents<INotificationReceiver>()) { directorReceiver.OnNotify(director.playableGraph.GetRootPlayable(0), notification, null); } } } } } else { director.time = time; director.Evaluate(); } } For example, this won't support markers on tracks other than the root track, which would be expected. (Also, I'm not sure which arguments to pass to playable and context of OnNotify).
The pause-set time-evaluate-play solution doesn't work for me - the OnNotify events still happen after all that has happened - I needed to use the Hazneliel solution