Search Unity

Question How to get the tracking binding from a timeline PlayableBehaviour?

Discussion in 'Timeline' started by riek100, Apr 2, 2023.

  1. riek100

    riek100

    Joined:
    Sep 16, 2018
    Posts:
    13
    I've been looking through the Unity docs for hours and lots of tutorials, but I can't seem to find how to get the track binding from a Playable.

    The reason I want to be able to get the track binding is so that I can store off the initial values of the component for the track in my track mixer with OnPlayableCreate. Once the timeline finishes, I want the initial values to be restored in the OnPlayableDestroy.

    Thank you for any answers, advice, or tips!
     
  2. Yuchen_Chang

    Yuchen_Chang

    Joined:
    Apr 24, 2020
    Posts:
    127
    There may be no way to find Track Binding on OnPlayableCreate, because when the playable is created, the playable may have not connected to any output or other playables (I'm not sure, I haven't check it). Same as OnPlayableDestroy, that the connection may been already destroyed.
    Also, it'll be a little tricky to retrieve binding output from playable itself. (have to recursively go up and finally find the ScriptableOutput)

    Instead, I suggest to use
    OnBehaviourPlay
    to initialize and cache the binding, and use
    OnPlayableDestroy
    to finalize the binding.
    Take Animator as the binding output type as an example:
    Code (CSharp):
    1.  
    2. private bool isInitialized = false;
    3. private Animator cachedTrackBinding = null;
    4.  
    5. public override void OnBehaviourPlay(Playable playable, FrameData info)
    6. {
    7.     // FrameData.output.GetUserData() is the TrackBinding in UnityEngine.Object form
    8.     Initialize(info.output.GetUserData());
    9. }
    10.  
    11. private void Initialize(UnityEngine.Object userData)
    12. {
    13.     // we only want to initialize at the very first time
    14.     if (isInitialized) return;
    15.  
    16.     cachedTrackBinding = userData as Animator;
    17.     if (cachedTrackBinding) {
    18.         // do initialize thing...
    19.     }
    20.     isInitialized = true;
    21. }
    22.  
    23. public override void OnPlayableDestroy(Playable playable)
    24. {
    25.     if (cachedTrackBinding) {
    26.         // do finalize thing...
    27.     }
    28. }
    29.  
    Note that
    OnBehaviourPlay
    will also be called when the timeline paused and resumed, so use a flag to Initialize only once.

    (Beware that these initialize/finalize thing will also be executed when it edit mode. If it is not desired, you can check
    Application.isPlaying
    to guard it.)
     
    Last edited: Apr 3, 2023
    planternfish, davidrochin and riek100 like this.
  3. riek100

    riek100

    Joined:
    Sep 16, 2018
    Posts:
    13
    Thank you for such a detailed answer, this was extremely helpful and much closer to the results I wanted then everything else I have tried.

    To add a bit more detail, I'm trying to create a clip that lerps an object to a position.
    upload_2023-4-2_23-17-50.png

    I have the cube start a (0,0,0) and the first clip lerps it to (10,10,10) and the second clip lerps it to (20,20,20). At the moment the cube will lerp to the position of whichever clip is playing and if I leave the timeline during editor mode, the cube will be permanently moved to the last position on the timeline. I want to have the cube reset back to its original position before any of the clips were played when in editor mode.

    Is there any way to achieve this?
     
  4. riek100

    riek100

    Joined:
    Sep 16, 2018
    Posts:
    13
    So, I finally got it using @Yuchen_Chang method with a track mixer.

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3. using UnityEngine.Playables;
    4.  
    5. public class LerpToPositionTrackMixer : PlayableBehaviour
    6. {
    7.     private bool isInitialized = false;
    8.     private Transform cachedTrackBinding;
    9.     private Vector3 initialPosition;
    10.  
    11.     public override void OnBehaviourPlay(Playable playable, FrameData info)
    12.     {
    13.         Initialize(playable, info.output.GetUserData());
    14.     }
    15.  
    16.     public override void ProcessFrame(Playable playable, FrameData info, object playerData)
    17.     {
    18.         Transform transform = playerData as Transform;
    19.  
    20.         if (!transform) { return; }
    21.  
    22.         for (int i = 0; i < playable.GetInputCount(); i++)
    23.         {
    24.             if (playable.GetInputWeight(i) > 0f)
    25.             {
    26.                 var input = playable.GetInput(i);
    27.  
    28.                 ScriptPlayable<LerpToPositionBehaviour> behaviourPlayable = (ScriptPlayable<LerpToPositionBehaviour>)input;
    29.  
    30.                 float elapsedTime = (float)(input.GetTime() / input.GetDuration());
    31.  
    32.                 Debug.Log($"Lerp of clip {i} with {elapsedTime}");
    33.  
    34.                 LerpToPositionBehaviour behaviour = behaviourPlayable.GetBehaviour();
    35.                 transform.position = Vector3.Lerp(positionsForInputs[i], behaviour.Target.position, elapsedTime);
    36.             }
    37.         }
    38.     }
    39.  
    40.     public override void OnPlayableDestroy(Playable playable)
    41.     {
    42.         if (cachedTrackBinding)
    43.         {
    44.             cachedTrackBinding.position = initialPosition;
    45.         }
    46.     }
    47.  
    48.     private List<Vector3> positionsForInputs = new List<Vector3>();
    49.  
    50.     private void Initialize(Playable playable, Object userData)
    51.     {
    52.         // we only want to initialize at the very first time
    53.         if (isInitialized) return;
    54.  
    55.         cachedTrackBinding = userData as Transform;
    56.  
    57.         if (cachedTrackBinding)
    58.         {
    59.             initialPosition = cachedTrackBinding.position;
    60.             positionsForInputs.Add(initialPosition);
    61.  
    62.             for (int i = 0; i < playable.GetInputCount(); i++)
    63.             {
    64.                 var input = playable.GetInput(i);
    65.  
    66.                 ScriptPlayable<LerpToPositionBehaviour> behaviourPlayable = (ScriptPlayable<LerpToPositionBehaviour>)input;
    67.                 LerpToPositionBehaviour behaviour = behaviourPlayable.GetBehaviour();
    68.  
    69.                 positionsForInputs.Add(behaviour.Target.position);
    70.             }
    71.         }
    72.  
    73.         isInitialized = true;
    74.     }
    75. }
    76.  
    Is this the best way to do it? I'm not sure to be honest. There is a slight bug in this script, where it doesn't lerp to snap to the final position in the very last frame of the clip, but at the very least the clip resets back to the initial values of the track binding component.
     
  5. Yuchen_Chang

    Yuchen_Chang

    Joined:
    Apr 24, 2020
    Posts:
    127
    Oh so the question is for previewing, sorry for the misunderstanding.
    For editor previewing & reverting after preview, There's a method called
    GatherProperties
    in TrackAsset. If you set the properties that may change when previewing in the method, the timeline will automatically revert them after preview. (Same system as Animation Previewing! I think)

    sample code may be like this:
    Code (CSharp):
    1.         public void GatherProperties(PlayableDirector director, IPropertyCollector driver)
    2.         {
    3.             const string kLocalPosition = "m_LocalPosition";
    4.             const string kLocalRotation = "m_LocalRotation";
    5.  
    6.             Transform trackBinding = director.GetGenericBinding(this) as Transform;
    7.             if (trackBinding == null) return;
    8.  
    9.             var trackBindingGo = trackBinding.gameObject;
    10.  
    11.             driver.AddFromName<Transform>(trackBindingGo, kLocalPosition + ".x");
    12.             driver.AddFromName<Transform>(trackBindingGo, kLocalPosition + ".y");
    13.             driver.AddFromName<Transform>(trackBindingGo, kLocalPosition + ".z");
    14.  
    15.             driver.AddFromName<Transform>(trackBindingGo, kLocalRotation + ".x");
    16.             driver.AddFromName<Transform>(trackBindingGo, kLocalRotation + ".y");
    17.             driver.AddFromName<Transform>(trackBindingGo, kLocalRotation + ".z");
    18.             driver.AddFromName<Transform>(trackBindingGo, kLocalRotation + ".w");
    19.         }
    keywords:
    GatherProperties
    ,
    IPropertyPreview
     
    riek100 likes this.
  6. riek100

    riek100

    Joined:
    Sep 16, 2018
    Posts:
    13
    This worked extremely well....words can't thank you enough. May I ask where you found all this information about timeline, any particular tutorials or documentation?
     
  7. Yuchen_Chang

    Yuchen_Chang

    Joined:
    Apr 24, 2020
    Posts:
    127
    Glad to see that helps you!
    There are seldom timeline script tutorials, but some good code samples may help you.

    I think a good start point is to look into code samples from the Timeline package (go to package manager, and import "Samples" under Timeline package), or, import the Default Playables that Unity put on the asset store.
    (Those two have almost the same sample tracks, but sample in Timeline package have additional timeline-editor samples; while Default Playables has a super easy-to-use Timeline Playable Wizard that creates all Boilerplate code for you when creating custom track)

    Ultimately, there's a super-detailed blog-post that explains the functions and relations of all important timeline classes, but it's in Japanese. if you're interested, the blog-post is here. You can use some page-translation tools of your browser, or a google-translated version is here.
    (I benefit from this post a lot)
     
    Last edited: Apr 3, 2023