Search Unity

Question Adding a new Timeline to PlayableDirector and new bindings.

Discussion in 'Timeline' started by VOTRUBEC, Sep 17, 2020.

  1. VOTRUBEC

    VOTRUBEC

    Joined:
    Dec 17, 2014
    Posts:
    106
    Hi all,

    I've got code that works. It does what I want it to. But, I have the feeling I'm either doing too many steps, or forcing the graph to fully rebuild when I don't actually need to it.

    The premise is that I check for "details" about a an object in a current game event step. The details contain a Timeline and any "receivers" I'm wanting to bind to individual tracks.

    My concern here is that it seems I need to update the timeline in the playable director, rebuild the graph, then go through the output tracks, set the generic biding for each track (if there's a matching string), then REBUILD the graph AGAIN.

    That just doesn't feel right to me. And I'll be honest, I started digging in to customised tracks about a week ago, so I'm far from being an expert on the subject. But the documentation and examples seem pretty lacking at the moment. = /

    If anyone can shed any light on better ways to accomplish this, I'd be super appreciative. Even if it's just to say that this IS in fact the way to achieve the result. Or a no-nonsense breakdown intermediate-advanced tutorial/breakdown page or video would be cool too.

    Cheers!

    Code (CSharp):
    1.  
    2.             if ( currentStep.ObjectIsTakingPartInEvent ( o, out var details ) )
    3.             {
    4.                 var director = instance.PlayableDirector;
    5.                 director.playableAsset = details.Timeline;
    6.                 director.RebuildGraph ( );
    7.                 for ( int i = 0; i < director.playableGraph.GetOutputCount ( ); i++ )
    8.                 {
    9.                     var output = director.playableGraph.GetOutput ( i );
    10.                     // Check to see if there's a binding for the output track.
    11.                     for ( int j = 0, count = details.TrackReceviers.Count; j < count; ++j )
    12.                     {
    13.                         if ( output.GetEditorName ( ) == details.TrackReceviers [ j ].TrackName )
    14.                         {
    15.                             director.SetGenericBinding ( output.GetReferenceObject ( ), details.TrackReceviers [ j ].DialogoueRecevier );
    16.                             break;
    17.                         }
    18.                     }
    19.                 }
    20.                 director.RebuildGraph ( );
    21.                 director.time = 0;
    22.                 director.Play ( );
     
  2. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    So a couple things that can be done to avoid RebuildGraph

    1) The graph doesn't need to be rebuilt to get the bindings.

    details.Timeline.GetOutputTracks() will give you the tracks that can be bound. output.GetReferenceObject() is actually just a reference to the track. Iterate over GetOutputTracks() instead to avoid the first rebuild.

    i.e., director.SetGenericBinding(track, details.TrackReceviers [ j ].DialogoueRecevier) will work just fine.

    Alternatively, you can store a reference to the track directly instead of TrackName to avoid the iteration altogether, TrackAssets are ScriptableObjects and can be referenced like other assets.

    2) The graph shouldn't need to be rebuilt immediately prior to Play().

    director.SetGenericBinding should change the binding if the graph is already built, if not PlayableDirector.RebindPlayableGraphOutputs() can be used instead.

    And director.Play() will rebuild the graph if needed.

    You should only need to rebuild the graph if the timeline has structural changes, like adding a clip, or track. Or if you bind a track that was previously unbound (timeline may skip building unbound tracks).

    I hope that helps!
     
    juliafcarvalho likes this.
  3. VOTRUBEC

    VOTRUBEC

    Joined:
    Dec 17, 2014
    Posts:
    106
    Sean,

    thank you so much for your time. I made the modifications you suggested, and it now looks to be working correctly (as before), but with NO Graph rebuilds in my code. The trick seems to be applying the bindings to the timeline tracks, before setting the timeline to the Playable Director playableAsset.

    The one thing I didn't try implementing was storing the Track assets directly. These are ScriptableObjects, but must be hidden though. So it wouldn't be a matter of dragging and dropping them into the relevant object field for the Designers (non-technical users). I guess to use it in a visual scenario, I'd need to populate a drop down of the found tracks...? Time/money project constraints are pushing me to just leave it as is ; )

    Anyway, thanks again, and Cheers!

    Code (CSharp):
    1. if ( currentStep.ObjectIsTakingPartInEvent ( o, out var details ) )
    2. {
    3.     var director = instance.PlayableDirector;
    4.     foreach ( var t in details.Timeline.GetOutputTracks ( ) )
    5.     {
    6.         // Check to see if there's a binding for the output track.
    7.         for ( int i = 0, count = details.TrackReceviers.Count; i < count; ++i )
    8.         {
    9.             if ( t.name == details.TrackReceviers [ i ].TrackName )
    10.             {
    11.                 director.SetGenericBinding ( t, details.TrackReceviers [ i ].DialogoueRecevier );
    12.                 break;
    13.             }
    14.         }
    15.     }
    16.     director.playableAsset = details.Timeline;
    17.     director.time = 0;
    18.     director.Play ( );
    19.     return true;
    20. }
     
  4. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    You are very welcome. That looks much more efficient - rebuild graph is the most expensive call to make at runtime, so minimizing it will prevent slowdowns.

    As for the tracks, you are spot on. They have a hidden flag set, so drag and drop isn't trivial. Timeline hides everything in the file (tracks, clip assets, markers) except animation clips and the timeline asset itself - although in hindsight, maybe it shouldn't hide tracks.
     
    VOTRUBEC likes this.
  5. VOTRUBEC

    VOTRUBEC

    Joined:
    Dec 17, 2014
    Posts:
    106
    Well, all I can say is that in this particular case, it would turn out quite handy. Something like how images allow a foldout showing sprites, and Meshes on models.
     
  6. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    Agreed. In fact, here's a simple script that will do that if it helps.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Playables;
    3. using UnityEngine.Timeline;
    4.  
    5. [RequireComponent(typeof(PlayableDirector))]
    6. [ExecuteInEditMode]
    7. public class UnhideTracks : MonoBehaviour
    8. {
    9.  
    10. #if UNITY_EDITOR
    11.     void OnEnable()
    12.     {
    13.         var playableDirector = GetComponent<PlayableDirector>();
    14.         if (playableDirector == null)
    15.             return;
    16.  
    17.         var timeline = playableDirector.playableAsset as TimelineAsset;
    18.         if (timeline == null)
    19.             return;
    20.  
    21.         foreach (var track in timeline.GetOutputTracks())
    22.         {
    23.             if ((track.hideFlags & HideFlags.HideInHierarchy) != 0)
    24.             {
    25.                 track.hideFlags &= ~HideFlags.HideInHierarchy;
    26.                 UnityEditor.EditorUtility.SetDirty(track);
    27.             }
    28.         }
    29.     }
    30. #endif
    31. }
     
    Last edited: Sep 21, 2020
  7. VOTRUBEC

    VOTRUBEC

    Joined:
    Dec 17, 2014
    Posts:
    106
    Hi Sean,

    I see what you're doing there, and tried it out. But, unfortunately, I didn't see any changes to the Timeline asset.

    I just started writing out my process of what I'd tried, until I thought maybe I should try and restart the whole editor. At THAT point I could see the hidden tracks. Brilliant, this could really help the designers.

    I know this is starting to move out of the realms of your department, but I wanted to make a menu item to show/hide all the tracks in the known timelines, but I can't expect the designers to restart Unity every time they change the asset visibility. Is there a way to force the visibility changes to be displayed in the Project view without having the editor? Like a Project Window refresh? EditorApplication.RepaintProjectWindow didn't do the trick unfortunately.

    Thanks again for the feedback! Much appreciated.

    [SOLVED] AssetDatabase.SaveAssets() forced the dirty tracks to be saved! And for good measure, I left the RepaintProjectWindow in place. As it'll be a Menu Item, a second or two saving the asset database shouldn't be a deal breaker in terms of productivity.
     
    Last edited: Sep 23, 2020
  8. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    Exactly - SaveAssets is the trick! I should have added that :)