Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

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:
    91
    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:
    123
    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:
    48
    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:
    425
    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:
    909
    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:
    72
    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:
    425
    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:
    909
    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:
    425
    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:
    425
    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:
    909
    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:
    425
    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.