Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Signal Emitter Not Setting Timeline Speed To 0 Immediately

Discussion in 'Timeline' started by Payaso_Prince, Apr 6, 2023.

  1. Payaso_Prince

    Payaso_Prince

    Joined:
    Jul 17, 2017
    Posts:
    85
    Hello All,

    I'm currently using a Signal Emitter to call a method that "stops" my Timeline by setting the speed to 0.
    Then I wait for player input to go through some dialogue and then continue the Timeline by setting its speed to 1.

    Code (CSharp):
    1. public void StopTimeline()
    2.     {
    3.         director.playableGraph.GetRootPlayable(0).SetSpeed(0);
    4.         StartCoroutine(GameEngine.Instance.DialogueManager.HandleCutsceneDialogue());
    5.     }
    The Signal Emitter is calling the method to stop the timeline at the 200 marker, but I'm getting varied results of where it actually stops. In this example, it stopped halfway between 200 & 201.


    Because of this, my characters briefly continue animating for a moment after the 200 mark when they shouldn't be animating yet.

    Is there anyway to guarantee the Timeline stopping (speed 0) immediately at the 200 mark like I want?

    An obvious work around would simply be to not animate my characters until the 201 mark, but because I'm getting varied results, I'm worried that sometimes my Timeline still won't stop in time.

    I'd like for my cutscene to look the same every time.
    What is the best way to ensure my Timeline stays consistent?
    Is using a Signal Emitter the best way to "stop" (set the speed to 0) the Timeline?

    Thanks so much for taking the time!:)
     
  2. Yuchen_Chang

    Yuchen_Chang

    Joined:
    Apr 24, 2020
    Posts:
    101
    The reason is probably that timeline is updating using
    Time.time
    , which would not perfectly match to each frame (= 0.016(s) in 60fps, depend on your setting).
    For absolute time control, maybe you can do something like, set the display fps to 50, and manually update the PlayableDirector in
    FixedUpdate()
    (which uses Time.fixedDeltaTime, which is 50fps).

    edit:
    also, I noticed there's a
    PlayableDirector.Play(FrameRate)
    method, and it seems that it can perfectly match the frame if you play by this method; However, it's internal, which means Unity want's to hide it, so I can't guarantee if there is performance drop or something inside. (It is used by Timeline Editor)

    Here's a simple extension method
    PlayableDirector.PlayAtFrameRate(FrameRateType)
    , which lets you call
    Play(FrameRate)
    indirectly. (please use at your own risk)
     

    Attached Files:

    Last edited: Apr 7, 2023
    Payaso_Prince likes this.
  3. Payaso_Prince

    Payaso_Prince

    Joined:
    Jul 17, 2017
    Posts:
    85
    Hey, I definitely appreciate the response!

    Though, I'd really rather not lock my game to a specific frame rate.
    I'm also pretty wary about using that hidden internal method.:confused:

    If Signal Emitters have this flaw then it is what it is, but is there really no other way to pause your Timeline besides using a Signal Emitter? I kind of find that really hard to believe since Timeline has been around for so long and many people use it. I'd imagine this issue would be more well known too.:(
     
  4. Yuchen_Chang

    Yuchen_Chang

    Joined:
    Apr 24, 2020
    Posts:
    101
    This is not a flaw or an issue, it's just how timeline works. You can't play something in continuous time, but request it to be paused at discrete time (the game engine will never know when you want to pause it).

    An another way is to just check if PlayableDirector is going to go across the pause marker in every Update (by checking the deltaTime), and if it is, match PlayableDirector's time to the marker time, and then pause.

    Code (CSharp):
    1.         [SerializeField] private PlayableDirector director;
    2.         [SerializeField] private SignalAsset pauseMarker;
    3.  
    4.         private List<double> pauseFrameTimes;
    5.  
    6.         private void Start()
    7.         {
    8.             var markerTrack = ((TimelineAsset)director.playableAsset).markerTrack;
    9.             // get pause assets/markers
    10.             pauseFrameTimes = markerTrack.GetMarkers()
    11.                 .OfType<SignalEmitter>() // or, your custom marker class
    12.                 .Where(se => se.asset == pauseMarker)
    13.                 .OrderBy(se => se.time)
    14.                 .Select(se => se.time)
    15.                 .ToList();
    16.         }
    17.  
    18.         private void Update()
    19.         {
    20.             PauseAndMatchToMarkerIfNeeded();
    21.         }
    22.  
    23.         private void PauseAndMatchToMarkerIfNeeded()
    24.         {
    25.             if (pauseFrameTimes.Count == 0) return;
    26.             if (director.state != PlayState.Playing) return;
    27.             // if it is going to pass through a pause marker
    28.             if (director.time < pauseFrameTimes[0] && director.time + Time.deltaTime >= pauseFrameTimes[0]) {
    29.                 // manual handle the time to fit the marker time, and pause
    30.                 director.playableGraph.Evaluate((float)(pauseFrameTimes[0] - director.duration));
    31.                 pauseFrameTimes.RemoveAt(0);
    32.  
    33.                 director.playableGraph.GetRootPlayable(0).SetSpeed(0);
    34.                 StartCoroutine(GameEngine.Instance.DialogueManager.HandleCutsceneDialogue());
    35.             }
    36.         }
    37.