Search Unity

Timeline: struggling with nuances of custom script clips

Discussion in 'Timeline' started by fastgamedev, Jun 16, 2017.

  1. fastgamedev

    fastgamedev

    Joined:
    Mar 27, 2014
    Posts:
    79
    Timelines are an amazing tool, with lots of nuances to using them. I am trying to write a custom BasicPlayableBehaviour to animate scrolling numbers in a text field, and while for the most part it's working, I am struggling with some nuances. Would appreciate your help.

    Here is the code:

    Code (CSharp):
    1. public class ScrollNumbersPlayable : BasicPlayableBehaviour {
    2.   public ExposedReference<UnityEngine.UI.Text> text;
    3.   public int startingNumber;
    4.   public int endingNumber;
    5.   private UnityEngine.UI.Text _text;
    6.  
    7.   public override void OnGraphStart(Playable playable) {
    8.     _text = text.Resolve(playable.GetGraph().GetResolver());
    9.   }            
    10.  
    11.   public override void ProcessFrame(Playable playable, FrameData info, object playerData) {
    12.     if (playable.GetTime() < 0) {
    13.       return;
    14.     }
    15.     double time = (playable.GetTime() / playable.GetDuration());
    16.     double value = startingNumber * (1 - time) + endingNumber * time;
    17.     _text.text = Math.Round(value).ToString();
    18.   }
    19. }
    The problems:
    1. The value of the text field on the last frame of the clip is wrong, because playable.GetTime() is always less than playable.GetDuration() even on the last frame. Somehow I need to know that the last frame is reached. How?
    2. In the editor, whenever the playhead is positioned before the clip starts, I want the text field value to be set to startingNumber. And whenever the playhead is positioned after the clip ends, I want it to be set to endingNumber. How do I do this?
     
  2. julienb

    julienb

    Unity Technologies

    Joined:
    Sep 9, 2016
    Posts:
    177
    For your first problem, there are two solutions, the first being an easy one and the other needing a little more setup.

    a) Use OnBehaviourPause

    In your custom PlayableBehaviour, override OnBehaviourPause and then check evaluationType from the FrameData argument. If the clip is in Playback, then you hit the end.

    Code (CSharp):
    1.  
    2. public override void OnBehaviourPause(Playable playable, FrameData info)
    3. {
    4.     if (info.evaluationType == FrameData.EvaluationType.Playback)
    5.     {
    6.          Debug.Log("End of clip");
    7.     }
    8. }
    Be aware that this will only work in play mode.

    b) Use a Mixer


    A more advanced solution involves a Track Mixer. A mixer can get access to all the playable behaviours on a given track. It also has access to the global time, which can be useful to do processing that clips cannot do, since they have only a local time. See this Subtitle example from seant_unity, which will help you setup the mixer.

    The mixer can also be used to solve your second problem.
     

    Attached Files:

    Last edited: Jun 19, 2017
  3. Nieles_GH

    Nieles_GH

    Joined:
    Jun 26, 2017
    Posts:
    57
    When I pause the timeline halfway the playable clip it is also in playback mode.
    From what I see now there is no clear way to see if the clip has actually ended.
    Why is there no onBehaviourStart amd onBehaviourEnd functions?
     
  4. playemgames

    playemgames

    Joined:
    Apr 30, 2009
    Posts:
    438
    This is pretty annoying actually, either IsDone on the Playable should tell if the Playable is done playing or give us some sort of normalized time to tell if a Playable is finished playing. Throwing a mixer into things doesn't simplify the use of playables as it was originally meant to be.

    I spent more than enough time today figuring out that double time = (playable.GetTime() / playable.GetDuration()); never gets to 1 or GetTime() never matches the GetDuration(). The simplest way would just to have a bool that tells us the Playable is done playing if it is not looping.
     
    Last edited: Jul 20, 2017
  5. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    What is happening is the scheduler (timeline) is recognizing the clip/playable time will exceed it's duration and deactivating it. This is preventing 'Done' from being set, and the time from advancing to/past the duration. A possible workaround is use OnBehaviourPause but check that playable.GetTime() + info.deltaTime >= playable.GetDuration() to distinguish between a clip completing and the timeline pausing.

    We have been discussing scheduling with the playable team lately, so we will definitely bring this up. It's something that definitely could be made easier!
     
  6. AlienMe

    AlienMe

    Joined:
    Sep 16, 2014
    Posts:
    93
    It seems that OnBehaviourPause gets called with different meanings, which is confusing:
    • When the Timeline starts playing.
    • When the Timeline is paused
    • When the Playable ended.
    Another option would be to implement an OnBehaviourEnd.
     
  7. playemgames

    playemgames

    Joined:
    Apr 30, 2009
    Posts:
    438
    I am not even using a PlayableBehavior just a plain Playable, it should be a part of it really. If I can play an AnimationClip through a Playable and am able to set a time on a Playable I should be able to get some direct value from it to see where exactly it is playing from easily. Either stick with the convention of a normalizedTime like we have with the animation state, or GetTime() should be in compared with GetDuration() and should match when it is done playing.

    This should all be as easy as it was originally touted a couple of years ago:

    Code (CSharp):
    1.  
    2. PlayableGraph playableGraph;
    3.  
    4. myAnimationClipPlayable = Playables.AnimationPlayableUtilities.PlayClip(myAnimator, clip, out playableGraph);
    5.  
    6. myAnimationClipPlayable.SetSpeed(2f);
    7.  
    8. myAnimationClipPlayable.SetTime(0.5f);
    9.  
    10. var time = myAnimationClipPlayable.GetTime();
    11.  
    12. var normalizedTime = time / myAnimationClipPlayable.GetDuration();
    13.  
    14. if (normalizedTime >= 1f || time >= myAnimationClipPlayable.GetDuration() || myAnimationClipPlayable.IsDone()) {
    15.     Debug.Log("Playable is finished playing.");
    16. }
    Also the Unity docs are really lagging behind, I am getting a lot of 404's as I go through to try to see what the API is for Playables and their tie ins with the Animator.
     
    Last edited: Jul 20, 2017
  8. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    Ah, I thought you were working in the context of timeline! Instead of GetDuration(), try using the clip length.
     
  9. playemgames

    playemgames

    Joined:
    Apr 30, 2009
    Posts:
    438
    Hmm does GetDuration() calculate it differently? I will check that out and get back to you right away. I tried adding a deltaTime value to it, but then the animations after get messed up with part positioning.
     
  10. playemgames

    playemgames

    Joined:
    Apr 30, 2009
    Posts:
    438
    Looks like it is better but my animations seem to not sync properly and some of the positions of are off and some game objects in the heirarchy are disabled when they should be enabled in the animations, is this because Playables do not benefit from the write default values of the State Machine?

    Should probably update the Unity docs for the GetDuration() function and point it to the clip.length instead to get a proper value when playing from an animation clip. Why is it different from the clip anyway if I am only playing one at a time?
     
  11. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    Playable.GetDuration() represents the duration of the playable, which is something you can set. Having PlayClip set it to the clip length probably makes sense.

    As an example, Timeline sets the duration of the playable to match the length of the clip that generated it, so a clip that loops once will have duration = 2 * animationClip.length;

    As for the positions being off, or the game objects being off or on, I believe your assessment is correct. The state machine Write Default Value is not applied to Playables.
     
  12. playemgames

    playemgames

    Joined:
    Apr 30, 2009
    Posts:
    438
    Thanks for the info, looks like I am stuck with mecanim for now, unless I want to key everything that needs to be enabled/disabled in every animation.
     
  13. Psyco92

    Psyco92

    Joined:
    Nov 15, 2013
    Posts:
    22
    this was never made easier to deal with or at least the information is not easily accessible
     
  14. OlgaB_BFX

    OlgaB_BFX

    Joined:
    Nov 19, 2020
    Posts:
    5
    Yes. I am running into the same problem. will this ever get fixed?
     
  15. xiao-xxl

    xiao-xxl

    Joined:
    Nov 16, 2018
    Posts:
    48

    More than three years have passed, are there any results?
    I don’t understand why Unity often spends several years unable to implement a simple small function?
     
  16. akent99

    akent99

    Joined:
    Jan 14, 2018
    Posts:
    588
    To add a requirement on this thread (just noticed it). I would like to know its the last frame in processFrame() so I can render a progress bar at 100%. Not sure a separate "onBehaviorEnd()" would solve that or not. (I am going to cheat and subtract a small value from the duration as a workaround.). I freezeframe the last frame, so it's really obvious when the last frame still shows the progress bar at 99.8% but it's actually finished.
     
  17. xiao-xxl

    xiao-xxl

    Joined:
    Nov 16, 2018
    Posts:
    48
    "OnBehaviourPause" works for me. But this is too ugly。
    upload_2021-11-4_17-9-38.png