When using TimelineAsset.CreatePlayable() to make a PlayableGraph PlayableGraph.GetResolver() is always null; how do we create a resolver for it?

For various reasons I want to manage my own PlayableGraph created by using TimelineAsset.CreatePlayable() (see this post for context Simple suggestion to prevent performance issues caused by Timeline / PlayableDirector).

I do this like this:

public static PlayableGraph CreateStandalonePlayableGraphWithBindingsFromDirector( this PlayableDirector director, GameObject graphOwner, Dictionary< Type, InitialiseTimelineTrackOutputBinding > dictTrackTypeToCustomBindingHandler )		
{
	var playableGraph = PlayableGraph.Create( $"{graphOwner.name}-Standalone-{director.playableAsset.name}" );
	director.playableAsset.CreatePlayable( playableGraph, graphOwner );
	
	//
	// then I set up output bindings by iterating the TimelineAsset outputs and matching against the 
	// playable graph outputs (I can share code if it helps
	//

The above works fine for all Timeline track asset types I’ve tested except ControlTrack.

ControlTrack has no output binding, and instead is made up of clips which each have a reference to a GameObject and/or a Prefab.

I can iterate the ControlTrack clips in the TimelineAsset, and should be able to set the clip bindings like this…

foreach( var timelineClip in controlTrack.GetClips() )
{
	var clipAsset          = timelineClip.asset as ControlPlayableAsset;
	var controlClipBinding = director.GetReferenceValue( clipAsset.sourceGameObject.exposedName, out var isValid );

	if( isValid )
	{
		// ...resolver is null after this line...
		var resolver = playableGraph.GetResolver();
		resolver.SetReferenceValue( clipAsset.sourceGameObject.exposedName, controlClipBinding );
	}
}

but the value returned as resolver by playableGraph.GetResolver() is always null.

How can I get a valid resolver for the PlayableGraph without it being owned by a PlayableDirector??

It seems to me that I need to create a class which implements IExposedPropertyTable which somehow links all the ExposedReference instances on the ControlPlayableAsset clips in the ControlTrack to their values.

There is little to no detail on how IExposedPropertyTable is supposed to work or what its role is exactly - I assumed that the GameObject reference on the ControlPlayableAsset were somehow set in the graph (similarly to how PlaybleOutput instances are bound to scene objects by using PlaybleOutput.SetUserData)…

…however the functions in the IExposedPropertyTable API imply that ExposedReference values are looked up in the IExposedPropertyTable in real time, and that exposed references do a live look up in the graphs’s IExposedPropertyTable at runtime do get their binding - is this correct?

I got to the bottom of this.

As it turns out, you can trivially create an IExposedPropertyTable as essentially a wrapper over a dictionary and pass that to PlayableGraph.SetResolver().

However, the behaviour of the ControlTrack asset when creating the playable(s) in inserts into the PlayableGraph is to initialise all the ExposedReference values once on creation and they are not resolved again.

For my use case of wanting to create a PlayableGraph containing a Timeline not owned by a PlayableDirector but to copy all the bindings from a PlayableDirector then you need to:

  1. ensure that all the ExposedReference values from the PlaybleDirector are in the your custom IExposedPropertyTable
  2. and that you have assigned it to the PlayableGraph you’re creating the timeline in before you pass it TimelineAsset.CreatePlayable()

The code I have is a bit large for posting inline, so I’ve made a .unitypackage of a test scene and the proof of concept code I used to achieve it - will post it in the reply to this…

I hope it helps someone else because it took me waaaay too long to work all this out!! :exploding_head: :sob:

Also, as I mentioned in this post Simple suggestion to prevent performance issues caused by Timeline / PlayableDirector

this whole thing would have been avoidable there was the option to prevent PlaybleDirector from automatically destroying its PlayableGraph on Stop()/Disable()/Timeline End
(e.g. Preserve & Reuse PlayableGraph :white_check_mark: )

Here’s a gist with the proof of concept code to:

  • manually create a PlaybleGraph from a PlayableDirector’s TimelineAsset
  • initialise it using all the bindings set up on the PlayableDirector
  • play the graph, stop the graph, restart the graph

And here’s a link to a Unity package with a sample scene showing it working: TimelineTest.unitypackage - Google Drive

Known limitations:

  • this copies bindings which are visible in the PlayableDirector’s inspector (i.e. those accessible via PlayableDirector.GetGenericBinding)
  • it also copies bindings for ControlTrack timeline tracks which rely on IExposedPropertyTable / PlayableDirector.GetReferenceValue
    • other types of timeline track which use this method of binding to scene objects will require additional code to handle them