Search Unity

How to know when a timeline is complete ?

Discussion in 'Timeline' started by Kiupe, Sep 4, 2017.

  1. Kiupe

    Kiupe

    Joined:
    Feb 1, 2013
    Posts:
    528
    Hi guys,

    When playing a timeline how to detect it has been completely played? I tried to compare the time and duration properties but when the timeline wrap property is set to "none" the "time" value will never be equal or greater than the "duration" value and will be reset to 0 when the timeline is completed.

    Does Unity consider to add event to that ? Because even if checking properties could work with other wrap mode it means that the code that want to know when the timeline is completed should be a Behavior in order to check in the Update method or call a Coroutines. I know it's possible to call Coroutines frome an other Behavior and for sure you could come with another solutions, but events would be very convenients.
     
    wyb314 likes this.
  2. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    You can check that the state of the PlayableDirector is not PlayState.Playing.

    We are looking at adding events to PlayableDirector to inform, at least, when a timeline is completed playing.
     
  3. Kiupe

    Kiupe

    Joined:
    Feb 1, 2013
    Posts:
    528
    Ok, but when "wrap" property is set to "hold" the state stay forever to "Playing". So there is no "one" solutino to check that. Depending of the "wrap" property value you have to check the state or compare time and duration. Not realy convenient.
     
    clownhunter and TheNullReference like this.
  4. Autarkis

    Autarkis

    Joined:
    Oct 10, 2011
    Posts:
    318
    Activate a scripted game object at the end your timeline to fire off that event.
     
    Davicro likes this.
  5. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    As far as I know there is no guarantee that event will get fired. There's no guarantee an event will be processed. So this is not reliable with low framerate or hiccup from OS, etc.

    @seant_unity please correct me if I'm wrong.
     
  6. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    You are correct. It sounds like the best course of action is a listener on the playable director that triggers when the playstate changes (completed), when the time stays the same (hold), or when the delta time goes backwards (loop).
     
  7. Kiupe

    Kiupe

    Joined:
    Feb 1, 2013
    Posts:
    528
    By the way, is it normal when the "wrap" property is set to "hold" the state is still "playing" ? I can understand why, it's like the last frame is "looping" but not sure why it's a good thing. Is cool to be able to maintain the last frame state but would it not be possible to do that in a "permanent" way that would prevent to let the director playing again and agin ?
     
    Carwashh likes this.
  8. Drapan

    Drapan

    Joined:
    Apr 9, 2013
    Posts:
    14
    When the extrapolationMode is == DirectorWrapMode.Hold you can check if it's complete by checking .time against .duration on the director.
     
  9. Kiupe

    Kiupe

    Joined:
    Feb 1, 2013
    Posts:
    528
    Yes, I know. But what bothers me here is that you have to check with different type of conditions depending on that mode. Not really intuitive nor easy. I really want a simple event that could be triggered when a timeline is complete.
     
  10. DGordon

    DGordon

    Joined:
    Dec 8, 2013
    Posts:
    649
    What we should really have is a timeline.onComplete += callback;
     
    truncoat, rlegiedz, codestage and 3 others like this.
  11. julienb

    julienb

    Unity Technologies

    Joined:
    Sep 9, 2016
    Posts:
    177
    We are currently in the process of adding Actions (onPlay, onPause) to the PlayableDirector, so you can add your callbacks through the API.
     
    akpharaoh likes this.
  12. DGordon

    DGordon

    Joined:
    Dec 8, 2013
    Posts:
    649
    Great. So long as we have a way of using code to hook into the exact moment it completes.

    Out of curiousity, what do you mean Actions? As in, System.Action? Or do you mean something more similar to UnityEvents?
     
  13. julienb

    julienb

    Unity Technologies

    Joined:
    Sep 9, 2016
    Posts:
    177
    I mean System.Action, sorry for the confusion ;)
     
  14. DGordon

    DGordon

    Joined:
    Dec 8, 2013
    Posts:
    649
    Does this mean only 1 action can be attached to onPlay? As in: timeline.onPlay = () => Debug.Log("do something");

    That works, but that means you can't just listen and unlisten for the event ... you need to make sure you're not stomping something else. Also, doesn't most of the Unity API use events?
     
    Last edited: Sep 27, 2017
  15. Kiupe

    Kiupe

    Joined:
    Feb 1, 2013
    Posts:
    528
    Please add an OnComplete event which will be trigger when the timeline duration is reached, whatever the wrap mode is.
     
  16. Carwashh

    Carwashh

    Joined:
    Jul 28, 2012
    Posts:
    762
    And onClipEnter and onClipExit
     
    mightysky and Deeeds like this.
  17. Kiupe

    Kiupe

    Joined:
    Feb 1, 2013
    Posts:
    528
    Is there a dedicated post where is it possible to follow what you are working on Timeline ?
     
    DGordon likes this.
  18. julienb

    julienb

    Unity Technologies

    Joined:
    Sep 9, 2016
    Posts:
    177
    You can do onPlay += callback with System.Action. Should work just as well as events. ;)
     
  19. DGordon

    DGordon

    Joined:
    Dec 8, 2013
    Posts:
    649
    Okay, that works then. Don't get me wrong, I use Actions and Funcs all the time, they're great ... but I'm still curious what the reason is behind using actions here instead of events? Since a lot of other Unity systems use events for this kind of functionality (ie: VideoPlayer), why is this being built differently? I would have thought consistency between different parts of the Unity API would have been enforced, especially when we're talking about the same thing from a user experience (hooking into an onComplete, or any other "event").

    Just wondering if there was something specific that warranted the difference, or its a preference of the team working on this.
     
  20. julienb

    julienb

    Unity Technologies

    Joined:
    Sep 9, 2016
    Posts:
    177
    Noted! I agree we should have a event for Timeline completion. I am not sure having onClipEnter/onClipExit events is the right solution, but we definitely need a robust way of knowing when a clip is playing or is finished.
     
    Kacper1263, Deeeds and Kiupe like this.
  21. julienb

    julienb

    Unity Technologies

    Joined:
    Sep 9, 2016
    Posts:
    177
    Sorry, I didn't use the right terms in my previous post, my bad (I should have used the word delegate, not event). So, there will be a System.Action event in the PlayableDirector. You will be able to use the += and -= operators, but not the = operator, which is a good thing. We try to avoid creating new delegates in the scripting API, so this is why System.Action is used here.
     
    Deeeds likes this.
  22. DGordon

    DGordon

    Joined:
    Dec 8, 2013
    Posts:
    649
    Ohh. That makes much more sense now. I had a feeling something was off :p. That sounds great, thanks!
     
    julienb likes this.
  23. Greyborn

    Greyborn

    Joined:
    May 26, 2016
    Posts:
    61
    Hey all, just joining this thread and wanted to ask/confirm that if what is being discussed above would allow us to accomplish the use-cases mentioned in my original question here regarding skipping (and executing certain clips that had not yet played on skipping): https://forum.unity.com/threads/skipping-support.498236/

    Thank you for any clarity on this!
     
  24. Exeneva

    Exeneva

    Joined:
    Dec 7, 2013
    Posts:
    432
    Sorry to revive an old thread, but did a way to check the completion of a timeline iteration get added in 2017.3?
     
  25. edwon

    edwon

    Joined:
    Apr 24, 2011
    Posts:
    266
    Are the timeline complete events on the way? I'm desparate for this...
     
  26. zyzyx

    zyzyx

    Joined:
    Jul 9, 2012
    Posts:
    227
    If this feature is part of the timeline events:
    Timeline events?
     
  27. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    The events on the playable director are available in 2018.1.
     
    codestage and zyzyx like this.
  28. popMark

    popMark

    Joined:
    Apr 14, 2013
    Posts:
    114
    Having trouble atm because playableDirector.state always returns Paused even when playing, is this a bug?
     
  29. JMA1

    JMA1

    Joined:
    May 2, 2018
    Posts:
    1
    I think i found a solution !
    By time keeping the PlayableDirector.time if >0.
    See if this works:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Playables;
    3.  
    4. public class TimelineController : MonoBehaviour {
    5.  
    6.     private double timeKeeper;
    7.     private PlayableDirector director;
    8.     public GameObject Intro, Level;
    9.     public bool ResetTimeline;
    10.  
    11.     // Use this for initialization
    12.     void Start () {
    13.         director = GetComponent<PlayableDirector>();
    14.         director.Play();
    15.     }
    16.  
    17.     // Update is called once per frame
    18.     void Update () {
    19.         if (director.state==PlayState.Paused && (timeKeeper+Time.deltaTime)>=director.duration)
    20.         {
    21.             //Transition animation play!!
    22.             Intro.SetActive(false);
    23.             Level.SetActive(true);
    24.         }
    25.  
    26.         if (director.time>0)
    27.         {
    28.             timeKeeper = director.time;
    29.         }
    30.  
    31.         if (ResetTimeline)
    32.         {
    33.             // Reset button
    34.             ResetTimeline = false;
    35.             // Resets level objects
    36.             Intro.SetActive(true);
    37.             Level.SetActive(false);
    38.             // Resets time keeping
    39.             timeKeeper = 0d;
    40.             // Resets director
    41.             director.Stop();
    42.             director.time = 0;
    43.             // Starts the timeline
    44.             Start();
    45.         }
    46.     }
    47. }
     
  30. Kiupe

    Kiupe

    Joined:
    Feb 1, 2013
    Posts:
    528
    Do you plan to have an OnComplete event ?

    Thanks
     
  31. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    No, but the stopped event will get called, unless it's holding or looping.
     
    forestrf likes this.
  32. Kiupe

    Kiupe

    Joined:
    Feb 1, 2013
    Posts:
    528
  33. EwieElektro

    EwieElektro

    Joined:
    Feb 22, 2016
    Posts:
    45
    in hold mode, the stopped Event are not called. (latest unity Version). :/ curriosly after i stop the Editor-Player.
     
  34. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    The stopped event is not called in Hold/Loop because the timeline isn't stopped, it's still playing. The playable director stopped event refers to timeline being stopped and releasing it's hold on any objects in the scene.
     
  35. Rodolinc

    Rodolinc

    Joined:
    Sep 23, 2013
    Posts:
    63
    So, what is the best way to know when a timeline has reached the end of its longest layer (timeline not moving)? Checking the time and duration or activating a "messenger" object? I lost some hours trying to find out why the stopped event wasnt triggering, only to find out the reason. I think you should tell about this on the documentation.
     
    Last edited: Jan 11, 2019
    dithyrambs likes this.
  36. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    Either way works. With 2019.1 you can add a signal at the end of the timeline to notify it has reached the end, even if it's about to loop or hold.

    It's a good suggestion to add that to the documentation for the stopped event, we will look into doing that.
     
    dithyrambs and Rodolinc like this.
  37. Rodolinc

    Rodolinc

    Joined:
    Sep 23, 2013
    Posts:
    63
    Thanks I ended up doing the event solution for now, but Ill suggest the team upgrading to 2019.1
     
  38. Chambers88

    Chambers88

    Joined:
    Feb 25, 2018
    Posts:
    6
    I don't recommend this because the playableDirector state returns Paused if the window loses focus (or if the editor is paused), which will make your game behave as if the timeline has ended when you click outside the window.

    BTW @seant_unity can you confirm if this is intended behavior or a bug? I personally think it is a little weird (and dangerous) because normally we don't expect that pausing the editor would affect the game state.
     
  39. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    I would have to classify it as intended behaviour, although you are right, it is little weird. It returns pause because the time isn't advancing. We really should have more states than Playing and Paused.
     
    Chambers88 likes this.
  40. khan-amil

    khan-amil

    Joined:
    Mar 29, 2012
    Posts:
    206
    Ended uo here while using Unity 2020 and sad to see that yet again another feature that looks cool in unity is still only half supported.
    An OnCOmplete event is really needed.
     
    Ziplock9000 and zIyaGtVm like this.
  41. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
  42. tweedie

    tweedie

    Joined:
    Apr 24, 2013
    Posts:
    311
    This isn't really a good suggestion / workflow, as it requires making this addition for each timeline you might play on the director, rather than a general solution for any playable on the director.

    Given how common it is to have timelines that are designed to play out and then hold, there should be some kind of event that fires when a timeline has reached it's natural end / entered it's "Wrap mode".

    In an ideal world, I'd love to see an event like
    OnStateChanged<PlayState>
    , that passed through the new state. If PlayState was expanded to include more states than just Paused and Playing, which don't provide nearly enough information, perhaps with flags indicating whether it wrapped etc, that single event would probably suffice.

    At the very least, it would be great to have an "onWrapped" event that is called whenever the timeline either plays out, holds, or loops, depending on what the director's wrap mode / extrapolationMode is set to.

    Can you explain why you are opposed to adding some form of OnComplete event? Without a good explanation, it honestly seems absurd not to have an event for this. I generally care a lot more about a timeline completing than I do about it being paused, and I want to know about that regardless of how my timeline ends/wraps.
     
  43. tweedie

    tweedie

    Joined:
    Apr 24, 2013
    Posts:
    311
    Ended up writing a small wrapper class to manage these notifications. It requires a reference to a PlayableDirector, passes on the Played/Paused/Stopped events, but also notifies you of the playable completing or wrapping. There's also a StateChanged event should you want notifications about any change of state.

    Usual warning of quick fairly-untested forum code applies etc etc

    Main component:
    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using UnityEngine;
    4. using UnityEngine.Playables;
    5.  
    6. namespace PlayableExtensions
    7. {
    8.    public class PlayableDirectorNotifications : MonoBehaviour
    9.    {
    10.       //-----------------------------------------------------------------------------------------
    11.       // Constants
    12.       //-----------------------------------------------------------------------------------------
    13.      
    14.       private const double EPSILON = 0.00001;
    15.      
    16.       //-----------------------------------------------------------------------------------------
    17.       // Events
    18.       //-----------------------------------------------------------------------------------------
    19.  
    20.       public event EventHandler Played;
    21.       public event EventHandler Paused;
    22.       public event EventHandler Stopped;
    23.       public event EventHandler Completed;
    24.      
    25.       public event EventHandler<PlayableState> StateChanged;
    26.       public event EventHandler<DirectorWrapMode> Wrapped;
    27.  
    28.       //-----------------------------------------------------------------------------------------
    29.       // Inspector Variables
    30.       //-----------------------------------------------------------------------------------------
    31.  
    32.       [SerializeField] private PlayableDirector _director = default;
    33.  
    34.       //-----------------------------------------------------------------------------------------
    35.       // Private Fields
    36.       //-----------------------------------------------------------------------------------------
    37.  
    38.       private Coroutine _watchRoutine;
    39.       private double _lastTime = double.MinValue;
    40.  
    41.       //-----------------------------------------------------------------------------------------
    42.       // Unity Lifecycle Methods
    43.       //-----------------------------------------------------------------------------------------
    44.  
    45.       private void Start()
    46.       {
    47.          //If the director played in awake, we might miss the played event invocation,
    48.          //if our OnEnable is called after its Awake
    49.          if (_director.playOnAwake)
    50.          {
    51.             Director_Played(_director);
    52.          }
    53.       }
    54.      
    55.       private void OnEnable()
    56.       {
    57.          _director.played += Director_Played;
    58.          _director.paused += Director_Paused;
    59.          _director.stopped += Director_Stopped;
    60.       }
    61.  
    62.       private void OnDisable()
    63.       {
    64.          _director.played -= Director_Played;
    65.          _director.paused -= Director_Paused;
    66.          _director.stopped -= Director_Stopped;
    67.  
    68.          if (_watchRoutine != null) StopCoroutine(_watchRoutine);
    69.       }
    70.  
    71.       //-----------------------------------------------------------------------------------------
    72.       // Event Handlers
    73.       //-----------------------------------------------------------------------------------------
    74.  
    75.       private void Director_Played(PlayableDirector director)
    76.       {
    77.          StateChanged?.Invoke(PlayableState.Playing);
    78.          Played?.Invoke();
    79.  
    80.          _watchRoutine = StartCoroutine(WatchDirector());
    81.       }
    82.  
    83.       private void Director_Paused(PlayableDirector director)
    84.       {
    85.          StateChanged?.Invoke(PlayableState.Paused);
    86.          Paused?.Invoke();
    87.  
    88.          if (_watchRoutine != null) StopCoroutine(_watchRoutine);
    89.       }
    90.  
    91.       private void Director_Stopped(PlayableDirector director)
    92.       {
    93.          StateChanged?.Invoke(PlayableState.Stopped);
    94.          Stopped?.Invoke();
    95.          
    96.          //If the delta time + the last recorded time is greater than the duration of the current playable,
    97.          //assume the playable finished playing with a Wrap mode of None, and invoke the Completed event.
    98.          if (_lastTime + Time.deltaTime >= director.playableAsset.duration)
    99.          {
    100.             _lastTime = double.MinValue;
    101.            
    102.             Completed?.Invoke();
    103.          }
    104.  
    105.          if (_watchRoutine != null) StopCoroutine(_watchRoutine);
    106.       }
    107.  
    108.       //-----------------------------------------------------------------------------------------
    109.       // Private Methods
    110.       //-----------------------------------------------------------------------------------------
    111.  
    112.       private IEnumerator WatchDirector()
    113.       {
    114.          _lastTime = double.MinValue;
    115.          
    116.          while (_director.time < EPSILON) yield return null;
    117.          
    118.          while (true)
    119.          {
    120.             //If the time is less than or equal to the last time recorded, invoke the Wrapped event
    121.             if (_director.time - _lastTime < EPSILON)
    122.             {
    123.                Wrapped?.Invoke(_director.extrapolationMode);
    124.                
    125.                //If the wrap mode is set to Hold, exit the method to avoid repeatedly invoking the method
    126.                if(_director.extrapolationMode == DirectorWrapMode.Hold) yield break;
    127.             }
    128.  
    129.             _lastTime = _director.time;
    130.             yield return null;
    131.          }
    132.       }
    133.    }
    134.    
    135.    public enum PlayableState
    136.    {
    137.       UnPlayed = 0,
    138.       Playing = 1,
    139.       Paused = 2,
    140.       Stopped = 3
    141.    }
    142.    
    143.    public delegate void EventHandler();
    144.  
    145.    public delegate void EventHandler<in T>(T value);
    146. }
    147.  

    And a Debug / test component with use examples:
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Playables;
    3.  
    4. namespace PlayableExtensions
    5. {
    6.     public class DebugDirectorNotifications : MonoBehaviour
    7.     {
    8.         //-----------------------------------------------------------------------------------------
    9.         // Inspector Variables
    10.         //-----------------------------------------------------------------------------------------
    11.  
    12.         [SerializeField] private PlayableDirectorNotifications _notifications = default;
    13.  
    14.         //-----------------------------------------------------------------------------------------
    15.         // Unity Lifecycle Methods
    16.         //-----------------------------------------------------------------------------------------
    17.  
    18.         private void OnEnable()
    19.         {
    20.             _notifications.Played += Notifications_Played;
    21.             _notifications.Paused += Notifications_Paused;
    22.             _notifications.Stopped += Notifications_Stopped;
    23.             _notifications.Completed += Notifications_Completed;
    24.             _notifications.StateChanged += Notifications_StateChanged;
    25.             _notifications.Wrapped += Notifications_Wrapped;
    26.         }
    27.  
    28.         private void OnDisable()
    29.         {
    30.             _notifications.Played -= Notifications_Played;
    31.             _notifications.Paused -= Notifications_Paused;
    32.             _notifications.Stopped -= Notifications_Stopped;
    33.             _notifications.Completed -= Notifications_Completed;
    34.             _notifications.StateChanged -= Notifications_StateChanged;
    35.             _notifications.Wrapped -= Notifications_Wrapped;
    36.         }
    37.  
    38.         //-----------------------------------------------------------------------------------------
    39.         // Event Handlers
    40.         //-----------------------------------------------------------------------------------------
    41.  
    42.         private void Notifications_Played() { Debug.Log("Played"); }
    43.  
    44.         private void Notifications_Paused() { Debug.Log("Paused"); }
    45.  
    46.         private void Notifications_Stopped() { Debug.Log("Stopped"); }
    47.      
    48.         private void Notifications_Completed() { Debug.Log("Completed"); }
    49.  
    50.         private void Notifications_StateChanged(PlayableState state) { Debug.Log(state); }
    51.  
    52.         private void Notifications_Wrapped(DirectorWrapMode wrapMode) { Debug.Log(wrapMode); }
    53.     }
    54. }

    Edit: Included a small fix for directors marked for playOnAwake

    Hope it's useful to somebody!
     

    Attached Files:

    Last edited: Sep 17, 2020
  44. frimarichard

    frimarichard

    Joined:
    Jul 24, 2017
    Posts:
    31
    PlayableDirector.reachedEnd event please, to trigger regardless of wrap mode. It would simplify so much for everyone.
     
    Ziplock9000, florianBrn and Carwashh like this.
  45. wolilio

    wolilio

    Joined:
    Aug 19, 2019
    Posts:
    29
    still no event in 2020
     
    DeadCastles and SolarFalcon like this.
  46. Carwashh

    Carwashh

    Joined:
    Jul 28, 2012
    Posts:
    762
    Last edited: Dec 31, 2020
  47. hzn403

    hzn403

    Joined:
    Apr 23, 2013
    Posts:
    8
    Is there any progress on this thread?
    I'm having the same problem and want the OnComplete event!
     
  48. WryMim

    WryMim

    Joined:
    Mar 16, 2019
    Posts:
    69
    4 years Unity can't make event OnComplete, nothing new :D:D:D:D:D

    _playable.stopped ACTUATES AT EXIT FROM SCENE AND AT COMPLETION, at that _playable.time = 0 in both cases :D:D:D:D
     
  49. Carwashh

    Carwashh

    Joined:
    Jul 28, 2012
    Posts:
    762
    * Don't want to.
     
  50. tweedie

    tweedie

    Joined:
    Apr 24, 2013
    Posts:
    311
    I find it far more offensive that we've never even had an explanation for why such a useful and common event hasn't been added.

    I will repeat from my earlier post:
    This is one of those things that smells of a system that was implemented in isolation from actual use cases or actual users, because I don't know any Unity devs (who use Timeline) that haven't complained about this.

    It's also very frustrating given this requires such a trivial amount of work to implement, it doesn't even feel right calling it a feature request. It might not be a cool, fun, fancy new feature, but it's necessary.