Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Is there a way to guarantee the timeline will pause without crossing a certain threshold?

Discussion in 'Timeline' started by jmdodge, Sep 13, 2019.

  1. jmdodge

    jmdodge

    Joined:
    Jan 3, 2019
    Posts:
    3
    Not sure of a better way to word the title, but basically what I'm doing is this:

    I have a timeline that's meant to be an interactable series of events. For illustration purposes, say:
    1. A box appears
    2. A clip on the timeline executes an arbitrary event that says: pause the timeline until a button is pushed.
    During this time, the box should stay visible.
    3. Pushing the button resumes the timeline, which deactivates the box so that it disappears.

    The problem I'm happening is that by the time the event telling the timeline to pause has executed, my box has already disappeared because the play head has moved past the point where it was supposed to stop.


    I could create a longer "buffer zone" to catch the pause command, but that also means that when a button is pushed, there is a little delay as it plays through the buffer zone to the next part. This is a pretty brittle solution anyway and if the frame rate goes low enough may not even work.

    I tried changing the "Update Method" of the playable director to "Unscaled Game Time", and this seems to reliably get the timeline to pause on the same frame as the pause event, but I don't understand it well enough to feel confident that this is going to work reliably if the frame rate of the game gets wonky.

    I should say also, I'm using an asset from the asset store to execute my events on the timeline: https://assetstore.unity.com/packages/3d/characters/timeline-events-115300

    It uses clips that are configured to execute a unity event. Not sure if there would be any difference with using markers or the built in "signal" system.


    Any advice is appreciated, thanks!
     
  2. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    In general no, at least not in a single timeline. The reason is exactly what you observe - there is no guarantee of landing at an exact time. Timeline is sample at intervals determined by the current frame rate, which can fluctuate.

    An alternative is to manually advance using smaller timesteps (similar to fixed update loop), but this has a few drawbacks. In particular, Timelines signals won’t fire from manual updates, nor will audio play correctly.

    The other alternatives are to use multiple timelines, or some of the time jump mechanisms found here.
     
  3. jmdodge

    jmdodge

    Joined:
    Jan 3, 2019
    Posts:
    3
    Thank you for your reply!

    Regarding the "time jump" mechanisms you referenced in the blog post, that seems like the ideal solution for me - but I'm a little confused.

    In the first portion of the post there is "Idea 1 – Stopping the Timeline", wherein the author pauses the timeline while dialogue is visible and waits for the user to press the spacebar in order to advance. This is exactly what I'm trying to do!

    What is it that prevents the timeline stopping at exactly the right spot in this instance? In order to work as intended, the dialogue box needs to remain visible on the screen, but if the playhead were to go a single frame beyond the stop point, presumably the dialogue box would be deactivated or no longer visible.

    Supposing the above is accomplished by rewinding the playhead back to the exact point on the timeline where it was supposed to stop, what prevents events from occurring that may be on the subsequent frames (like sounds playing or some game event being invoked)? In other words, suppose I want to stop on frame 5, and on frame 6 I trigger a spawn event, but Unity doesn't sample the timeline until frame 7. In that case it could stop and rewind back to frame 5, but it can't "undo" the spawn event on frame 6, right? Or maybe there's a way to stop the cascade of events on passed frames from running once you say "stop"?

    Thanks in advance for your time!
     
  4. ewanuno

    ewanuno

    Joined:
    May 11, 2017
    Posts:
    58
    from the unity blog post about timeline signals (https://blogs.unity3d.com/2019/05/21/how-to-use-timeline-signals/)

    "Are signals guaranteed to be emitted? Yes. Signals will not be skipped; they will be emitted on the next frame regardless of the game’s FPS."


    i'm doing something very similar, (pause signal stops the main timeline) pointer click restarts it via an event trigger on a UI panel.

    knowing that there might be a frame or so of delay before the timeline stops,(and potentially when it starts again)
    i leave at least 5 frames between the marker and the next clip on the timeline just to be safe, it seems to be working fine so far, but i haven't really tested on drastically underpowered devices yet.
     
  5. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    There is no way to 'undo' signals/events. Adding in padding is one way to do it, but as ewanuno mentioned it can be fragile because of frame rate.

    Instead of spawning, you can use an activation clip on a pre-spawned object in the scene. In that case, rewinding would work, unless the spawned object causes another object to change in some way.

    If you need to rewind, then you need to make sure the timeline is deterministic. Signals break determinism - they cause a state change that timeline does not know how to restore. The time machine example is deterministic.
     
  6. jmdodge

    jmdodge

    Joined:
    Jan 3, 2019
    Posts:
    3
    So my options are:
    1. Add some padding at stop points to ensure the timeline doesn't end up passing into an undesirable state. Live with the fact that a crazy bad frame rate could still break this. Use markers to jump ahead to the next section when triggered.
    2. Handle overrun stop points by rewinding, but this won't work with signals or other arbitrary events because there's no way to prevent them from executing if the play head overruns a stop point where they occur (I'm using these types of signals).
    3. Multiple timelines. I think this would cause me some problems with managing the state of the objects that are in the timeline, e.g.holding vs resetting position of the bound objects and how the interaction would be with several timelines working in sequence... I have to think about that some more I guess. Also it somewhat spoils the convenience and ease-of-use that this approach offers in the first place - I like being able to work with a long/complicated sequence in one place.

    Honestly, none of these really feel like great or elegant solutions, but I suppose something is workable.

    Is there any other recommended approach for what I'm trying to do? Working with long, complex animation sequences where there is some dynamic user interaction in between sections of that sequence?
     
  7. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    So, the trick here would be to prevent the time from moving past a certain point. This is exactly what the Hold option on the playable director does.

    Seeing that none of those you've listed are really good options, I made a little test track/clip that modifies the duration of the timeline and the wrap mode on the fly. The clip area is the time to accept input, if no input is given then it waits at the end of the clip.

    I hope that helps.
     

    Attached Files:

    Yuchen_Chang and seoyeon222222 like this.
  8. CyRaid

    CyRaid

    Joined:
    Mar 31, 2015
    Posts:
    134
    Hold/Altering duration from script is a very hacky way of doing it. Why couldn't you just make a guaranteed 'stop point' where while it gets the sampled time, it'd set it to the stop point, then do the processing using the 'stopped' value? This way at least there wouldn't need to be any 'padding' and you know signals could stop at guaranteed time points (like 4.00000). Perhaps make an option on a signal. How do I know this works? I've developed a timeline component for a game engine I made a long while back.
     
    Midiphony-panda likes this.
  9. CyRaid

    CyRaid

    Joined:
    Mar 31, 2015
    Posts:
    134
    I made a couple simple scripts which kinda did what I was looking for. I made a PauseMarker that you can put on your timeline: upload_2021-10-2_6-30-39.png .. Which will set the PlayableDirector time to whatever the PauseMarker was set on, and calls evaluate and pause.

    Seems to work for me for now.. Another better solution would have been to try and use a manual time advance and a OnUpdate() to advance the time, and evaluate on a LateUpdate() but using the adjusted time (from a PauseMarker for example).

    Do note that putting PauseReceiver on the Object with PlayableDirector does not work I've found, but putting it on a different object does.
     

    Attached Files:

  10. Midiphony-panda

    Midiphony-panda

    Joined:
    Feb 10, 2020
    Posts:
    242
    @CyRaid I would have implemented the same way you did :)

    However, your PauseReceiver works as expected when put on a GameObject with a PlayableDirector. And nothing happens when the PauseReceiver component is put on another GameObject than the one with the running PlayableDirector. I think this is the expected behaviour for INotificationReceivers implementations, you might want to check your setup.

    My understanding of the INotificationReceiver interface is that it receives notifications from the PlayableDirector only if it is on the same GameObject.
     
    Last edited: Oct 4, 2021
  11. CyRaid

    CyRaid

    Joined:
    Mar 31, 2015
    Posts:
    134
    Weird! haha .. Yeah no it didn't work when put on the GameObject with PlayableDirector.. But then again, the receiver is put on the parent GameObject of the PlayableDirector. I wonder if it's being sent to the parent objects as well?
     
  12. Midiphony-panda

    Midiphony-panda

    Joined:
    Feb 10, 2020
    Posts:
    242
    Well I have no idea as well, since the documentation isn't very extensive ^^"
    https://docs.unity3d.com/ScriptReference/Playables.INotificationReceiver.html
    https://docs.unity3d.com/ScriptReference/Playables.INotificationReceiver.OnNotify.html

    EDIT : nevermind, it's pretty well explained in the Marker customization topic.

    It's a very clear explanation @julienb , it would be nice to have it in the manual or documentation :)
     
    Last edited: Oct 5, 2021
  13. seoyeon222222

    seoyeon222222

    Joined:
    Nov 18, 2020
    Posts:
    176


    sorry for the necro

    I think your method seems dangerous.
    I made a simple example of this causing problems.
    This is a very extreme example.
    However, with the same logic,
    the same problem will arise due to unpredictable performance frame drop accuracy problems.

    upload_2023-11-6_6-23-45.png

    1. Add Debug Log to OnNotify
    2. Place PauseMarker at very short intervals,

    and try playing timeline multiple times

    There is a moment when Marker1 and Marker2 are triggered at the same time!
    In this case, since the progress is reached at Marker2,
    then Active Track's Clip state is OFF

    It's been about four years,
    Is there a better way?
     
    determi_love likes this.
  14. Yuchen_Chang

    Yuchen_Chang

    Joined:
    Apr 24, 2020
    Posts:
    126
    You could make the playable director run at a fixed frame rate, by manipulating it's play speed. By this, you could guarantee that it only move 1 frame even if there's a frame drop.
    Code (CSharp):
    1.         [SerializeField] private float targetFrameRate = 1 / 60f;
    2.         [SerializeField] private PlayableDirector director;
    3.  
    4.         private void Update()
    5.         {
    6.             if (Time.deltaTime > 0f) {
    7.                 SetTimeScale(targetFrameRate / Time.deltaTime);
    8.             }
    9.         }
    10.  
    11.         public void SetTimeScale(float timeScale)
    12.         {
    13.             var graph = director.playableGraph;
    14.             if (!graph.IsValid()) return;
    15.             var timelinePlayable = graph.GetRootPlayable(0);
    16.             if (!timelinePlayable.IsValid()) return;
    17.  
    18.             timelinePlayable.SetSpeed(timeScale);
    19.         }
     
    Whatever560 and Sarai like this.
  15. seoyeon222222

    seoyeon222222

    Joined:
    Nov 18, 2020
    Posts:
    176

    thanks :)
    Did you actually use that method in your project?
    @seant_unity say

    Isn't the fixed update loop way that seant_unity said similar to the way you suggested?
    I want to know your experience,
    Was there no problem doing SetSpeed manual setting in update?


    +
    I thought about it a little bit more,
    but the method proposed by seant_unity doesn't seem to be 100% perfect either.

    A very short clip can be skippable in unity timeline system (when frame drop)
    seant_unity adjusts the duration in the prepare frame step,
    if WaitForInputClip is very short, it will be skipped
     
  16. tsukimi

    tsukimi

    Joined:
    Dec 10, 2014
    Posts:
    77
    (Reply from another account)
    Yes, it's totally fine in my project, and no, manually setting speed in every Update() isn't equal to manually evaluating the playable graph. I'm only just setting the speed, and lets the playable graph runs on its system, which, the signals will be triggered when the playable graph updates.

    In the above example, I was just demonstrating a sample usage of SetSpeed(). In my project, I let the playable graph runs nomally, but only if the graph is going to pass a certain frame, I use SetSpeed() and let the graph to perfectly match to the frame. (there may be float errors, but it is 10^-8 level, which is way lower than interval of 1 frame)
    Here's how it works:
    In normal situation, playable graph system updates between
    Update()
    and
    LateUpdate()
    using the current
    Time.deltaTime
    . By this, you can totally check if the playable graph will run over a certain frame in the
    Update()
    phase, before the playable graph updates. Therefore, even if there's a frame drop like 1 sec, you can set the speed to be 0.001, to let the playable graph only move 0.001sec in the current update.
    Fixed speed is just the simplest method, more effort (like checking specific markers, checking the clips' start and end time, etc), more flexible behavior. It depends on what you want.
     
    seoyeon222222 likes this.
  17. seoyeon222222

    seoyeon222222

    Joined:
    Nov 18, 2020
    Posts:
    176

    Thank you for your kind reply. That was helpful :)
     
    Yuchen_Chang likes this.
  18. Sarai

    Sarai

    Joined:
    Jul 20, 2014
    Posts:
    30
    Thank you so much for sharing this code. I no longer have to worry about my clip won't get called because user's device capabilities.

    In my case, when user's framerate is > 60 fps, I just simply limit the timeScale to 1, so it doesn't get speed up
     
    tsukimi and Yuchen_Chang like this.
  19. seoyeon222222

    seoyeon222222

    Joined:
    Nov 18, 2020
    Posts:
    176



    I'm sorry, I just need to check a little more.

    For example
    Target frame rate: 1/60f (60fps)
    Actual frame rate: 30fps (avg)
    Animation length on timeline: 10Sec

    In this case, does it take 20 seconds to play the timeline?
     
  20. Yuchen_Chang

    Yuchen_Chang

    Joined:
    Apr 24, 2020
    Posts:
    126
    Yes, since every frame only run 1/60 (sec), you will need 600 frames to finish the timeline, and 600/30 = 20(sec).
     
    seoyeon222222 likes this.