How to have both Timeline and a custom playable graph play nice?

We are trying to create an idle system for a character which builds a PlayableGraph that plugs a number of timelines into an AnimationMixer which then gets piped out to an AnimationOutputPlayable attached to an Animator. We want to be able to override the idle system output with timelines in the scene that drive narrative sequences. The narrative sequences are authored as plain old AnimationTracks so that our designers/artists get full use of the native UI. The problem is that if we target the same Animator from our custom PlayableGraph as well as the AnimationTrack in the timeline, the timeline no longer plays. The playable director attached to the timeline reports that its graph is invalid.

From what I understand the Animator is supposed to layer these inputs and the custom PlayableGraph is supposed to override the timeline so it sounds like this is a proper use case. Is there anything else we should consider here?

The idle system’s graph and narrative timeline graph are attached below in respective order. The red nodes on the left are AnimationPlayableOutputs.

How are you creating the playable graphs? Your post mentioned
"The playable director attached to the timeline reports that its graph is invalid"

Are you using the playable director and then modifying it’s graph, or are you compiling timeline as part of a different graph?

In either case, timeline will create it’s own animation outputs. The best approach would probably be to unbind those using animationPlayableOutput.SetTarget(null). Then create your own animation playable output and set the source playable.

What you are attempting can work, but will be a bit tricky to get everything setup correctly.

The idle system graph is created like this:

        playableGraph = PlayableGraph.Create();

        // Graph outputs to the animator driving the character
       AnimationPlayableOutput playableOutput =
            AnimationPlayableOutput.Create(playableGraph,
                                           "Character Animator",
                                           animator);

        // Create a mixer that will blend any registered playables. Should start
        // with 0 inputs.
        animMixer = AnimationMixerPlayable.Create(playableGraph, numMixerInputs);

        playableOutput.SetSourcePlayable(animMixer);
        playableOutput.SetSourceInputPort(0);

The idle system has several subsystems that each have their own mixer but those subsystem mixer outputs are connected to the inputs of the mixer listed above like this:

    public void ConnectPlayable<T>(T playable, int outputPort)
        where T : struct, IPlayable
    {
        // Hook up the playable to the next available input in the mixer
        animMixer.SetInputCount(numMixerInputs + 1);
        playableGraph.Connect(playable, outputPort, animMixer, numMixerInputs);
        numMixerInputs++;
    }

Where a subsystem would call ConnectPlayable like this:

    ConnectPlayable(subsystemMixerPlayable, 0);

The narrative sequence timelines are separate graphs that output to the same Animator and are being driven by a playable director. As part of our debugging efforts we called IsValid() on the playable director’s graph which returns true at init time (Awake) but once the director tries to start playing it returns false. Attempting to perform operations on any playables that were part of this graph also elicit the following error in the console:

InvalidOperationException: This PlayableOutput is invalid. It may have been deleted.

I saw that you mentioned rewiring the output in this thread:

We tried applying that to the narrative timeline graph like so and calling it on Start:

        // Redirect the timeline graph output to another output that we control
        PlayableGraph nisGraph = nisDirector.playableGraph;

        if (nisGraph.IsValid()) {
            AnimationPlayableOutput oldOutput = (AnimationPlayableOutput) nisGraph.GetOutputByType<AnimationPlayableOutput>(0);
            if (oldOutput.IsOutputValid() && oldOutput.GetTarget() != null) {
                output = AnimationPlayableOutput.Create(nisGraph, "fake", oldOutput.GetTarget());
                output.SetSourcePlayable(oldOutput.GetSourcePlayable());
                output.SetSourceInputPort(oldOutput.GetSourceInputPort());
                output.SetWeight(1f);
                oldOutput.SetTarget(null);
            }
        }

This doesn’t change anything though. The narrative timeline still doesn’t play and the nisDirector’s graph becomes invalid and stops after it starts to play.

I should also mention we are on 2017.4.2f2

So the part I am a bit confused about is in the two playable graphs that are shown, the first one (the CharacterAnimationGraph) has two nested timelines. Are you using playable directors to create that somehow? Or are you using TimelineAsset.CreatePlayable() to generate those subgraphs?

Overall, are you trying to have one playable graph that controls everything?

