Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.

Question I need advice for runtime binding

Discussion in 'Timeline' started by mishakozlov74, Jan 13, 2022.

  1. mishakozlov74

    mishakozlov74

    Joined:
    Aug 8, 2018
    Posts:
    139
    Hello!


    I want to implement some kind of GameDirector, which will run timelines during the game.
    Of course, it's going to be different scenes and since Unity doesn't support cross-scene references, I need to load scenes and bind objects on runtime.

    I managed to create scene loader, it works fine, but after scene is loaded, I need to map objects.
    I added an array with key-value pairs for track name and object name, which I'll find on runtime.
    Code (CSharp):
    1. foreach (var mappedObject in mappedObjects) {
    2.     if (mappedObject.TrackName == playableAssetOutput.streamName) {
    3.           director.SetGenericBinding(
    4.                playableAssetOutput.sourceObject,        
    5.                GameObject.Find(mappedObject.GameObjectName)
    6.           );
    7.      }
    8. }
    It works. But when I use Cinemachine, it requires reference to VirtualCameraBase for every clip, and I don't understand how to assign it properly.

    Besides that, I don't understand the difference between SetGenericBinding and SetReferenceValue, maybe it's exactly what I need?

    Thanks a lot.
     
  2. Gregoryl

    Gregoryl

    Unity Technologies

    Joined:
    Dec 22, 2016
    Posts:
    7,331
    VirtualCameraBase is a base class for VirtualCamera, FreeLook, MixingCamera, etc, so any one of those will work.
     
  3. mishakozlov74

    mishakozlov74

    Joined:
    Aug 8, 2018
    Posts:
    139
    Thanks for the reply, but how do I assign this value properly?
    I see that VirtualCamera has Resolve method, but I don't understand how to use it.
     
  4. mishakozlov74

    mishakozlov74

    Joined:
    Aug 8, 2018
    Posts:
    139
    Code (CSharp):
    1.  
    2. if (playableAssetOutput.sourceObject.GetType() == typeof(CinemachineTrack)) {
    3.    
    4.     var clips = (playableAssetOutput.sourceObject as CinemachineTrack).GetClips();
    5.  
    6.     foreach (var clip in clips) {
    7.         var mappedShot = mappedCinemachineShots.Find(s => s.TrackName == clip.displayName);
    8.         var shot = clip.asset as CinemachineShot;
    9.  
    10.         director.SetReferenceValue(
    11.             shot.VirtualCamera.exposedName,
    12.             GameObject.Find(mappedShot.GameObject).GetComponent<CinemachineVirtualCamera>()
    13.         );
    14.     }
    15.    
    16. }
    17.  
    18.  
    This is how I set these values. And in inspector everything is fine, references work, but cinemachine doesn't.

    Maybe problem is with CinemachineTrack?
     
    Last edited: Jan 14, 2022
  5. mishakozlov74

    mishakozlov74

    Joined:
    Aug 8, 2018
    Posts:
    139
    Okay, the problem is definitely with the Editor, take a look



    When timeline goes by itself, Cinemachine ignores shots, but once I clicked on specific time, it starts working.
     
    Last edited: Jan 14, 2022
  6. mishakozlov74

    mishakozlov74

    Joined:
    Aug 8, 2018
    Posts:
    139
    @Gregoryl maybe you have any ideas regarding this weird behaviour of Cinemachine?

    i feel that I need to somehow let CineachineBrain know that now it’s controlled by timeline.
     
  7. akent99

    akent99

    Joined:
    Jan 14, 2018
    Posts:
    588
    I did not want to stop deeper Unity official responses, but reading the docs it sounded like there was an ExposedReference class to help with runtime binding. Resolve() was to provide context to help with that runtime resolution into an actual object reference. This thread was interesting to me:
    https://forum.unity.com/threads/set-controlplayableasset-clips-source-game-object.528318/

    Basically allowing a reference from a TImeline asset (which can be shared between scenes) which can be resolved using runtime binding data (stored in a specific Scene).
     
  8. akent99

    akent99

    Joined:
    Jan 14, 2018
    Posts:
    588
    I assume you have checked Console for exceptions being thrown? I have had problems with Timeline going nutty when things throw exceptions. Jumping the playhead around the timeline might skip past an exception (or just reset state better due to a 'seek'). (E.g. I had a bug where animation tracks got assigned to the wrong characters. Pausing and seeking etc at runtime fixed the bindings. I assume some different code got invoked when I did the seek compared to a normal start. I worked around the problem by fixing something else that was wrong. So I could well believe exceptions thrown during "initialization" can cause some steps to be skipped, where "seek" is more robust in finishing things even when exceptions occur.)
     
  9. mishakozlov74

    mishakozlov74

    Joined:
    Aug 8, 2018
    Posts:
    139
    Hey, thanks for your reply!
    Of course, there are no errors in the console, nothing that could sign problem at all.
    All exposed references are resolved correctly.

    At this point I think that it is some kind of "Undefined behaviour" when both Timeline and Cinemachine set up at the same moment with unpredicted result.
     
  10. mishakozlov74

    mishakozlov74

    Joined:
    Aug 8, 2018
    Posts:
    139
    Okay, I found that CinemachineShotPlayable's VirtualCamera is null, even though I added it to CinemachineShot. (the clip on the track).

    I bind values at runtime, when timeline is playing.
    CinemachineShotPlayable's are created, I change only values in asset, not memory.

    I think I found the solution.. But it's the dirtiest hack in my whole life.

    In my PlayableBehaviour I load scene and subscribe to event when it's loaded

    Code (CSharp):
    1. public override void OnBehaviourPlay(Playable playable, FrameData info) {
    2.     base.OnBehaviourPlay(playable, info);
    3.    
    4.     director = playable.GetGraph().GetResolver() as PlayableDirector;
    5.    
    6.     SceneManager.sceneLoaded += OnSceneLoaded;
    7.     SceneManager.LoadSceneAsync(sceneName, addictive ? LoadSceneMode.Additive : LoadSceneMode.Single);
    8. }
    Then I loop through director.playableAsset.outputs and check
    Code (CSharp):
    1. var brain = director.GetGenericBinding(playableAssetOutput.sourceObject);
    2.  
    3. if (brain == null) {
    4.     director.GetComponent<GameDirector>().RebuildTimeline();
    5. }
    And in my GameDirector I have a function to rebuild graph in next frame by coroutine
    Code (CSharp):
    1. private IEnumerator RebuildTimelineCoroutine() {
    2.     yield return null;
    3.     _director.RebuildGraph();
    4. }
    5.  
    6. public void RebuildTimeline() {
    7.     StartCoroutine(RebuildTimelineCoroutine());
    8. }
    So next time brain variable will be set, scene not loaded, everything is fine.
    I can't believe it works.
     
    Last edited: Jan 15, 2022