Quick answer: Control Clips use ExposedReference. At runtime, re-bind via director.SetReferenceValue(clip.sourceGameObject.exposedName, sceneInstance) if the binding was lost (e.g., after instantiating from a prefab).
A cutscene timeline includes a Control Track that activates a particle prefab at a specific second. In the editor it works perfectly. At runtime, the prefab never appears, no errors. The TimelineAsset is intact; the PlayableDirector plays the timeline; only the Control Clip’s effect is missing.
Why Control Track References Get Lost
Timeline assets store references to scene GameObjects via ExposedReference. Each clip has a string “exposed name” that the asset uses to ask its containing PlayableDirector for the actual object. The binding lives on the PlayableDirector, not the asset.
When you instantiate a prefab that contains both a PlayableDirector and a TimelineAsset, the binding may or may not survive depending on how Unity serialized it. Common failure: the bind dictionary serializes scene-instance IDs that don’t exist in the loaded scene.
Fix 1: Re-Bind at Runtime
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
public class CutsceneBinder : MonoBehaviour
{
[SerializeField] PlayableDirector director;
[SerializeField] GameObject sparksPrefab;
void Start()
{
var asset = director.playableAsset as TimelineAsset;
foreach (var track in asset.GetOutputTracks())
{
if (track is ControlTrack ct)
{
foreach (var clip in ct.GetClips())
{
var control = clip.asset as ControlPlayableAsset;
director.SetReferenceValue(control.sourceGameObject.exposedName, sparksPrefab);
}
}
}
director.Play();
}
}
The binder iterates Control Clips and re-binds each sourceGameObject to a concrete scene/prefab reference. Now the Control Clip can find its target.
Fix 2: Use Bindings Field Directly
For simple cases with one binding, the Inspector’s Bindings section on PlayableDirector exposes each ExposedReference. Drag the scene GameObject into the slot. Save the scene. Bindings survive scene saves but may break on prefab instantiation.
Fix 3: Control Activation vs Active On Play
On the Control Clip Inspector:
- Active On Play — the source GameObject is set active at clip start, inactive at clip end.
- Control Activation — the parent of the source GameObject is activated. Useful when the source is a child you want to keep separately togglable.
- Post Playback → Active — final state after the clip.
If the GameObject was already active and the clip didn’t need to activate it, the clip may “run” without visibly doing anything. Check the source GameObject was inactive at clip start.
Diagnosing
Add logging to the Control:
director.played += d => Debug.Log($"played: {d.playableAsset.name}");
director.stopped += d => Debug.Log($"stopped: {d.playableAsset.name}");
If “played” fires but the Control prefab doesn’t appear, the binding is wrong — apply Fix 1. If “played” doesn’t fire, the parent director never played — check upstream control flow.
Verifying
Open the Timeline window during runtime (Window → Sequencing → Timeline) and select the PlayableDirector. The Control Clip should highlight; you should see the prefab spawned in the scene at the clip’s start time. Step through with the Pause+Step controls to confirm.
“Timeline bindings live on the director, not the asset. If your prefab spawn loses them, re-bind at runtime via SetReferenceValue.”
Build a CutsceneBinder helper component once — reuse across every cutscene-spawning prefab in the project.