If you are using TimelineAsset.CreatePlayable() to nest timeline graphs (instead of using playable directors), that is the best approach. Not the easiest, as the playable director is there to simplify graph setup. Trying to connect Playables or Outputs that reside in different graphs is prohibited and should gives you errors. If you are trying to interleave your graph with the graph generated by a playable director, that’s is likely the reason you are getting InvalidOperation errors.

To avoid the playable director, you can try to use TimelineAsset.CreatePlayable(). It will return the root level playable, and you will need to the set the wrap mode and duration on that playable. Also, it will create PlayableOutputs, but they will come back unbound. You can leave those unbound (or even destroy them), if you are going to create your own to bind your root level mixer to.

That code disables the old outputs, and redirects to new outputs to allow explicit weight control on the output. If you are going this route, you should only have one bound output on the entire graph and the source playable should point to your mixer (no need to call SetSourceInputPort, since your output should process the entire graph). You may need to call SetPropogateSetTime(true) on your animation mixers so that the time that’s set on the mixers (when the graph is played) gets automatically passed to it’s inputs. By default, time does not propagate down the graph.


Or an alternate solution (i.e. more standard):
Have a state machine or playable graph that just plays the idle (i.e. non-timeline parts) animation.
In your timelines, optionally ease-in the first clip, ease-out the last clip (to get blending).
Turn off play on awake on your playable directors.
When you want a narrative sequence to play, simple call playableDirector.Play() on that timeline. It should transition to the animation track, and return to the previous graph/state machine when complete (assume the wrap mode is none).

I hope all my rambling helps clarify…

The timelines in the idle system are created using TimelineAsset.CreatePlayable(). The idle system also creates a mixer for blending between multiple idle timelines and then the output of that mixer is plugged into the CharacterAnimator’s playable graph. So all of that is one graph.

The narrative sequence is driven by a PlayableDirector and will generally be part of a timeline that could control a number of characters across multiple moments. I think that means the narrative sequence needs to stay a separate graph unless it’s possible to rewire the outputs of just some of the tracks as inputs into the mixer on the CharacterAnimator’s graph.

Our designers would like to be able to author idle states with timelines so that they can synchronize audio tracks and other things easily with the idle animations. If there’s no other way to do it we could fall back on the state machine route for these but it’s definitely not our first choice.

So given all of these factors what is the right way to hook this up?

No, that’s not possible. But if the idle system is playing, playing the narrative sequences from the playable director should transition to and from the idle. If that’s not working, it may have to do with order the animator plays them. The order AnimationPlayableOutput.SetTarget is called sets the priority, the last one having highest priority.

This should be possible, but any you will need to explictly set the bindings for all tracks on the PlayableOutputs created by TimelineAsset.CreatePlayable() except the animation track you want to mix. In addition, you will also need to manage the duration of the timelines, they won’t automatically stop.

Ok so that means I would have to call SetTarget on the CharacterAnimator’s graph before any PlayableDirectors that are driving narrative sequence timelines setup their graphs?

This also still doesn’t explain why the narrative sequence graph becomes invalid. If both the CharacterAnimator’s graph and the narrative sequence PlayableDirector have graphs with AnimationPlayableOutputs targeting the same animator, is it possible that calling SetTarget on one AnimationPlayableOutput could invalidate the graph of the other AnimationPlayableOutput?

Yes, your graph should be set up before any playable directors start playing.

The playable directors graph becomes invalid when the timeline is stopped - either automatically when it’s complete (I.e. it’s time goes past the duration and wrap mode is none), or when playableDirector.Stop() is called. Is either of those cases possible?

Unlikely. We don’t call Stop() on the playableDirector anywhere and since the timeline never even starts playing it doesn’t reach the end. I just tried not starting the PlayableDirector at all and it seems that sometime after swapping the output, the graph’s playable handle turns null. Any idea what could cause that?

I suspect that something is causing the timeline playable time to get set past it’s duration (that would trigger an auto-stop). If you set the playable director wrap mode to Loop, does it still happen?

So after some more digging I found that the root of the problem is that one of the idle subsystems which does some procedural animation is calling SetCurve on a generated AnimationClip. After this call, the narrative sequence’s timeline becomes invalid. Is this expected behaviour? If it is, what would be the recommended way to blend procedurally generated animation (through either linear interpolation or a skinned mesh) with other animation clips?

It’s expected behaviour if the timeline is open in the Timeline Editor. The editor will rebuild the active timeline when any animation clip is modified. The editor forcing a rebuild on the graph occurs during various modifications.

