Runtime modification of bindings

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

1 Like

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:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;

namespace PixelWizards.Shared.Utiltiies
{

    public class DynamicTimelineBinding : MonoBehaviour
    {
        public List<GameObject> trackList = new List<GameObject>();
        public PlayableDirector timeline;
        public TimelineAsset timelineAsset;
        public bool autoBindTracks = true;

        // Use this for initialization
        private void Start()
        {
            if (autoBindTracks)
                BindTimelineTracks();
        }

        public void BindTimelineTracks()
        {
            Debug.Log("Binding Timeline Tracks!");
            timelineAsset = (TimelineAsset)timeline.playableAsset;
            // iterate through tracks and map the objects appropriately
            for( var i = 0; i < trackList.Count; i ++)
            {
                if( trackList[i] != null)
                {
                    var track = (TrackAsset)timelineAsset.outputs[i].sourceObject;
                    timeline.SetGenericBinding(track, trackList[i]);
                }
            }
        }
    }
}

What this looks like is this:

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!

10 Likes

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…

public class Test01 : MonoBehaviour
{
    public GameObject Player;
    public TimelineAsset TimlineAsset;
    public string SourceTrackName;
    public string TargetTrackName;
    public string MoveParticleTrackName;

    private RaycastHit hit;
   
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit))
            {
                var director = new GameObject("Ability").AddComponent<PlayableDirector>();
                director.playableAsset = TimlineAsset;

                var target = hit.collider.gameObject;

                foreach (var playableAssetOutput in director.playableAsset.outputs)
                {
                    if (playableAssetOutput.streamName == SourceTrackName)
                    {
                        director.SetGenericBinding(playableAssetOutput.sourceObject, Player);
                    }
                    else if (playableAssetOutput.streamName == TargetTrackName)
                    {
                        director.SetGenericBinding(playableAssetOutput.sourceObject, target);
                    }
                    else if (playableAssetOutput.streamName == MoveParticleTrackName)
                    {
                        var tr = playableAssetOutput.sourceObject as PlayableTrack;
                        foreach (var clip in tr.GetClips())
                        {
                            var t = clip.asset as MoveObjectPlayable;
                            director.SetReferenceValue(t.LerpMoveTo.exposedName, target.transform);
                            director.SetReferenceValue(t.LerpMoveFrom.exposedName, Player.transform);
                        }
                    }
                }
                director.Play();
            }
        }
    }
}

I would agree that a Tag system would be nice.

By the way, what’s the “Control Track” ?

Thanks

3 Likes

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.

4 Likes

very useful! thanks all

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:

//You can assume these variables existed already and were properly set
PlayableGraph graph = //...;
TimelineAsset timeline = //...;

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

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

4 Likes

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 :slight_smile:

2 Likes

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.

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.

3 Likes

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

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.

using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;


public class DynamicActors : MonoBehaviour
{

    public PlayableDirector playableDirector;
    public PlayableAsset playableAsset;
    public GameObject[] newTarget = new GameObject[2];

    public void ChangeActor()
    {
        var outputs = playableAsset.outputs;
        foreach (var itm in outputs)
        {
            Debug.Log(itm.streamName);
            if(itm.streamName == "Animation Track") {
                playableDirector.SetGenericBinding(itm.sourceObject, newTarget[0]);
            }
            else if(itm.streamName == "Animation Track1")
            {
                playableDirector.SetGenericBinding(itm.sourceObject, newTarget[1]);
            }
        }
    }
}

Thanks for the help Seant, this will do in my case, although i wonder why i cant access AnimationPlayableOutput.
Cheers :slight_smile:

2 Likes

It’s under UnityEngine.Animations (with an ‘s’)

1 Like

omg PLEASE implement a tag system. I’m dying for this right now

2 Likes

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

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.

Thanks,

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

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.

[quote=“seant_unity, post:19, topic: 662589, username:seant_unity”]
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.
[/quote]

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