unity Rebind Control Track in Runtime

(1)

bing    PlayableDirector  RebindPlayableGraphOutputs

(2)

Rebind Control Track in Runtime

https://forum.unity.com/threads/rebind-control-track-in-runtime.597418/#post-5398689

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!

 

=====================

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.

 

=======================

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?

======================

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.

====================

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!

 

======================

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.


===================

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.547833uploading.4e448015.gif转存失败重新上传取消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.     }

(4)

Rebind Control Track in Runtime

https://forum.unity.com/threads/rebind-control-track-in-runtime.597418/

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...

==================

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.


==============

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?

 

===================

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.


=================

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!

===================

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.

=================

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.     }

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值