Search Unity

  1. Unity 2019.1 is now released.
    Dismiss Notice

Runtime modification of bindings

Discussion in 'Timeline' started by Ziboo, Apr 24, 2017.

  1. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    290
    Hi,

    I need to do some sort of Ability System for my game.
    I think Timeline could be a really cool thing to use for that.

    In the big lines I need for instance:
    - Play animation
    - Play Fx @ time x
    - Move Fx to Target
    - Play Die animation on Target

    Works fine in editor, but of course, you can fire an ability on any target in the game.

    I was wondering if it's possible to instanciate a Director with the right Timeline asset, and being able to modify a reference.

    In my case, I would need to change the Target, to reflect the one you are targeting.

    I saw that the Director has SetGenericBinding and SetReferenceValue.

    First, I'm not quite sure the difference between the two.
    And Second, it asks for a key, and don't know where to reference that...

    Thanks
     
  2. mikew_unity

    mikew_unity

    Unity Technologies

    Joined:
    Sep 27, 2016
    Posts:
    91
    you definitely can do this - the demo I'm working on for the Vision Summit next week uses this quite extensively.

    Here is a sample script that does dynamic timeline binding of tracks:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Playables;
    5. using UnityEngine.Timeline;
    6.  
    7. namespace PixelWizards.Shared.Utiltiies
    8. {
    9.  
    10.     public class DynamicTimelineBinding : MonoBehaviour
    11.     {
    12.         public List<GameObject> trackList = new List<GameObject>();
    13.         public PlayableDirector timeline;
    14.         public TimelineAsset timelineAsset;
    15.         public bool autoBindTracks = true;
    16.  
    17.         // Use this for initialization
    18.         private void Start()
    19.         {
    20.             if (autoBindTracks)
    21.                 BindTimelineTracks();
    22.         }
    23.  
    24.         public void BindTimelineTracks()
    25.         {
    26.             Debug.Log("Binding Timeline Tracks!");
    27.             timelineAsset = (TimelineAsset)timeline.playableAsset;
    28.             // iterate through tracks and map the objects appropriately
    29.             for( var i = 0; i < trackList.Count; i ++)
    30.             {
    31.                 if( trackList[i] != null)
    32.                 {
    33.                     var track = (TrackAsset)timelineAsset.outputs[i].sourceObject;
    34.                     timeline.SetGenericBinding(track, trackList[i]);
    35.                 }
    36.             }
    37.         }
    38.     }
    39. }
    What this looks like is this:

    DynamicTimelineBinding.jpg

    Basically the 'track list' on the dynamic binding script mirrors the tracks in the timeline sequence.

    You can modify this base script to do specific lookups in the scene to bind to specific tracks etc. Currently the tracks are just an array you can access, so if you change the order of tracks, it breaks the binding, which isn't ideal, but otherwise works well.

    Something we are considering / investigating going forward is having tags for tracks (similar to the general unity tags) so you can mark a track as 'character 1' or something and then look it up while binding - in a similar manner to the GameObject.FindByTag() methods that are used throughout.

    Hope this helps!
     
    Cong37921, senkal_, panta and 2 others like this.
  3. Ziboo

    Ziboo

    Joined:
    Aug 30, 2011
    Posts:
    290
    Hey,

    Thanks for the answer. At the end I figure it out, but I'm using the tracks list names.
    I also needed to modify Reference in some PlayableTrack , not very easy...

    Code (CSharp):
    1. public class Test01 : MonoBehaviour
    2. {
    3.     public GameObject Player;
    4.     public TimelineAsset TimlineAsset;
    5.     public string SourceTrackName;
    6.     public string TargetTrackName;
    7.     public string MoveParticleTrackName;
    8.  
    9.     private RaycastHit hit;
    10.    
    11.     void Update()
    12.     {
    13.         if (Input.GetMouseButtonDown(0))
    14.         {
    15.             if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit))
    16.             {
    17.                 var director = new GameObject("Ability").AddComponent<PlayableDirector>();
    18.                 director.playableAsset = TimlineAsset;
    19.  
    20.                 var target = hit.collider.gameObject;
    21.  
    22.                 foreach (var playableAssetOutput in director.playableAsset.outputs)
    23.                 {
    24.                     if (playableAssetOutput.streamName == SourceTrackName)
    25.                     {
    26.                         director.SetGenericBinding(playableAssetOutput.sourceObject, Player);
    27.                     }
    28.                     else if (playableAssetOutput.streamName == TargetTrackName)
    29.                     {
    30.                         director.SetGenericBinding(playableAssetOutput.sourceObject, target);
    31.                     }
    32.                     else if (playableAssetOutput.streamName == MoveParticleTrackName)
    33.                     {
    34.                         var tr = playableAssetOutput.sourceObject as PlayableTrack;
    35.                         foreach (var clip in tr.GetClips())
    36.                         {
    37.                             var t = clip.asset as MoveObjectPlayable;
    38.                             director.SetReferenceValue(t.LerpMoveTo.exposedName, target.transform);
    39.                             director.SetReferenceValue(t.LerpMoveFrom.exposedName, Player.transform);
    40.                         }
    41.                     }
    42.                 }
    43.                 director.Play();
    44.             }
    45.         }
    46.     }
    47. }
    I would agree that a Tag system would be nice.

    By the way, what's the "Control Track" ?

    Thanks
     
  4. mikew_unity

    mikew_unity

    Unity Technologies

    Joined:
    Sep 27, 2016
    Posts:
    91
    nice - yeah looking up track names is a good way to do this.

    Control Track is a special track that lets you control Timeline sequences in other game objects. You can think of it as a 'macro' timeline track.

    You can organize scenes in a movie, each as their own track (for example) and then add a control track to a master timeline, which then drives the other timelines. Or you could have different team members working in their own timelines and combine them together to create your master shot sequence.

    How to use:

    1) create control track
    2) create control track shot clip (right click on track -> create)
    3) select shot clip
    4) add the game object with the 'other' timeline into the 'game object' reference on the inspector

    Now when you scrub the control track, it will play the timeline sequence in the references gameobject.
     
    viscira and GameDevCouple_I like this.
  5. EliotVR

    EliotVR

    Joined:
    Jan 12, 2017
    Posts:
    5
    very useful! thanks all
     
  6. ModLunar

    ModLunar

    Joined:
    Oct 16, 2016
    Posts:
    68
    Not to be that person, lol, but I'm using my own class to manage timelines that I'm playing back during runtime as opposed to the PlayableDirector. So in my case, I don't have a PlayableDirector to do SetGenericBindings(...) for me -- instead, I'm managing my own PlayableGraph and inserting the playables generated by the TimelineAsset manually like this:

    Code (CSharp):
    1. //You can assume these variables existed already and were properly set
    2. PlayableGraph graph = //...;
    3. TimelineAsset timeline = //...;
    4.  
    5. Playable timelinePlayable = timeline.CreatePlayable(graph, gameObject);

    However, some of the AnimationTracks in my timeline sequence don't seem to be properly binding to my scene objects. Is there a way for me to set the bindings via the ScriptPlayable<TimelinePlayable> that was inserted into my PlayableGraph? I saw some stuff about UnityEngine.PlayableBinding, but it only seems to contain the key, which was weird to me -- where's the value associated with that key?
     
  7. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    841
    After the timelinePlayable is created, you can bind (or change bindings) on the PlayableOutputs of the graph. Each track (non-group, non-override track) creates a corresponding output in the graph. (side note: This is where timelineAsset.GetOutputTracks() get it's naming).

    Use AnimationPlayableOutput.SetTarget, AudioPlayableOutput.SetTarget for animation and audio tracks respectively, and PlayableOutput.SetUserData for all other track types (which are all ScriptPlayableOutputs).
     
    senkal_, GameDevCouple_I and ModLunar like this.
  8. ModLunar

    ModLunar

    Joined:
    Oct 16, 2016
    Posts:
    68
    Ah, seant, you've come to my rescue again and again with the Timeline/Playables stuff! Thank you for your answer, I like the intuition that's behind those names, it makes more sense when you put it that way :)
     
    GameDevCouple_I likes this.
  9. UnnamedGameDev

    UnnamedGameDev

    Joined:
    Jul 24, 2012
    Posts:
    15
    So i should call AnimationPlayableOutput.SetTarget on what?
    I want to have a timeline object in my scene which is referenced in my Player class. From the player class i want to tell the timeline to change the actor of the animation. Both uses humanoid rig's i just want to play the same animation with different models depending on certain conditions. Is this doable at all? Im having trouble understanding the naming and how the different components interact with eachother.
     
  10. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    841
    Let's see if I can clarify it.

    The playable director stores bindings between tracks and targets (i.e. animators, gameObjects, audioSources). When playableDirector.Play() is called it creates a PlayableGraph, which can be thought of as an instance of a timeline. Each track, creates a corresponding PlayableOutput in the graph, and the playable director sets the target of each PlayableOutput.

    This means that you can change targets prior to playing the graph using PlayableDirector.SetGenericBinding(track, target); Once the graph is playing, this will have no effect until you Stop (not Pause) the graph, and Play again - this is generally true of any changes to the timeline itself.

    Once the graph is playing, you can switch targets by modifying the PlayableOutputs on the graph directly. For example, if you know the first track is always an animation track, you can change the target using the following:

    var animationOutput = (AnimationPlayableOutput) playableDirector.playableGraph.GetOutput(0);
    animationOutput.SetTarget(myAnimator);

    Hopefully that makes it a bit more clear.
     
  11. UnnamedGameDev

    UnnamedGameDev

    Joined:
    Jul 24, 2012
    Posts:
    15
    EDIT: Apparently AnimationPlayableOutput is not a recognized type. Both AnimationPlayableAsset and AnimationPlayableUtilities is recognized, i have included the assembly references:
    using UnityEngine.Playables;
    using UnityEngine.TimeLine;
    This seems very strange...
    EDIT: Im using unity 2017.3
     
    Last edited: Mar 8, 2018
  12. UnnamedGameDev

    UnnamedGameDev

    Joined:
    Jul 24, 2012
    Posts:
    15
    So i've managed to create a function which sets the Actors of animationclips inside the timeline at runtime. It works fine if you only want to set the actors once.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Playables;
    3. using UnityEngine.Timeline;
    4.  
    5.  
    6. public class DynamicActors : MonoBehaviour
    7. {
    8.  
    9.     public PlayableDirector playableDirector;
    10.     public PlayableAsset playableAsset;
    11.     public GameObject[] newTarget = new GameObject[2];
    12.  
    13.     public void ChangeActor()
    14.     {
    15.         var outputs = playableAsset.outputs;
    16.         foreach (var itm in outputs)
    17.         {
    18.             Debug.Log(itm.streamName);
    19.             if(itm.streamName == "Animation Track") {
    20.                 playableDirector.SetGenericBinding(itm.sourceObject, newTarget[0]);
    21.             }
    22.             else if(itm.streamName == "Animation Track1")
    23.             {
    24.                 playableDirector.SetGenericBinding(itm.sourceObject, newTarget[1]);
    25.             }
    26.         }
    27.     }
    28. }
    Thanks for the help Seant, this will do in my case, although i wonder why i cant access AnimationPlayableOutput.
    Cheers :)
     
    Last edited: Mar 8, 2018
    cahumalu likes this.
  13. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    841
    It's under UnityEngine.Animations (with an 's')
     
    UnnamedGameDev likes this.
  14. mradfo21

    mradfo21

    Joined:
    May 16, 2013
    Posts:
    193
    omg PLEASE implement a tag system. I'm dying for this right now
     
    GameDevCouple_I likes this.
  15. NezeqGaash

    NezeqGaash

    Joined:
    Feb 25, 2016
    Posts:
    2
    Hey,
    I really Enjoy to use and learn about the Timeline features,
    It looks like a great way to create tools for content and graphic designers.

    Can you please help out?

    How can I do a binding to a specific Object (GameObject) that is located on specific playable ?
    I dont want to bind a track but a playable: same as it is done on the control track.

    Thanks in advance
    Adi
     
  16. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    841
    Control Tracks clips use exposed references for bindings. The exposed reference is simply an ID, and the actually binding is stored inside the PlayableDirector using Set/GetReferenceValue.
     
  17. NezeqGaash

    NezeqGaash

    Joined:
    Feb 25, 2016
    Posts:
    2
  18. Dr-Game

    Dr-Game

    Joined:
    Mar 12, 2015
    Posts:
    136
    HIHI~How can I set the Animation Clip (Override) at RunTime??
    Is it only possible in custom track?
    I want use TimeLine as a template.Want to feed character ,animationclip,specific position at running time ~~
     
    Last edited: Feb 21, 2019
  19. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    841
    By default, timeline is a template, and the bindings are specific to the playable director playing it.

    You can modify the content of the timeline using the API - e.g. walk the timeline using TimelineAsset.GetOutputTracks(), TrackAsset.GetClips(), TimelineClip.asset, etc.. - and make changes before playing the timeline. That means you can have multiple playable directors modifying the timeline, playing it, and each will play the same timeline with their own parameters.

    If you want to modify it while it's playing, you can try using PlayableDirector.RebuildGraph() after making changes. Otherwise you need to modify the playable graph (the compiled instance of the timeline), which can be tricky.
     
  20. Dr-Game

    Dr-Game

    Joined:
    Mar 12, 2015
    Posts:
    136
    Thanks to seant_unity,You help me a lot.But a wierd thing is Where is the Graph??I never find the interface in unity editor.I can find the window" Playable Graph" in the image above
     
    Last edited: Feb 26, 2019
  21. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    841
    UnityEditor.Playables.Utility.GetAllGraphs() will get you all the current playable graphs. I believe that is what the visualizer is using.
     
  22. Dr-Game

    Dr-Game

    Joined:
    Mar 12, 2015
    Posts:
    136
    Sorry I put the code line UnityEditor.Playables.Utility.GetAllGraphs() in void Start() .But noting shows!!
    The is the visualizer window " Playable Graph" ????
     
  23. FutureWang

    FutureWang

    Joined:
    May 24, 2018
    Posts:
    7
    I am doing something to auto build timeline ControlPlayableAsset use code.
    Code (CSharp):
    1. void BlendTimeLine2Main(GameObject root, PlayableBinding item, int startIndex)
    2.     {
    3.         float fTime = 0;
    4.         for (int i = startIndex; i < cameraInfos.Count + startIndex; i++)
    5.         {
    6.             var track = item.sourceObject as ControlTrack;
    7.             var clip = track.CreateDefaultClip();
    8.             clip.displayName = cameraInfos[i - startIndex].nameCamera;
    9.             var clip1 = clip.asset as ControlPlayableAsset;
    10.             //clip1.name = cameraInfos[i - startIndex].nameCamera;
    11.             var ERValue = new ExposedReference<GameObject>();
    12.             ERValue.defaultValue = root.transform.GetChild(i).gameObject;
    13.             clip1.sourceGameObject = ERValue;
    14.             clip.duration = cameraInfos[i - startIndex].time;
    15.             clip.start = fTime;
    16.             //clip1.sourceGameObject.Resolve(
    17.             //    timelineMain.GetComponent<PlayableDirector>().playableGraph.GetResolver());
    18.  
    19.             fTime += cameraInfos[i - startIndex].time;
    20.         }
    21.     }
    I create a default clip and set the ControlPlayableAsset param in editor,but when i remove the test scene(with this plugin) in Hierarchy ,and drag the scene back ,the param in ControlPlayableAsset goon. Is there miss something?
    (unity 2018.3.5f1)
     
  24. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    841
    The defaultValue of the exposed reference won't serialize scene objects. You need to bind it using the PlayableDirector, using a name.

    e.g.
    clip1.sourceGameObject.exposedName = "myExposedName" + i;
    director.SetReferenceValue("myExposedName" + i, root.transform.GetChild(i).gameObject);
     
  25. mrtenda

    mrtenda

    Joined:
    Jun 6, 2017
    Posts:
    18
    This worked in 2018.2, but I'm having various issues with binding to the timeline in this way in 2019.1:
    • Using PlayableDirector.SetGenericBinding() on the *second* item in TimelineAsset.outputs results in binding to the *first* track in the timeline window (when I would expect it to bind to the second track in the timeline window).
    • For a timeline with 2 tracks in the timeline window, TimelineAsset.outputs.Count() returns 3, when I would expect it to return 2. (PlayableDirector.playableGraph.GetOutputCount() correctly returns 2)
    It seems like there is some new inconsistency between how a timeline's tracks are represented in the timeline window vs how TimelineAsset.outputs is structured. (It also seems odd that TimelineAsset.outputs.Count() != PlayableDirector.playableGraph.GetOutputCount())

    What would the recommended way be to bind to a particular timeline track at runtime (and before the graph is played) in 2019.1?

    Here's a sample unity 2019.1.2f1 project that reproduces the issue with SetGenericBinding: https://www.dropbox.com/s/34vpf07zbbjuj5k/Timeline 2019.1 Binding Test.zip?dl=0
     
    Last edited: May 16, 2019 at 2:08 AM
  26. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    841
    Yes, there is! The inconsistency comes from animation tracks with humanoid characters. There are extra outputs in the playable graph that put a consistent T pose for the character inside the editor. In the player, or playmode, they outputs should be the same.

    You can iterate using timelineAsset.GetOutputTracks(). That gives a list of tracks that produce PlayableOutputs (i.e. excludes groups, override tracks, etc...). As in your code above the track is the input for SetGenericBinding.
     
  27. mrtenda

    mrtenda

    Joined:
    Jun 6, 2017
    Posts:
    18
    I'm not sure I understand... I'm seeing that timelineAsset.GetOutputTracks().Count() returns 3 for a timeline with two animation tracks:

    upload_2019-5-16_14-33-21.png

    Are you saying that timelineAsset.GetOutputTracks().Count() should return 2 in this case? I'm still not sure how I would correctly iterate timelineAsset.GetOutputTracks() to bind to this timeline.
     
  28. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    841
    Ah, there is an output track for the signals in 2019.1 as well. It's a hidden track that's exposed by the pin button.
     
  29. mrtenda

    mrtenda

    Joined:
    Jun 6, 2017
    Posts:
    18
    Oh, I see, that makes sense!

    Part of my confusion was because our unity project has many timelines created before signals were introduced, so our new timelines have the signals output track and our old ones don't. I was able to get things to work by checking if the first item in TimelineAsset.GetOutputTracks() was a MarkerTrack or not, and skipping over it if it is.

    Thanks again @seant_unity, you saved me again :)