A workaround could be to you could listen to AnimationUtility.OnCurveModified and adapt if the graph becomes invalid. Although, modifying an animation clip is not supported at runtime, so depending on your application you may run into an issue there as well.

Depending on what you are doing, you could do something simple like blend in a LateUpdate call, or if you are using 2018.2 beta you could try C# animation jobs ( https://discussions.unity.com/t/697133 ).

2018.2 beta is unfortunately not an option based on where we are in our dev cycle. We’ve been experimenting with blending in a LateUpdate but the trouble is we want to blend the procedural anim with the resulting pose after the idle system and any narrative sequence timelines have all run. We can’t find a good place to capture this state after the idle system and timelines. Any recommendations?

What problem is introduced when using LateUpdate? Both the state-machine and Timeline are updated between Update and LateUpdate calls.

I guess the real challenge is that we’re trying to blend poses by interpolating the resulting quaternion from the state-machine and Timeline update with a procedurally generated pose calculated in LateUpdate. The discrepancy is in the fact that a transform that has been touched by the Animator will have been “reset” at the start of the LateUpdate call by the state-machine/Timeline update whereas a transform that was not touched during that update will retain the procedural pose from the previous LateUpdate call. We can’t find a reasonable way to differentiate between the two cases. We tried using Transform.hasChanged but that is not robust as it is set to true for any number of reasons. Is there a better way to approach this that we’re not thinking of?

There’s nothing I’m aware of that’s better, other than maybe putting custom tracks and state machine callbacks to notify that an update occurred.

@seant_unity by this do you mean use this https://docs.unity3d.com/Manual/TimelineEasingClips.html to adjust start and end of clip to be sloped start / end for blending, and then play a timeline on the director when needed?

If the timeline finishes will that mean the graph is now null / empty? or will it transition back to the graph that was playing. trying to get my head around what having a playable director component does, as I know it has its own graph. Does playing a timeline asset on a animator via a director replace the graph or simply connect new things to it?

Essentially from the quoted part above, I think I have massively misunderstood playables and timelines and believed them to be far more complicated than they actually are.

I just want an idle animation system, but one where I can trigger one shot timeline clips that have audio and other tracks, have those clips play, then transition back to the animation system. What you wrote makes it seem like that is very much in grasp and without too much headaches so eager to clarify this! :slight_smile:

EDIT: Anyone interested, the answer is yes. It is this easy. Color me happy :slight_smile:

1 Like

@seant_unity @mikew_unity
I have a question about the part where you say to call TimelineAsset.CreatePlayable() and to bind the PlayableOutputs.

I’m able to create the playable from the timeline, connect it to my PlayableGraph, and get it to play my animations and particle effects that are stored in the timeline, but I’m not able to get the sounds to play. Do you have any samples of how to bind the PlayableOutputs when using TimelineAsset.CreatePlayable?

I was previously just playing my PlayableDirectors directly instead of trying to put them into the PlayableGraph, which worked pretty well, but I wasn’t able to blend out of the Timeline at an arbitrary point, hence why I’m trying to switch to using a PlayableGraph to run the timelines.

I followed @gekidoslayer 's example to do Timeline Output binding from this forum post which worked well for PlayableDirectors, but I can’t figure out how to bind outputs for when I use TimelineAsset.CreatePlayable :frowning:

For reference, here’s what it looks like when I play the Timeline via TimelineAsset.CreatePlayable connected to my PlayableGraph:

Perhaps the reason I don’t hear any audio when I’m playing via the PlayableGraph is because I’m not connecting the audio playables to an audio output? It seems wrong that I’m connecting the audio playables to the AnimationOutput root.

And here’s what it looks like when I play the same Timeline directly through a PlayableDirector:

You can get the graphs audio outputs by using PlayableGraph.GetOutputByType(). If there isn’t one you can create one.

Then you should set the SourcePlayable to the TimelinePlayable in the graph (PlayableOutput.SetSourcePlayable()) and PlayableOutput.SetSourceOutputPort to the index of the input of the audio mixer of the timeline playable.

The output is what determines what actually gets traversed in the graph and by who. Pointing it to the timeline playable tells it where to start traversing from and makes sure the clips are correctly activated at the right time, and SetSourceOutputPort (this may be SetSourceInputPort, depending on the version) tells it to only traverse the audio subgraph.

Even if there is a subgraph of audio nodes, if they aren’t attached to an audio output, no audio will be played.