How does the binding of Unity timeline work?

Xiao Jiang
4 min readJun 11, 2023

--

Timeline is Unity’s solution that lets you play cinematic content. It supports playing animations, particle simulation, audio clip, object activeness, as well as recording directly in timeline window. With timeline, we can create sequences that can be triggered in-game or used for cutscenes. Although timeline can be easily created, playing it at runtime can be more tricky. It’s likely that you want to play a timeline sequence using the character controlled by player in the level and handle the control back once finish, all performed seamlessly. That’s where comes the runtime binding of timeline. To easily binds timeline at runtime, you can use this free tool: https://github.com/Brian-Jiang/PragmaTimeline. The rest of this article will based on this tool.

Use PragmaTimeline to easily restore bindings of timeline at runtime

The first type of binding is GenericBinding, which are the objects that binds to the track you see in timeline editor. In runtime, you may add a PlayableDirector component and set the timeline asset, or you may load the prefab that contains it directly. But either case you will find the objects you bind to the track are empty because they are scene objects that cannot be saved in assets. To play the timeline correctly, you need to get PlayableDirector component and call SetGenericBinding where the key is the track asset of that timeline(weird, but that’s how it works), and the value is the object you want to bind. Although it gives you a way to bind, how can you know which track asset is which track in your timeline? One way is to look up by track name(which I barely change and can mess up easily), but the better way is to record and store in a config file.

Binding you set on the track are stored as generic binding in PlayableDirector
The above bindings are actually stored

In PragmaTimeline, I created a struct that stores this. It still uses a name, but you can easily edit it in inspector

[Serializable]
public struct TrackBindInfo {
public string key;
public TrackAsset trackAsset;
}

At runtime, you can iterate through all tracks and try find the generic binding of that track. BindingMap is a dictionary that you pass in to map name to object.

foreach (var track in timelineAsset.GetOutputTracks()) {
if (track == timelineAsset.markerTrack) continue;

if (trackBindMap.TryGetValue(track, out var trackBindInfo)) {
var key = trackBindInfo.key;
if (bindingMap.TryGetValue(key, out var value)) {
Director.SetGenericBinding(track, (Object) value);
}
}
}

Then the second type of binding is ExposedReference which can be used anywhere in Unity although it’s barely mentioned. It allows you to reference scene object by serialize a name and look up the name in an IExposedPropertyTable which contains the object you set earlier. When you use control track, you can see that the object referenced in a clip is using ExposedReference rather than direct reference.

A clip in control track uses exposed reference

In PragmaTimeline, I also used a struct to store this information.

[Serializable]
public struct ControlBindInfo {
public string key;
public int hash;
public ControlPlayableAsset playableAsset;
}

And similarly at runtime, they are restored

foreach (var controlBind in controlBindMap) {
var controlBindInfo = controlBind.Value;
var key = controlBindInfo.key;
if (bindingMap.TryGetValue(key, out var value)) {
Director.SetReferenceValue(controlBindInfo.hash, (Object) value);
}
}

Note that PropertyName is used as key which is a hashed string where the string is just a random guid generated when you edit timeline.

PragmaTimeline supports these two bindings that timeline uses and let you quickly edit it in inspector, by clicking the Update button, it will collection all bindings and let you name it.

Click the Update button, it will collection all bindings and let you name it

While in runtime, you can just pass a dictionary map the name to real object

var map = new Dictionary<string, object> {
{"LogoRenderer", go.GetComponentInChildren<SpriteRenderer>()},
{"LogoAnimator", go.GetComponent<Animator>()},
}
player = instance.GetComponent<TimelinePlayer>();
player.Init(map);
player.PlayTimeline(true);

PragmaTimeline also supports nested timeline where you just need to use nested dictionary

var map = new Dictionary<string, object> {
{
"Logo", new Dictionary<string, object> {
{"LogoRenderer", go.GetComponentInChildren<SpriteRenderer>()},
{"LogoAnimator", go.GetComponent<Animator>()}
}
},
}

In above example, Logo is map to the sub-timeline where two more object belong to it.

In conclusion, if you use PragmaTimeline, you can easily collect objects that need bindings, name them, and use dictionary to restore binding. Otherwise you will need to use your own way to restore them by calling SetReferenceValue and SetGenericBinding on PlayableDirector.

Try PragmaTimeline for free here: https://github.com/Brian-Jiang/PragmaTimeline

--

--

Xiao Jiang

I'm a game creator, which combines design and development. I'll write about my experience when creating games.