Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Rebind Control Track in Runtime

Discussion in 'Timeline' started by darrencperry, Dec 13, 2018.

  1. darrencperry

    darrencperry

    Joined:
    Jan 22, 2014
    Posts:
    20
    So far I've managed to reassign a control track target in the editor with the following:

    Code (CSharp):
    1.  
    2. PlayableDirector masterPlayableDirector; // assume set correctly
    3. PlayableDirector scenePlayableDirector;
    4.  
    5. TimelineAsset playable = masterPlayableDirector.playableAsset as TimelineAsset;
    6.         foreach (PlayableBinding binding in playable.outputs)
    7.         {
    8.             if (binding.sourceObject is ControlTrack)
    9.             {
    10.                 ControlTrack controlTrack = (ControlTrack)binding.sourceObject;
    11.                 if (controlTrack.name == "some track name")
    12.                 {
    13.                     foreach (TimelineClip clip in controlTrack.GetClips())
    14.                     {
    15.                         ControlPlayableAsset playableClip = (ControlPlayableAsset)clip.asset;
    16.                         masterPlayableDirector.SetReferenceValue(playableClip.sourceGameObject.exposedName, scenePlayableDirector.gameObject);
    17.                     }
    18.                 }
    19.             }
    20.         }
    but I understand there is a different process during runtime. I've accessed the ControlTrack via the playableGraph and this looks correct in the inspector during runtime but the 'child' timeline isn't playing...

    Any help greatly appreciated!
     
  2. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    That should work in the runtime as well, unless the master playable director is already playing. If it is, the simplest solution is call masterPlayableDirector.RebuildPlayableGraph(); which will reconstruct the graph on the fly. However, it may not be the most performant solution.
     
  3. darrencperry

    darrencperry

    Joined:
    Jan 22, 2014
    Posts:
    20
    Thanks for your response!

    Here's what I'm trying to achieve...
    • Master scene that loads other scenes
    • Other scenes have timelines
    • Master scene has a master audio track that everything else needs to be timed to
    • Currently I'm using control tracks to accurately play other scene's timelines
    • Control tracks need rebinding when new scenes load, timeline is always playing
    The above RebuildPlayableGraph() throws this error
    PlayableDirector::RebuildGraph can not be called while the graph is being evaluated.
    BTW I'm assuming it's actually RebuildGraph() https://docs.unity3d.com/ScriptReference/Playables.PlayableDirector.RebuildGraph.html

    Do you think a better approach is to have the other timelines inside the master scene and rebind all of their tracks when the new scene is loaded? I'm worried about the amount of tracks that will need rebinding but maybe this is a safer and more efficient approach. Although, would RebuildPlayableGraph() still need calling?
     
  4. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    Are you calling RebuildGraph() from inside a PlayableBehaviour method like PrepareFrame/OnBehaviourPlay/etc...? Because that won't work (it's sort of like calling DestroyObject(this)).

    The rebinding and call to RebindPlayableGraph() should be called from a Monobehaviour Update or OnEnable (or perhaps in your case, OnSceneLoaded)

    I think your approach is definitely worth a try, and I don't see why it wouldn't work. I played around with something similar to do cross scene bindings in timeline. It's work in progress, but I'll attach it here in case there is anything in there that may help you.
     

    Attached Files:

  5. darrencperry

    darrencperry

    Joined:
    Jan 22, 2014
    Posts:
    20
    Thanks seant!

    Yeh it was getting called from your prototype timeline event (awesome by the way) - https://forum.unity.com/threads/timeline-events.479400/#post-3185580

    I put the RebuildGraph call into a coroutine to call on the next frame but there's a terrible jump backwards on the timeline of about a second in the editor and in a build it jumps back to the start of the timeline.

    Thanks for this example, it might be the way to go. However, currently when you load a new scene while the timeline is playing, the timeline restarts from the beginning in editor and build.

    from RebuildGraph Script reference:

    RebuildGraph attempts to maintain the current playback state.
    It just seems like this is failing...

    I'm thinking I'm just going to have to trigger the scene specific timelines at the correct time and they should stay near enough in time with the master audio track, all running on DSP time...

    We can still plot out and work on the scene specific timelines in the context of the master timeline with the above script and Control Tracks so that's a help at least!
     
  6. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    Hmm..okay, that sounds like a bug. It should keep playing. You could try setting the time, but because you are using DSPtime and audio, that will likely cause an audio hitch.

    Any other track other than control track, and you could easily rebind the track on the fly. But control tracks don't use track bindings end up generating a variety of playables that each store component/gameObject references. Possible to do, but likely very tricky to get right.

    I think triggering the scene specific timelines is probably the easiest solution. If needed you might be able to synchronize them using a custom playable of some kind. The DirectorControlPlayable is used to run a subdirector in control tracks. Maybe it's feasible to modify the master playable graph on the fly and attach one (basically a custom control track).

    It's a very interesting problem, and one that I'm know others are running into. I wish I had a more bulletproof solution, but it's something we will likely look at in the near future.
     
    Flurgle and darrencperry like this.
  7. creepteks

    creepteks

    Joined:
    Jun 2, 2017
    Posts:
    19
    First of all, I have to thank you for this piece of code. It helped me a lot.
    Secondly, I am currently working on a project in which I had a scenario that was not so much different from the scenario you described here. I know it's an old post, but since it helped me, I am posting my scenario and its solution, hoping it would help others.

    here is the scenario:
    1. I have a locked box that can be opened by the player and upon its opening, the "main (parent) timeline" starts playing. This timeline is always the same.
    2. I have different animation sequences, each for a cutscene containing different recorded audio clips and comic motions. When the player opens each locked box, a specific cutscene should be played.
    3. the "main timeline" has 2 parts: the first part should start before the specific cutscene animation sequence, and the second part should start after the nested timeline has finished playing.

    I could have solved this problem by creating various timeline assets for the main timeline, which controlled the nested timeline using ControlTrack. However, I decided to have only 1 timeline asset for my main timeline and create an script that could play a generic timeline instance and wait until it finished playing.
    I hosted the repository on github so everybody can easily access it: https://github.com/Mahdad-Baghani/nestedGenericTimelineFlowControl
    here's a screenshot of the main timeline asset:
    timemachine.png a) the "timeMachineTrack" controls the flow of the nested timeline and only allow the main timeline to advance if the nested timeline has finished playing
    b) the "nestedTimeline" Control Track is there so I can set the nested timeline as source gameobject for the control track at runtime. the clip within the track allows me to mark the time the nested timeline should start playing. I just have to instantiate the prefab of the nested timeline and set the source game object of the Control track and and it will start playing at the start of the clip (if the nested timeline is marked as "play on awake", of cource; otherwise you should call Play on the its playableDirector instance).

    here is the piece of code I used for this scenario:
    Code (CSharp):
    1. public void StartTimelineInstance()
    2.     {
    3.         // instantiate the nested timeline
    4.         m_generatedNestedTimelineObject = Instantiate(m_nestedCassetteTimeline, transform);
    5.         var nestedTimeline = m_generatedNestedTimelineObject.GetComponent<PlayableDirector>();
    6.         nestedTimeline.stopped += (director) =>
    7.         {
    8.             // when the nested timeline finished playing, the parent is allowed to move forward the set marker
    9.             timelineConditionMet = true;
    10.             Destroy(m_generatedNestedTimelineObject);
    11.         };
    12.  
    13.         // create parent timeline bindings
    14.         foreach (var track in m_timelineDirector.playableAsset.outputs)
    15.         {
    16.             if (track.sourceObject is ControlTrack)
    17.             {
    18.                 ControlTrack ct = (ControlTrack)track.sourceObject;
    19.                 if (ct.name == "nestedTimeline")
    20.                 {
    21.                     foreach (TimelineClip timelineClip in ct.GetClips())
    22.                     {
    23.                         ControlPlayableAsset playableAsset = (ControlPlayableAsset)timelineClip.asset;
    24.                         playableAsset.postPlayback = ActivationControlPlayable.PostPlaybackState.Revert;
    25.                         playableAsset.updateDirector = false;
    26.                         playableAsset.updateParticle = false;
    27.  
    28.                         // set the reference of the nested timeline to the parent playable asset
    29.                         m_timelineDirector.SetReferenceValue(playableAsset.sourceGameObject.exposedName, nestedTimeline.gameObject);
    30.                         // rebind the playableGraph of the parent timeline director
    31.                         m_timelineDirector.RebindPlayableGraphOutputs();
    32.                     }
    33.                 }
    34.             }
    35.         }
    36.  
    37.         // now I can play the timeline
    38.         m_timelineDirector.Play();
    39.     }
     

    Attached Files:

    Last edited: Jan 22, 2020
    mishakozlov74 and seant_unity like this.
  8. Whatever560

    Whatever560

    Joined:
    Jan 5, 2016
    Posts:
    505
    Thanks for your investigations.
    In my case the rebind didn't work. What did is Stop the timeline, Set the reference and set it to play again. The source is then correctly taken into account and the particle system spawns in the given Transform.

    Code (CSharp):
    1. var wasPlaying = Timeline.playableGraph.IsPlaying();
    2. Timeline.Stop();
    3. playableDirector.SetReferenceValue(c.sourceGameObject.exposedName, transformToBind);
    4. //playableDirector.RebindPlayableGraphOutputs(); //Did nothing and seemed to add a micro-stutter.
    5. if(wasPlaying) Timeline.Play();